mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2026-05-05 05:11:23 +00:00
Initial update to 1.21.10 (#2304)
The server side changes are pretty straightforward: - Level.isClientSide is now a method. - NeoForge has switched to a Fabric-style item/fluid storage API. This is a fairly simple migration, as we've got support for this already! This does mean that we need a wrapper type here too. We really should support generics in the generic method system :/. Unfortunately, this is another rough update on the rendering front. The main change here is that BE renderers now a) have separate extract/render phases and b) "draw" to a scene graph (SubmitNodeCollector) rather than directly. - Update lectern models to use the model/layer system, rather than constructing model parts directly. We don't *need* to do this, but makes this a bit more consistent with vanilla. - Remove TurtleUpgradeModel.renderForLevel. I could not figure out a way to make this look nice with the separate extract/render phases. This is made worse by the fact that CC:T doesn't actually use this anywhere, so not sure what we actually need! Something to bring back in the future, if other people need it. - The turtle/monitor F3 info now uses vanilla's debug renderer system. We could maybe make the monitor one use a gizmo in future updates, so it renders coordinates in-world instead. Not sure. Unfortunately, some bits are entirely broken right now: - Breaking progress for turtles. There's no way to do custom breaking progress, so we just render this as normal custom geometry, which has all sorts of depth-buffer issues. - Monitors do not use a VBO (again, no way to do this), and render every frame. This means monitors are pretty slow to render right now (one or two is fine, but monitor arrays are unusable). Will need to submit PRs to the various mod loaders for this.
This commit is contained in:
@@ -68,5 +68,5 @@ tasks.ideaSyncTask {
|
||||
tasks.named("checkDependencyConsistency", DependencyCheck::class.java) {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
// Minecraft depends on asm, but Fabric forces it to a more recent version
|
||||
override(libs.findLibrary("asm").get(), "9.8")
|
||||
override(libs.findLibrary("asm").get(), "9.9")
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ repositories {
|
||||
includeGroup("com.simibubi.create")
|
||||
includeGroup("net.commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
includeGroup("dev.emi")
|
||||
includeGroup("maven.modrinth")
|
||||
includeGroup("me.shedaniel.cloth")
|
||||
includeGroup("me.shedaniel")
|
||||
|
||||
@@ -15,4 +15,4 @@ isUnstable=true
|
||||
modVersion=1.116.2
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.21.8
|
||||
mcVersion=1.21.10
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
||||
fabric-api = "0.129.0+1.21.8"
|
||||
fabric-loader = "0.16.14"
|
||||
neoForge = "21.8.0-beta"
|
||||
fabric-api = "0.137.0+1.21.10"
|
||||
fabric-loader = "0.17.3"
|
||||
neoForge = "21.10.46-beta"
|
||||
neoMergeTool = "2.0.0"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2025.09.14"
|
||||
parchmentMc = "1.21.8"
|
||||
yarn = "1.21.7+build.1"
|
||||
parchment = "2025.10.12"
|
||||
parchmentMc = "1.21.10"
|
||||
yarn = "1.21.10+build.2"
|
||||
|
||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||
fastutil = "8.5.15"
|
||||
@@ -36,12 +36,11 @@ kotlin-coroutines = "1.10.2"
|
||||
nightConfig = "3.8.3"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.1.7+1.21"
|
||||
fabricPermissions = "0.3.3"
|
||||
iris-fabric = "1.9.1+1.21.7-fabric"
|
||||
iris-forge = "1.9.1+1.21.7-neoforge"
|
||||
jei = "23.1.0.4"
|
||||
modmenu = "13.0.2"
|
||||
jei = "26.0.0.1"
|
||||
modmenu = "16.0.0-rc.1"
|
||||
moreRed = "6.0.0.3"
|
||||
rei = "18.0.800"
|
||||
sodium-fabric = "mc1.21.6-0.6.13-fabric"
|
||||
@@ -74,7 +73,7 @@ nullAway = "0.12.11"
|
||||
shadow = "9.2.2"
|
||||
spotless = "8.0.0"
|
||||
teavm = "0.13.0-SQUID.2"
|
||||
vanillaExtract = "0.2.1"
|
||||
vanillaExtract = "0.3.0"
|
||||
versionCatalogUpdate = "1.0.1"
|
||||
|
||||
[libraries]
|
||||
@@ -105,16 +104,15 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
# Minecraft mods
|
||||
create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" }
|
||||
create-forge = { module = "com.simibubi.create:create-1.21.1", version.ref = "create-forge" }
|
||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" }
|
||||
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" }
|
||||
jei-api = { module = "mezz.jei:jei-1.21.7-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.21.7-fabric", version.ref = "jei" }
|
||||
jei-forge = { module = "mezz.jei:jei-1.21.7-neoforge", version.ref = "jei" }
|
||||
jei-api = { module = "mezz.jei:jei-1.21.10-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.21.10-fabric", version.ref = "jei" }
|
||||
jei-forge = { module = "mezz.jei:jei-1.21.10-neoforge", version.ref = "jei" }
|
||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
|
||||
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
|
||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
|
||||
|
||||
@@ -6,12 +6,15 @@ package dan200.computercraft.api.client;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.SheetedDecalTextureGenerator;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
|
||||
import net.minecraft.client.renderer.item.BlockModelWrapper;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
@@ -19,10 +22,11 @@ import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ItemOwner;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Vector3f;
|
||||
@@ -113,7 +117,7 @@ public final class StandaloneModel {
|
||||
* Set up an {@link ItemStackRenderState.LayerRenderState} to render this model.
|
||||
*
|
||||
* @param layer The layer to set up.
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int)
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, ItemOwner, int)
|
||||
*/
|
||||
public void setupItemLayer(ItemStackRenderState.LayerRenderState layer) {
|
||||
layer.setExtents(extents);
|
||||
@@ -127,26 +131,37 @@ public final class StandaloneModel {
|
||||
* Render the model directly.
|
||||
*
|
||||
* @param transform The current pose stack transformations.
|
||||
* @param buffers The buffer source to use for rendering.
|
||||
* @param collector The node collector to render to.
|
||||
* @param light The current light texture coordinate.
|
||||
* @param overlay The current overlay texture coordinate.
|
||||
*/
|
||||
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
render(transform, buffers, light, overlay, null);
|
||||
public void submit(PoseStack transform, SubmitNodeCollector collector, int light, int overlay) {
|
||||
submit(transform, collector, light, overlay, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the model directly.
|
||||
*
|
||||
* @param transform The current pose stack transformations.
|
||||
* @param buffers The buffer source to use for rendering.
|
||||
* @param light The current light texture coordinate.
|
||||
* @param overlay The current overlay texture coordinate.
|
||||
* @param tints The tints for this model.
|
||||
* @param transform The current pose stack transformations.
|
||||
* @param collector The node collector to render to.
|
||||
* @param light The current light texture coordinate.
|
||||
* @param overlay The current overlay texture coordinate.
|
||||
* @param tints The tints for this model.
|
||||
* @param crumblingOverlay The current breaking progress.
|
||||
*/
|
||||
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay, int @Nullable [] tints) {
|
||||
var pose = transform.last();
|
||||
var buffer = buffers.getBuffer(renderType);
|
||||
public void submit(PoseStack transform, SubmitNodeCollector collector, int light, int overlay, int @Nullable [] tints, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
|
||||
collector.submitCustomGeometry(transform, renderType, (pose, buffer) -> render(pose, buffer, tints, light, overlay));
|
||||
|
||||
if (crumblingOverlay != null && renderType.affectsCrumbling()) {
|
||||
// FIXME: We need a custom hook here, which renders to crumblingBufferSource. Currently the DESTROY_TYPES
|
||||
// buffer gets flushed before the main model gets rendered.
|
||||
collector.submitCustomGeometry(transform, ModelBakery.DESTROY_TYPES.get(crumblingOverlay.progress()), (pose, buffer) ->
|
||||
render(pose, new SheetedDecalTextureGenerator(buffer, crumblingOverlay.cameraPose(), 1.0f), null, light, overlay)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void render(PoseStack.Pose pose, VertexConsumer buffer, int @Nullable [] tints, int light, int overlay) {
|
||||
for (var quad : quads) {
|
||||
float r, g, b, a;
|
||||
var idx = quad.tintIndex();
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.BlockModelWrapper;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
@@ -70,11 +67,6 @@ public final class BasicUpgradeModel implements TurtleUpgradeModel {
|
||||
getModel(side).setupItemLayer(layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
getModel(side).render(transform, buffers, light, overlay);
|
||||
}
|
||||
|
||||
private record Unbaked(ResourceLocation left, ResourceLocation right) implements TurtleUpgradeModel.Unbaked {
|
||||
@Override
|
||||
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
|
||||
|
||||
@@ -9,12 +9,10 @@ import com.mojang.math.Axis;
|
||||
import com.mojang.math.Transformation;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
@@ -23,7 +21,6 @@ import net.minecraft.client.renderer.special.SpecialModelRenderer;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -77,15 +74,6 @@ public final class ItemUpgradeModel implements TurtleUpgradeModel {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
transform.mulPose(getRenderer(side).transform().getMatrix());
|
||||
transform.mulPose(Axis.YP.rotation(Mth.PI));
|
||||
Minecraft.getInstance().getItemRenderer().renderStatic(
|
||||
upgrade.getUpgradeItem(), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0
|
||||
);
|
||||
}
|
||||
|
||||
private static final class Unbaked implements TurtleUpgradeModel.Unbaked {
|
||||
@Override
|
||||
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
|
||||
@@ -120,14 +108,14 @@ public final class ItemUpgradeModel implements TurtleUpgradeModel {
|
||||
|
||||
private record TransformedRenderer(Transformation transform) implements SpecialModelRenderer<ItemStackRenderState> {
|
||||
@Override
|
||||
public void render(
|
||||
@Nullable ItemStackRenderState state, ItemDisplayContext itemDisplayContext, PoseStack poseStack,
|
||||
MultiBufferSource multiBufferSource, int overlay, int light, boolean bl
|
||||
public void submit(
|
||||
@Nullable ItemStackRenderState state, ItemDisplayContext context, PoseStack poseStack, SubmitNodeCollector sink,
|
||||
int light, int overlay, boolean foil, int outlineColour
|
||||
) {
|
||||
if (state == null) return;
|
||||
poseStack.pushPose();
|
||||
poseStack.mulPose(transform.getMatrix());
|
||||
state.render(poseStack, multiBufferSource, overlay, light);
|
||||
state.submit(poseStack, sink, light, overlay, outlineColour);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,17 @@ package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
@@ -71,11 +68,6 @@ public final class SelectUpgradeModel<T> implements TurtleUpgradeModel {
|
||||
getModel(upgrade).renderForItem(upgrade, side, renderer, resolver, transform, seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
getModel(upgrade).renderForLevel(upgrade, side, turtle, transform, buffers, light, overlay);
|
||||
}
|
||||
|
||||
private record Unbaked<T>(
|
||||
Cases<T> cases,
|
||||
Optional<TurtleUpgradeModel.Unbaked> fallback
|
||||
|
||||
@@ -4,24 +4,21 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ItemOwner;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -67,24 +64,10 @@ public interface TurtleUpgradeModel {
|
||||
* @param resolver The model resolver.
|
||||
* @param transform The root model's transformation.
|
||||
* @param seed The current model seed.
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int)
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, ItemOwner, int)
|
||||
*/
|
||||
void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed);
|
||||
|
||||
/**
|
||||
* Render this upgrade to a {@link MultiBufferSource}. This is used for rendering the block-entity form of the
|
||||
* upgrade.
|
||||
*
|
||||
* @param upgrade The upgrade being rendered.
|
||||
* @param side Which side of the turtle (left or right) the upgrade is equipped on.
|
||||
* @param turtle Access to the turtle that the upgrade resides on.
|
||||
* @param transform The current pose stack.
|
||||
* @param buffers The buffers to render to.
|
||||
* @param light The lightmap coordinate.
|
||||
* @param overlay The overlay coordinate.
|
||||
*/
|
||||
void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay);
|
||||
|
||||
/**
|
||||
* An unbaked turtle model. Much like other unbaked models (e.g. {@link ItemModel.Unbaked}), this should resolve
|
||||
* any dependencies and returned the fully-resolved model.
|
||||
|
||||
@@ -11,12 +11,6 @@ plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
sourceSets.client {
|
||||
java {
|
||||
exclude("dan200/computercraft/client/integration/emi")
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
accessWideners(
|
||||
"src/main/resources/computercraft.accesswidener",
|
||||
@@ -45,7 +39,6 @@ dependencies {
|
||||
compileOnly(libs.mixin)
|
||||
compileOnly(libs.mixinExtra)
|
||||
compileOnly(libs.bundles.externalMods.common)
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
testFixturesAnnotationProcessor(libs.autoService)
|
||||
|
||||
@@ -7,12 +7,8 @@ package dan200.computercraft.client;
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.render.CableHighlightRenderer;
|
||||
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
|
||||
import dan200.computercraft.client.render.PocketItemRenderer;
|
||||
import dan200.computercraft.client.render.PrintoutItemRenderer;
|
||||
import dan200.computercraft.client.render.*;
|
||||
import dan200.computercraft.client.render.monitor.MonitorHighlightRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorRenderState;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
@@ -22,14 +18,12 @@ import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.client.sounds.AudioStream;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
@@ -39,8 +33,7 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Event listeners for client-only code.
|
||||
@@ -70,31 +63,36 @@ public final class ClientHooks {
|
||||
ClientPocketComputers.reset();
|
||||
}
|
||||
|
||||
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|
||||
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
|
||||
public static BlockOutlineRenderer.@Nullable Renderer drawHighlight(Camera camera, BlockHitResult hit) {
|
||||
var cable = CableHighlightRenderer.drawHighlight(camera, hit);
|
||||
if (cable != null) return cable;
|
||||
|
||||
var monitor = MonitorHighlightRenderer.drawHighlight(camera, hit);
|
||||
if (monitor != null) return monitor;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean onRenderHeldItem(
|
||||
PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand,
|
||||
PoseStack transform, SubmitNodeCollector collector, int lightTexture, InteractionHand hand,
|
||||
float pitch, float equipProgress, float swingProgress, ItemStack stack
|
||||
) {
|
||||
if (stack.getItem() instanceof PocketComputerItem) {
|
||||
PocketItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
|
||||
PocketItemRenderer.INSTANCE.renderItemFirstPerson(transform, collector, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
|
||||
return true;
|
||||
}
|
||||
if (stack.getItem() instanceof PrintoutItem) {
|
||||
PrintoutItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
|
||||
PrintoutItemRenderer.INSTANCE.renderItemFirstPerson(transform, collector, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrameRenderState frame, ExtendedItemFrameRenderState state, int light) {
|
||||
public static boolean onRenderItemFrame(PoseStack transform, SubmitNodeCollector render, ItemFrameRenderState frame, ExtendedItemFrameRenderState state) {
|
||||
if (state.printoutData != null) {
|
||||
transform.mulPose(Axis.ZP.rotationDegrees(frame.rotation * 360.0f / 8.0f));
|
||||
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, state.printoutData, state.isBook, light);
|
||||
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, state.printoutData, state.isBook);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,37 +103,6 @@ public final class ClientHooks {
|
||||
SpeakerManager.onPlayStreaming(engine, channel, stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional information about the currently targeted block to the debug screen.
|
||||
*
|
||||
* @param addText A callback which adds a single line of text.
|
||||
*/
|
||||
public static void addBlockDebugInfo(Consumer<String> addText) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
|
||||
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
|
||||
|
||||
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
|
||||
|
||||
if (tile instanceof MonitorBlockEntity monitor) {
|
||||
addText.accept("");
|
||||
addText.accept(
|
||||
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
|
||||
);
|
||||
} else if (tile instanceof TurtleBlockEntity turtle) {
|
||||
addText.accept("");
|
||||
addText.accept("Targeted turtle:");
|
||||
addText.accept(String.format("Id: %d", turtle.getComputerID()));
|
||||
addTurtleUpgrade(addText, turtle, TurtleSide.LEFT);
|
||||
addTurtleUpgrade(addText, turtle, TurtleSide.RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
|
||||
var upgrade = turtle.getAccess().getUpgradeWithData(side);
|
||||
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
|
||||
}
|
||||
|
||||
public static BlockState getBlockBreakingState(BlockState state, BlockPos pos) {
|
||||
// Only apply to cables which have both a cable and modem
|
||||
if (state.getBlock() != ModRegistry.Blocks.CABLE.get()
|
||||
|
||||
@@ -12,6 +12,9 @@ import dan200.computercraft.client.item.colour.PocketComputerLight;
|
||||
import dan200.computercraft.client.item.model.TurtleOverlayModel;
|
||||
import dan200.computercraft.client.item.properties.PocketComputerStateProperty;
|
||||
import dan200.computercraft.client.item.properties.TurtleShowElfOverlay;
|
||||
import dan200.computercraft.client.model.LecternBookModel;
|
||||
import dan200.computercraft.client.model.LecternPocketModel;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
@@ -23,11 +26,14 @@ import dan200.computercraft.client.turtle.TurtleUpgradeModelManager;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.color.item.ItemTintSource;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||
import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
|
||||
import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.builders.LayerDefinition;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
||||
@@ -51,6 +57,7 @@ import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
|
||||
@@ -193,6 +200,12 @@ public final class ClientRegistry {
|
||||
register.accept(TurtleShowElfOverlay.ID, TurtleShowElfOverlay.CODEC);
|
||||
}
|
||||
|
||||
public static void registerLayerDefinitions(BiConsumer<ModelLayerLocation, Supplier<LayerDefinition>> register) {
|
||||
register.accept(LecternBookModel.LAYER, LecternBookModel::createLayer);
|
||||
register.accept(LecternPrintoutModel.LAYER, LecternPrintoutModel::createLayer);
|
||||
register.accept(LecternPocketModel.LAYER, LecternPocketModel::createLayer);
|
||||
}
|
||||
|
||||
public interface RegisterPictureInPictureRenderer {
|
||||
<T extends PictureInPictureRenderState> void register(Class<T> state, Function<MultiBufferSource.BufferSource, PictureInPictureRenderer<T>> factory);
|
||||
}
|
||||
@@ -200,4 +213,8 @@ public final class ClientRegistry {
|
||||
public static void registerPictureInPictureRenderers(RegisterPictureInPictureRenderer register) {
|
||||
register.register(PrintoutScreen.PrintoutRenderState.class, PrintoutScreen.PrintoutPictureRenderer::new);
|
||||
}
|
||||
|
||||
public static void registerDebugScreenEntries(BiConsumer<ResourceLocation, DebugScreenEntry> register) {
|
||||
register.accept(LookingAtBlockEntityDebugEntry.ID, LookingAtBlockEntityDebugEntry.create());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -105,24 +107,24 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
public boolean keyPressed(KeyEvent event) {
|
||||
// Forward the tab key to the terminal, rather than moving between controls.
|
||||
if (key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
|
||||
return getFocused().keyPressed(key, scancode, modifiers);
|
||||
if (event.key() == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
|
||||
return getFocused().keyPressed(event);
|
||||
}
|
||||
|
||||
return super.keyPressed(key, scancode, modifiers);
|
||||
return super.keyPressed(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(double x, double y, int button) {
|
||||
public boolean mouseReleased(MouseButtonEvent event) {
|
||||
// Reimplement ContainerEventHandler.mouseReleased, as it's not called in vanilla (it is in Forge, but that
|
||||
// shouldn't matter).
|
||||
setDragging(false);
|
||||
var child = getChildAt(x, y);
|
||||
if (child.isPresent() && child.get().mouseReleased(x, y, button)) return true;
|
||||
var child = getChildAt(event.x(), event.y());
|
||||
if (child.isPresent() && child.get().mouseReleased(event)) return true;
|
||||
|
||||
return super.mouseReleased(x, y, button);
|
||||
return super.mouseReleased(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,8 +143,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double x, double y, int button) {
|
||||
var changed = super.mouseClicked(x, y, button);
|
||||
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
||||
var changed = super.mouseClicked(event, doubleClick);
|
||||
// Clicking the terminate/shutdown button steals focus, which means then pressing "enter" will click the button
|
||||
// again. Restore the focus to the terminal in these cases.
|
||||
if (getFocused() instanceof DynamicImageButton) setFocused(terminal);
|
||||
@@ -150,9 +152,9 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged(double x, double y, int button, double deltaX, double deltaY) {
|
||||
return (getFocused() != null && getFocused().mouseDragged(x, y, button, deltaX, deltaY))
|
||||
|| super.mouseDragged(x, y, button, deltaX, deltaY);
|
||||
public boolean mouseDragged(MouseButtonEvent event, double deltaX, double deltaY) {
|
||||
return (getFocused() != null && getFocused().mouseDragged(event, deltaX, deltaY))
|
||||
|| super.mouseDragged(event, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.debug.DebugEntryLookingAtBlock;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* A {@link DebugScreenEntry} that provides information about the currently looked at block entity.
|
||||
*
|
||||
* @see DebugEntryLookingAtBlock
|
||||
*/
|
||||
public final class LookingAtBlockEntityDebugEntry implements DebugScreenEntry {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "looking_at_block_entity");
|
||||
|
||||
private final Map<BlockEntityType<?>, BiConsumer<List<String>, BlockEntity>> blockEntityEmitters = new HashMap<>();
|
||||
|
||||
private LookingAtBlockEntityDebugEntry() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer displayer, @Nullable Level level, @Nullable LevelChunk clientChunk, @Nullable LevelChunk serverChunk) {
|
||||
var entity = Minecraft.getInstance().getCameraEntity();
|
||||
var trueLevel = SharedConstants.DEBUG_SHOW_SERVER_DEBUG_VALUES ? level : Minecraft.getInstance().level;
|
||||
if (entity == null || trueLevel == null) return;
|
||||
|
||||
var hitResult = entity.pick(20.0, 0.0F, false);
|
||||
if (hitResult.getType() != HitResult.Type.BLOCK) return;
|
||||
|
||||
var blockEntity = trueLevel.getBlockEntity(((BlockHitResult) hitResult).getBlockPos());
|
||||
if (blockEntity == null) return;
|
||||
var emitter = blockEntityEmitters.get(blockEntity.getType());
|
||||
if (emitter == null) return;
|
||||
|
||||
List<String> lines = new ArrayList<>();
|
||||
emitter.accept(lines, blockEntity);
|
||||
displayer.addToGroup(ID, lines);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends BlockEntity> LookingAtBlockEntityDebugEntry register(BlockEntityType<T> type, BiConsumer<List<String>, T> emit) {
|
||||
blockEntityEmitters.put(type, (BiConsumer<List<String>, BlockEntity>) emit);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static DebugScreenEntry create() {
|
||||
return new LookingAtBlockEntityDebugEntry()
|
||||
.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), LookingAtBlockEntityDebugEntry::debugMonitor)
|
||||
.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), LookingAtBlockEntityDebugEntry::debugMonitor)
|
||||
.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), LookingAtBlockEntityDebugEntry::debugTurtle)
|
||||
.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), LookingAtBlockEntityDebugEntry::debugTurtle);
|
||||
}
|
||||
|
||||
private static void debugMonitor(List<String> lines, MonitorBlockEntity monitor) {
|
||||
lines.add(
|
||||
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
|
||||
);
|
||||
}
|
||||
|
||||
private static void debugTurtle(List<String> lines, TurtleBlockEntity turtle) {
|
||||
lines.add("Targeted turtle:");
|
||||
lines.add(String.format("Id: %d", turtle.getComputerID()));
|
||||
addTurtleUpgrade(lines, turtle, TurtleSide.LEFT);
|
||||
addTurtleUpgrade(lines, turtle, TurtleSide.RIGHT);
|
||||
}
|
||||
|
||||
private static void addTurtleUpgrade(List<String> out, TurtleBlockEntity turtle, TurtleSide side) {
|
||||
var upgrade = turtle.getAccess().getUpgradeWithData(side);
|
||||
if (upgrade != null) out.add(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import net.minecraft.client.ScrollWheelHandler;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -91,13 +92,13 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
public final boolean keyPressed(KeyEvent event) {
|
||||
// Forward the tab key to the terminal, rather than moving between controls.
|
||||
if (key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
|
||||
return getFocused().keyPressed(key, scancode, modifiers);
|
||||
if (event.key() == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
|
||||
return getFocused().keyPressed(event);
|
||||
}
|
||||
|
||||
return super.keyPressed(key, scancode, modifiers);
|
||||
return super.keyPressed(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -96,7 +96,7 @@ public final class OptionScreen extends Screen {
|
||||
);
|
||||
graphics.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING, 256, 256);
|
||||
|
||||
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
|
||||
assertNonNull(messageRenderer).render(graphics, MultiLineLabel.Align.LEFT, +PADDING, y + PADDING, FONT_HEIGHT, false, 0x404040);
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
|
||||
import net.minecraft.client.gui.render.state.GuiElementRenderState;
|
||||
import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -87,18 +88,18 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
if (key == GLFW.GLFW_KEY_RIGHT) {
|
||||
public boolean keyPressed(KeyEvent event) {
|
||||
if (event.key() == GLFW.GLFW_KEY_RIGHT) {
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == GLFW.GLFW_KEY_LEFT) {
|
||||
if (event.key() == GLFW.GLFW_KEY_LEFT) {
|
||||
previousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.keyPressed(key, scancode, modifiers);
|
||||
return super.keyPressed(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,7 +186,9 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
pose.translate(-0.5f * X_SIZE, -(Y_SIZE + COVER_SIZE), 0);
|
||||
pose.scale(1.0f, 1.0f, -1.0f);
|
||||
|
||||
drawBorder(pose, bufferSource, 0, 0, 0, state.page(), state.printout().pages(), state.printout().book(), LightTexture.FULL_BRIGHT);
|
||||
var buffer = bufferSource.getBuffer(PrintoutRenderer.BACKGROUND);
|
||||
drawBorder(pose.last().pose(), buffer, 0, 0, 0, state.page(), state.printout().pages(), state.printout().book(), LightTexture.FULL_BRIGHT);
|
||||
|
||||
drawText(
|
||||
pose, bufferSource, X_TEXT_MARGIN, Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * state.page(), LightTexture.FULL_BRIGHT,
|
||||
state.printout().text(), state.printout().colour()
|
||||
|
||||
@@ -19,7 +19,9 @@ import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.gui.render.state.GuiElementRenderState;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.input.CharacterEvent;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -78,22 +80,22 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean charTyped(char ch, int modifiers) {
|
||||
var terminalChar = StringUtil.unicodeToTerminal(ch);
|
||||
public boolean charTyped(CharacterEvent event) {
|
||||
var terminalChar = StringUtil.unicodeToTerminal(event.codepoint());
|
||||
if (StringUtil.isTypableChar(terminalChar)) computer.charTyped((byte) terminalChar);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
if (key == GLFW.GLFW_KEY_ESCAPE) return false;
|
||||
if (Screen.isPaste(key)) {
|
||||
public boolean keyPressed(KeyEvent event) {
|
||||
if (event.key() == GLFW.GLFW_KEY_ESCAPE) return false;
|
||||
if (event.isPaste()) {
|
||||
paste();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
|
||||
switch (KeyConverter.physicalToActual(key, scancode)) {
|
||||
if ((event.modifiers() & GLFW.GLFW_MOD_CONTROL) != 0) {
|
||||
switch (KeyConverter.physicalToActual(event.key(), event.scancode())) {
|
||||
case GLFW.GLFW_KEY_T -> {
|
||||
if (terminateTimer < 0) terminateTimer = 0;
|
||||
}
|
||||
@@ -106,11 +108,11 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
}
|
||||
|
||||
if (key >= 0 && terminateTimer < KEY_SUPPRESS_DELAY && rebootTimer < KEY_SUPPRESS_DELAY && shutdownTimer < KEY_SUPPRESS_DELAY) {
|
||||
if (event.key() >= 0 && terminateTimer < KEY_SUPPRESS_DELAY && rebootTimer < KEY_SUPPRESS_DELAY && shutdownTimer < KEY_SUPPRESS_DELAY) {
|
||||
// Queue the "key" event and add to the down set
|
||||
var repeat = keysDown.get(key);
|
||||
keysDown.set(key);
|
||||
computer.keyDown(key, repeat);
|
||||
var repeat = keysDown.get(event.key());
|
||||
keysDown.set(event.key());
|
||||
computer.keyDown(event.key(), repeat);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -122,14 +124,14 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyReleased(int key, int scancode, int modifiers) {
|
||||
public boolean keyReleased(KeyEvent event) {
|
||||
// Queue the "key_up" event and remove from the down set
|
||||
if (key >= 0 && keysDown.get(key)) {
|
||||
keysDown.set(key, false);
|
||||
computer.keyUp(key);
|
||||
if (event.key() >= 0 && keysDown.get(event.key())) {
|
||||
keysDown.set(event.key(), false);
|
||||
computer.keyUp(event.key());
|
||||
}
|
||||
|
||||
switch (KeyConverter.physicalToActual(key, scancode)) {
|
||||
switch (KeyConverter.physicalToActual(event.key(), event.scancode())) {
|
||||
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
|
||||
case GLFW.GLFW_KEY_R -> rebootTimer = -1;
|
||||
case GLFW.GLFW_KEY_S -> shutdownTimer = -1;
|
||||
@@ -141,18 +143,18 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
||||
if (!inTermRegion(event.x(), event.y())) return false;
|
||||
if (!hasMouseSupport() || event.button() < 0 || event.button() > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
var charX = (int) ((event.x() - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((event.y() - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
computer.mouseClick(button + 1, charX + 1, charY + 1);
|
||||
computer.mouseClick(event.button() + 1, charX + 1, charY + 1);
|
||||
|
||||
lastMouseButton = button;
|
||||
lastMouseButton = event.button();
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
|
||||
@@ -160,16 +162,16 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
public boolean mouseReleased(MouseButtonEvent event) {
|
||||
if (!inTermRegion(event.x(), event.y())) return false;
|
||||
if (!hasMouseSupport() || event.button() < 0 || event.button() > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
var charX = (int) ((event.x() - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((event.y() - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
if (lastMouseButton == button) {
|
||||
if (lastMouseButton == event.button()) {
|
||||
computer.mouseUp(lastMouseButton + 1, charX + 1, charY + 1);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
@@ -181,17 +183,17 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double v2, double v3) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
public boolean mouseDragged(MouseButtonEvent event, double v2, double v3) {
|
||||
if (!inTermRegion(event.x(), event.y())) return false;
|
||||
if (!hasMouseSupport() || event.button() < 0 || event.button() > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
var charX = (int) ((event.x() - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((event.y() - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
if (button == lastMouseButton && (charX != lastMouseX || charY != lastMouseY)) {
|
||||
computer.mouseDrag(button + 1, charX + 1, charY + 1);
|
||||
if (event.button() == lastMouseButton && (charX != lastMouseX || charY != lastMouseY)) {
|
||||
computer.mouseDrag(event.button() + 1, charX + 1, charY + 1);
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
}
|
||||
@@ -308,9 +310,8 @@ public class TerminalWidget extends AbstractWidget {
|
||||
@Nullable ScreenRectangle scissorArea
|
||||
) implements GuiElementRenderState {
|
||||
@Override
|
||||
public void buildVertices(VertexConsumer vertexConsumer, float z) {
|
||||
var quads = new FixedWidthFontRenderer.QuadEmitter(new Matrix4f().mul(pose).translate(0, 0, z), vertexConsumer);
|
||||
FixedWidthFontRenderer.drawTerminalBackground(quads, x, y, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
|
||||
public void buildVertices(VertexConsumer buffer) {
|
||||
FixedWidthFontRenderer.drawTerminalBackground(new Matrix4f().mul(pose), buffer, x, y, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -324,14 +325,14 @@ public class TerminalWidget extends AbstractWidget {
|
||||
@Nullable ScreenRectangle bounds, @Nullable ScreenRectangle scissorArea
|
||||
) implements GuiElementRenderState {
|
||||
@Override
|
||||
public void buildVertices(VertexConsumer vertexConsumer, float z) {
|
||||
var quads = new FixedWidthFontRenderer.QuadEmitter(new Matrix4f().mul(pose).translate(0, 0, z), vertexConsumer);
|
||||
FixedWidthFontRenderer.drawTerminalForeground(quads, x, y, terminal);
|
||||
FixedWidthFontRenderer.drawCursor(quads, x, y, terminal);
|
||||
public void buildVertices(VertexConsumer buffer) {
|
||||
var transform = new Matrix4f().mul(pose);
|
||||
FixedWidthFontRenderer.drawTerminalForeground(transform, buffer, x, y, terminal);
|
||||
FixedWidthFontRenderer.drawCursor(transform, buffer, x, y, terminal);
|
||||
|
||||
// The GUI renderer requires that the buffer is non-empty. Add a zero-size vertex so we always have something.
|
||||
for (var i = 0; i < 4; i++) {
|
||||
vertexConsumer.addVertex(0, 0, z).setColor(0x00ffffff).setUv(0, 0).setLight(LightTexture.FULL_BRIGHT);
|
||||
buffer.addVertex(0, 0, 0).setColor(0x00ffffff).setUv(0, 0).setLight(LightTexture.FULL_BRIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.integration.emi;
|
||||
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import dev.emi.emi.api.EmiEntrypoint;
|
||||
import dev.emi.emi.api.EmiPlugin;
|
||||
import dev.emi.emi.api.EmiRegistry;
|
||||
import dev.emi.emi.api.stack.Comparison;
|
||||
import dev.emi.emi.api.stack.EmiStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@EmiEntrypoint
|
||||
public class EMIComputerCraft implements EmiPlugin {
|
||||
@Override
|
||||
public void register(EmiRegistry registry) {
|
||||
registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
|
||||
registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
|
||||
|
||||
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
|
||||
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
|
||||
|
||||
for (var stack : RecipeModHelpers.getExtraStacks(Minecraft.getInstance().level.registryAccess())) {
|
||||
registry.addEmiStack(EmiStack.of(stack));
|
||||
}
|
||||
}
|
||||
|
||||
private static final Comparison turtleComparison = compareStacks((left, right)
|
||||
-> TurtleItem.getUpgrade(left, TurtleSide.LEFT) == TurtleItem.getUpgrade(right, TurtleSide.LEFT)
|
||||
&& TurtleItem.getUpgrade(left, TurtleSide.RIGHT) == TurtleItem.getUpgrade(right, TurtleSide.RIGHT));
|
||||
|
||||
private static final Comparison pocketComparison = compareStacks((left, right) -> PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||
|
||||
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||
return Comparison.of((left, right) -> {
|
||||
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
|
||||
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ItemOwner;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -35,7 +35,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
@Override
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int light) {
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable ItemOwner holder, int light) {
|
||||
var overlay = TurtleItem.getOverlay(stack);
|
||||
if (overlay == null) return;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ItemOwner;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -36,7 +36,7 @@ public record TurtleUpgradeModel(TurtleSide side, ItemTransforms base) implement
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
@Override
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int seed) {
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable ItemOwner holder, int seed) {
|
||||
var upgrade = TurtleItem.getUpgradeWithData(stack, side);
|
||||
if (upgrade == null) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.model.Model;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.LayerDefinition;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Unit;
|
||||
|
||||
import static dan200.computercraft.client.model.LecternPrintoutModel.TEXTURE_HEIGHT;
|
||||
import static dan200.computercraft.client.model.LecternPrintoutModel.TEXTURE_WIDTH;
|
||||
|
||||
/**
|
||||
* A model for {@linkplain PrintoutItem printed books} placed on a lectern.
|
||||
*
|
||||
* @see CustomLecternRenderer
|
||||
*/
|
||||
public final class LecternBookModel extends Model<Unit> {
|
||||
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_book"), "main");
|
||||
|
||||
public LecternBookModel(ModelPart root) {
|
||||
super(root, RenderType::entitySolid);
|
||||
}
|
||||
|
||||
public static LayerDefinition createLayer() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"spine",
|
||||
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
var angle = (float) Math.toRadians(5);
|
||||
parts.addOrReplaceChild(
|
||||
"left",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"right",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
|
||||
);
|
||||
|
||||
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,22 @@ import dan200.computercraft.client.pocket.PocketComputerData;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.client.model.Model;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.LayerDefinition;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.resources.model.MaterialSet;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
|
||||
/**
|
||||
@@ -27,18 +33,14 @@ import net.minecraft.world.item.component.DyedItemColor;
|
||||
*
|
||||
* @see CustomLecternRenderer
|
||||
*/
|
||||
public class LecternPocketModel {
|
||||
public static final ResourceLocation TEXTURE_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_normal");
|
||||
public static final ResourceLocation TEXTURE_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_advanced");
|
||||
public static final ResourceLocation TEXTURE_COLOUR = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_colour");
|
||||
public static final ResourceLocation TEXTURE_FRAME = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_frame");
|
||||
public static final ResourceLocation TEXTURE_LIGHT = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_light");
|
||||
public class LecternPocketModel extends Model<Unit> {
|
||||
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_pocket"), "main");
|
||||
|
||||
private static final Material MATERIAL_NORMAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_NORMAL);
|
||||
private static final Material MATERIAL_ADVANCED = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_ADVANCED);
|
||||
private static final Material MATERIAL_COLOUR = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_COLOUR);
|
||||
private static final Material MATERIAL_FRAME = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_FRAME);
|
||||
private static final Material MATERIAL_LIGHT = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_LIGHT);
|
||||
public static final Material MATERIAL_NORMAL = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_normal"));
|
||||
public static final Material MATERIAL_ADVANCED = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced"));
|
||||
public static final Material MATERIAL_COLOUR = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_colour"));
|
||||
public static final Material MATERIAL_FRAME = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_frame"));
|
||||
public static final Material MATERIAL_LIGHT = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_light"));
|
||||
|
||||
// The size of the terminal within the model.
|
||||
public static final float TERM_WIDTH = 12.0f / 32.0f;
|
||||
@@ -48,13 +50,11 @@ public class LecternPocketModel {
|
||||
private static final int TEXTURE_WIDTH = 48 / 2;
|
||||
private static final int TEXTURE_HEIGHT = 48 / 2;
|
||||
|
||||
private final ModelPart root;
|
||||
|
||||
public LecternPocketModel() {
|
||||
root = buildPages();
|
||||
public LecternPocketModel(ModelPart root) {
|
||||
super(root, RenderType::entityCutout);
|
||||
}
|
||||
|
||||
private static ModelPart buildPages() {
|
||||
public static LayerDefinition createLayer() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
parts.addOrReplaceChild(
|
||||
@@ -62,29 +62,43 @@ public class LecternPocketModel {
|
||||
CubeListBuilder.create().texOffs(0, 0).addBox(0f, -5.0f, -4.0f, 1f, 10.0f, 8.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the pocket computer model.
|
||||
*
|
||||
* @param poseStack The current pose stack.
|
||||
* @param bufferSource The buffer source to draw to.
|
||||
* @param packedLight The current light level.
|
||||
* @param packedOverlay The overlay texture (used for entity hurt animation).
|
||||
* @param family The computer family.
|
||||
* @param frameColour The pocket computer's {@linkplain DyedItemColor colour}.
|
||||
* @param lightColour The pocket computer's {@linkplain PocketComputerData#getLightState() light colour}.
|
||||
* @param poseStack The current pose stack.
|
||||
* @param collector The collector to draw to.
|
||||
* @param materials The current materials
|
||||
* @param packedLight The current light level.
|
||||
* @param family The computer family.
|
||||
* @param frameColour The pocket computer's {@linkplain DyedItemColor colour}.
|
||||
* @param lightColour The pocket computer's {@linkplain PocketComputerData#getLightState() light colour}.
|
||||
*/
|
||||
public void render(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, ComputerFamily family, int frameColour, int lightColour) {
|
||||
public void submit(
|
||||
PoseStack poseStack, SubmitNodeCollector collector, MaterialSet materials, int packedLight, ComputerFamily family, int frameColour, int lightColour
|
||||
) {
|
||||
if (frameColour != -1) {
|
||||
root.render(poseStack, MATERIAL_FRAME.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay);
|
||||
root.render(poseStack, MATERIAL_COLOUR.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, frameColour);
|
||||
collector.submitModel(
|
||||
this, Unit.INSTANCE, poseStack, MATERIAL_FRAME.renderType(RenderType::entityCutout),
|
||||
packedLight, OverlayTexture.NO_OVERLAY, -1, materials.get(MATERIAL_FRAME), 0, null
|
||||
);
|
||||
collector.submitModel(
|
||||
this, Unit.INSTANCE, poseStack, MATERIAL_COLOUR.renderType(RenderType::entityCutout),
|
||||
packedLight, OverlayTexture.NO_OVERLAY, frameColour, materials.get(MATERIAL_COLOUR), 0, null
|
||||
);
|
||||
} else {
|
||||
var buffer = (family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL).buffer(bufferSource, RenderType::entityCutout);
|
||||
root.render(poseStack, buffer, packedLight, packedOverlay);
|
||||
var material = family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL;
|
||||
collector.submitModel(
|
||||
this, Unit.INSTANCE, poseStack, material.renderType(RenderType::entityCutout),
|
||||
packedLight, OverlayTexture.NO_OVERLAY, -1, materials.get(material), 0, null
|
||||
);
|
||||
}
|
||||
|
||||
root.render(poseStack, MATERIAL_LIGHT.buffer(bufferSource, RenderType::entityCutout), LightTexture.FULL_BRIGHT, packedOverlay, lightColour);
|
||||
collector.submitModel(
|
||||
this, Unit.INSTANCE, poseStack, MATERIAL_LIGHT.renderType(RenderType::entityCutout),
|
||||
LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, lightColour, materials.get(MATERIAL_LIGHT), 0, null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,52 +4,50 @@
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.model.Model;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.LayerDefinition;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
|
||||
* <p>
|
||||
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
|
||||
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
|
||||
* A model for {@linkplain PrintoutItem printouts} placed on a lectern. This renders a variable number of pages (1-3),
|
||||
* stored in {@link State#pages}.
|
||||
*
|
||||
* @see CustomLecternRenderer
|
||||
*/
|
||||
public class LecternPrintoutModel {
|
||||
public static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/printout");
|
||||
public static final Material MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE);
|
||||
public final class LecternPrintoutModel extends Model<LecternPrintoutModel.State> {
|
||||
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_printout"), "main");
|
||||
|
||||
private static final int TEXTURE_WIDTH = 32;
|
||||
private static final int TEXTURE_HEIGHT = 32;
|
||||
public static final Material MATERIAL = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "printout"));
|
||||
|
||||
static final int TEXTURE_WIDTH = 32;
|
||||
static final int TEXTURE_HEIGHT = 32;
|
||||
|
||||
private static final String PAGE_1 = "page_1";
|
||||
private static final String PAGE_2 = "page_2";
|
||||
private static final String PAGE_3 = "page_3";
|
||||
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
|
||||
|
||||
private final ModelPart pagesRoot;
|
||||
private final ModelPart bookRoot;
|
||||
private final ModelPart[] pages;
|
||||
|
||||
public LecternPrintoutModel() {
|
||||
pagesRoot = buildPages();
|
||||
bookRoot = buildBook();
|
||||
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
|
||||
public LecternPrintoutModel(ModelPart root) {
|
||||
super(root, RenderType::entitySolid);
|
||||
pages = PAGES.stream().map(root::getChild).toArray(ModelPart[]::new);
|
||||
}
|
||||
|
||||
private static ModelPart buildPages() {
|
||||
public static LayerDefinition createLayer() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
parts.addOrReplaceChild(
|
||||
@@ -69,49 +67,20 @@ public class LecternPrintoutModel {
|
||||
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
private static ModelPart buildBook() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"spine",
|
||||
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
var angle = (float) Math.toRadians(5);
|
||||
parts.addOrReplaceChild(
|
||||
"left",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"right",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
public void renderBook(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay) {
|
||||
bookRoot.render(poseStack, buffer, packedLight, packedOverlay);
|
||||
}
|
||||
|
||||
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
|
||||
@Override
|
||||
public void setupAnim(State renderState) {
|
||||
var pageCount = renderState.pages;
|
||||
if (pageCount > pages.length) pageCount = pages.length;
|
||||
|
||||
var i = 0;
|
||||
for (; i < pageCount; i++) pages[i].visible = true;
|
||||
for (; i < pages.length; i++) pages[i].visible = false;
|
||||
}
|
||||
|
||||
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay);
|
||||
public static class State {
|
||||
public int pages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.state.LevelRenderState;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.CommonColors;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
@@ -19,7 +20,7 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
/**
|
||||
* Utilities for rendering block outline.
|
||||
*
|
||||
* @see ClientHooks#drawHighlight(PoseStack, MultiBufferSource, Camera, BlockHitResult)
|
||||
* @see ClientHooks#drawHighlight(Camera, BlockHitResult)
|
||||
*/
|
||||
public final class BlockOutlineRenderer {
|
||||
private BlockOutlineRenderer() {
|
||||
@@ -28,20 +29,23 @@ public final class BlockOutlineRenderer {
|
||||
/**
|
||||
* Render a block outline, handling both normal and high-contrast modes.
|
||||
*
|
||||
* @param transform The current transformations.
|
||||
* @param bufferSource The buffer source.
|
||||
* @param renderer The function to render a highlight.
|
||||
* @see LevelRenderer#renderBlockOutline(Camera, MultiBufferSource.BufferSource, PoseStack, boolean)
|
||||
* @see LevelRenderer#renderBlockOutline(MultiBufferSource.BufferSource, PoseStack, boolean, LevelRenderState)
|
||||
*/
|
||||
public static void render(MultiBufferSource bufferSource, Renderer renderer) {
|
||||
public static void render(PoseStack transform, MultiBufferSource bufferSource, Renderer renderer) {
|
||||
var highContrast = Minecraft.getInstance().options.highContrastBlockOutline().get();
|
||||
if (highContrast) renderer.render(bufferSource.getBuffer(RenderType.secondaryBlockOutline()), 0xff000000);
|
||||
if (highContrast) {
|
||||
renderer.render(transform, bufferSource.getBuffer(RenderType.secondaryBlockOutline()), 0xff000000);
|
||||
}
|
||||
|
||||
var colour = highContrast ? CommonColors.HIGH_CONTRAST_DIAMOND : ARGB.color(0x66, CommonColors.BLACK);
|
||||
renderer.render(bufferSource.getBuffer(RenderType.lines()), colour);
|
||||
renderer.render(transform, bufferSource.getBuffer(RenderType.lines()), colour);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Renderer {
|
||||
void render(VertexConsumer buffer, int colour);
|
||||
void render(PoseStack transform, VertexConsumer buffer, int colour);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.ShapeRenderer;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public final class CableHighlightRenderer {
|
||||
private CableHighlightRenderer() {
|
||||
@@ -21,21 +20,19 @@ public final class CableHighlightRenderer {
|
||||
/**
|
||||
* Draw an outline for a specific part of a cable "Multipart".
|
||||
*
|
||||
* @param transform The current transformation matrix.
|
||||
* @param bufferSource The buffer to draw to.
|
||||
* @param camera The current camera.
|
||||
* @param hit The block hit result for the current player.
|
||||
* @return If we rendered a custom outline.
|
||||
* @param camera The current camera.
|
||||
* @param hit The block hit result for the current player.
|
||||
* @return The custom renderer.
|
||||
*/
|
||||
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||
public static BlockOutlineRenderer.@Nullable Renderer drawHighlight(Camera camera, BlockHitResult hit) {
|
||||
var pos = hit.getBlockPos();
|
||||
var world = camera.getEntity().level();
|
||||
var level = camera.getEntity().level();
|
||||
|
||||
var state = world.getBlockState(pos);
|
||||
var state = level.getBlockState(pos);
|
||||
|
||||
// We only care about instances with both cable and modem.
|
||||
if (state.getBlock() != ModRegistry.Blocks.CABLE.get() || state.getValue(CableBlock.MODEM).getFacing() == null || !state.getValue(CableBlock.CABLE)) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
var shape = WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ()))
|
||||
@@ -47,10 +44,6 @@ public final class CableHighlightRenderer {
|
||||
var yOffset = pos.getY() - cameraPos.y();
|
||||
var zOffset = pos.getZ() - cameraPos.z();
|
||||
|
||||
BlockOutlineRenderer.render(
|
||||
bufferSource, (buffer, colour) -> ShapeRenderer.renderShape(transform, buffer, shape, xOffset, yOffset, zOffset, colour)
|
||||
);
|
||||
|
||||
return true;
|
||||
return (transform, buffer, colour) -> ShapeRenderer.renderShape(transform, buffer, shape, xOffset, yOffset, zOffset, colour);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.client.model.LecternBookModel;
|
||||
import dan200.computercraft.client.model.LecternPocketModel;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
@@ -13,19 +14,27 @@ import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
|
||||
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
|
||||
import net.minecraft.client.renderer.state.CameraRenderState;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.resources.model.MaterialSet;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
@@ -36,60 +45,89 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON
|
||||
* <p>
|
||||
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
|
||||
*/
|
||||
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
|
||||
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity, CustomLecternRenderer.State> {
|
||||
private static final int POCKET_TERMINAL_RENDER_DISTANCE = 32;
|
||||
|
||||
private final MaterialSet materials;
|
||||
private final LecternPrintoutModel printoutModel;
|
||||
private final LecternBookModel bookModel;
|
||||
private final LecternPocketModel pocketModel;
|
||||
|
||||
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
|
||||
printoutModel = new LecternPrintoutModel();
|
||||
pocketModel = new LecternPocketModel();
|
||||
materials = context.materials();
|
||||
bookModel = new LecternBookModel(context.bakeLayer(LecternBookModel.LAYER));
|
||||
printoutModel = new LecternPrintoutModel(context.bakeLayer(LecternPrintoutModel.LAYER));
|
||||
pocketModel = new LecternPocketModel(context.bakeLayer(LecternPocketModel.LAYER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay, Vec3 camera) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5f, 1.0625f, 0.5f);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot()));
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
|
||||
poseStack.translate(0, -0.125f, 0);
|
||||
public State createRenderState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractRenderState(CustomLecternBlockEntity lectern, State state, float f, Vec3 camera, ModelFeatureRenderer.@Nullable CrumblingOverlay overlay) {
|
||||
BlockEntityRenderer.super.extractRenderState(lectern, state, f, camera, overlay);
|
||||
|
||||
var item = lectern.getItem();
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
|
||||
if (item.is(ModRegistry.Items.PRINTED_BOOK.get())) {
|
||||
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
|
||||
} else {
|
||||
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());
|
||||
}
|
||||
state.setPrintout(item.is(ModRegistry.Items.PRINTED_BOOK.get()), PrintoutData.getOrEmpty(item).pages());
|
||||
} else if (item.getItem() instanceof PocketComputerItem pocket) {
|
||||
var computer = ClientPocketComputers.get(item);
|
||||
|
||||
pocketModel.render(
|
||||
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
|
||||
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
|
||||
state.setPocket(
|
||||
pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
|
||||
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState()),
|
||||
// Only render a terminal if we're close to it.
|
||||
computer == null || !Vec3.atCenterOf(lectern.getBlockPos()).closerThan(camera, POCKET_TERMINAL_RENDER_DISTANCE)
|
||||
? null : computer.getTerminal()
|
||||
);
|
||||
} else {
|
||||
state.setUnknown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(State state, PoseStack poseStack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5f, 1.0625f, 0.5f);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-state.blockState.getValue(LecternBlock.FACING).getClockWise().toYRot()));
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
|
||||
poseStack.translate(0, -0.125f, 0);
|
||||
|
||||
if (state.type == Type.PRINTOUT) {
|
||||
if (state.isBook) {
|
||||
collector.submitModel(
|
||||
bookModel, Unit.INSTANCE, poseStack, LecternPrintoutModel.MATERIAL.renderType(RenderType::entitySolid),
|
||||
state.lightCoords, OverlayTexture.NO_OVERLAY, -1,
|
||||
materials.get(LecternPrintoutModel.MATERIAL), 0, null
|
||||
);
|
||||
} else {
|
||||
collector.submitModel(
|
||||
printoutModel, state.printoutState, poseStack, LecternPrintoutModel.MATERIAL.renderType(RenderType::entitySolid),
|
||||
state.lightCoords, OverlayTexture.NO_OVERLAY, -1,
|
||||
materials.get(LecternPrintoutModel.MATERIAL), 0, null
|
||||
);
|
||||
}
|
||||
} else if (state.type == Type.POCKET_COMPUTER) {
|
||||
pocketModel.submit(poseStack, collector, materials, state.lightCoords, state.pocketFamily, state.pocketColour, state.pocketLight);
|
||||
|
||||
// Jiggle the terminal about a bit, so (0, 0) is in the top left of the model's terminal hole.
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(90f));
|
||||
poseStack.translate(-0.5 * LecternPocketModel.TERM_WIDTH, 0.5 * LecternPocketModel.TERM_HEIGHT + 1f / 32.0f, 1 / 16.0f);
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(180));
|
||||
|
||||
// Either render the terminal or a black screen, depending on how close we are.
|
||||
var terminal = computer == null ? null : computer.getTerminal();
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
|
||||
if (terminal != null && Vec3.atCenterOf(lectern.getBlockPos()).closerThan(camera, POCKET_TERMINAL_RENDER_DISTANCE)) {
|
||||
renderPocketTerminal(poseStack, quadEmitter, terminal);
|
||||
// Either render the terminal or a black screen.
|
||||
if (state.pocketTerminal != null) {
|
||||
renderPocketTerminal(poseStack, collector, state.pocketTerminal);
|
||||
} else {
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(poseStack, collector, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
private static void renderPocketTerminal(PoseStack poseStack, FixedWidthFontRenderer.QuadEmitter quadEmitter, Terminal terminal) {
|
||||
private static void renderPocketTerminal(PoseStack poseStack, SubmitNodeCollector collector, Terminal terminal) {
|
||||
var width = terminal.getWidth() * FONT_WIDTH;
|
||||
var height = terminal.getHeight() * FONT_HEIGHT;
|
||||
|
||||
@@ -103,6 +141,48 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
|
||||
var marginX = ((LecternPocketModel.TERM_WIDTH / scale) - width) / 2;
|
||||
var marginY = ((LecternPocketModel.TERM_HEIGHT / scale) - height) / 2;
|
||||
|
||||
FixedWidthFontRenderer.drawTerminal(quadEmitter, marginX, marginY, terminal, marginY, marginY, marginX, marginX);
|
||||
collector.submitCustomGeometry(poseStack, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) ->
|
||||
FixedWidthFontRenderer.drawTerminal(pose.pose(), buffer, marginX, marginY, terminal, marginY, marginY, marginX, marginX));
|
||||
}
|
||||
|
||||
private enum Type {
|
||||
PRINTOUT,
|
||||
POCKET_COMPUTER,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
public static final class State extends BlockEntityRenderState {
|
||||
private Type type = Type.PRINTOUT;
|
||||
private boolean isBook;
|
||||
private final LecternPrintoutModel.State printoutState = new LecternPrintoutModel.State();
|
||||
|
||||
private ComputerFamily pocketFamily = ComputerFamily.NORMAL;
|
||||
private int pocketColour;
|
||||
private int pocketLight;
|
||||
private @Nullable Terminal pocketTerminal; // TODO: Make this immutable
|
||||
|
||||
private State() {
|
||||
}
|
||||
|
||||
private void setUnknown() {
|
||||
this.type = Type.UNKNOWN;
|
||||
this.pocketTerminal = null;
|
||||
}
|
||||
|
||||
private void setPrintout(boolean isBook, int pages) {
|
||||
this.type = Type.PRINTOUT;
|
||||
this.isBook = isBook;
|
||||
this.printoutState.pages = pages;
|
||||
|
||||
this.pocketTerminal = null;
|
||||
}
|
||||
|
||||
private void setPocket(ComputerFamily family, int colour, int light, @Nullable Terminal terminal) {
|
||||
this.type = Type.POCKET_COMPUTER;
|
||||
this.pocketFamily = family;
|
||||
this.pocketColour = colour;
|
||||
this.pocketLight = light;
|
||||
this.pocketTerminal = terminal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ItemInHandRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
@@ -22,29 +22,29 @@ import java.util.Objects;
|
||||
/**
|
||||
* A base class for items which have map-like rendering when held in the hand.
|
||||
*
|
||||
* @see dan200.computercraft.client.ClientHooks#onRenderHeldItem(PoseStack, MultiBufferSource, int, InteractionHand, float, float, float, ItemStack)
|
||||
* @see dan200.computercraft.client.ClientHooks#onRenderHeldItem(PoseStack, SubmitNodeCollector, int, InteractionHand, float, float, float, ItemStack)
|
||||
*/
|
||||
public abstract class ItemMapLikeRenderer {
|
||||
/**
|
||||
* The main rendering method for the item.
|
||||
*
|
||||
* @param transform The matrix transformation stack
|
||||
* @param render The buffer to render to
|
||||
* @param collector The buffer to render to
|
||||
* @param stack The stack to render
|
||||
* @param light The packed lightmap coordinates.
|
||||
* @see ItemInHandRenderer#renderItem(LivingEntity, ItemStack, ItemDisplayContext, boolean, PoseStack, MultiBufferSource, int)
|
||||
* @see ItemInHandRenderer#renderItem(LivingEntity, ItemStack, ItemDisplayContext, PoseStack, SubmitNodeCollector, int)
|
||||
*/
|
||||
protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light);
|
||||
protected abstract void renderItem(PoseStack transform, SubmitNodeCollector collector, ItemStack stack, int light);
|
||||
|
||||
public void renderItemFirstPerson(PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
public void renderItemFirstPerson(PoseStack transform, SubmitNodeCollector collector, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
Player player = Objects.requireNonNull(Minecraft.getInstance().player);
|
||||
|
||||
transform.pushPose();
|
||||
if (hand == InteractionHand.MAIN_HAND && player.getOffhandItem().isEmpty()) {
|
||||
renderItemFirstPersonCenter(transform, render, lightTexture, pitch, equipProgress, swingProgress, stack);
|
||||
renderItemFirstPersonCenter(transform, collector, lightTexture, pitch, equipProgress, swingProgress, stack);
|
||||
} else {
|
||||
renderItemFirstPersonSide(
|
||||
transform, render, lightTexture,
|
||||
transform, collector, lightTexture,
|
||||
hand == InteractionHand.MAIN_HAND ? player.getMainArm() : player.getMainArm().getOpposite(),
|
||||
equipProgress, swingProgress, stack
|
||||
);
|
||||
@@ -56,15 +56,15 @@ public abstract class ItemMapLikeRenderer {
|
||||
* Renders the item to one side of the player.
|
||||
*
|
||||
* @param transform The matrix transformation stack
|
||||
* @param render The buffer to render to
|
||||
* @param collector The buffer to render to
|
||||
* @param combinedLight The current light level
|
||||
* @param side The side to render on
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemInHandRenderer#renderOneHandedMap(PoseStack, MultiBufferSource, int, float, HumanoidArm, float, ItemStack)
|
||||
* @see ItemInHandRenderer#renderOneHandedMap(PoseStack, SubmitNodeCollector, int, float, HumanoidArm, float, ItemStack)
|
||||
*/
|
||||
private void renderItemFirstPersonSide(PoseStack transform, MultiBufferSource render, int combinedLight, HumanoidArm side, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
private void renderItemFirstPersonSide(PoseStack transform, SubmitNodeCollector collector, int combinedLight, HumanoidArm side, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var offset = side == HumanoidArm.RIGHT ? 1f : -1f;
|
||||
transform.translate(offset * 0.125f, -0.125f, 0f);
|
||||
@@ -73,7 +73,7 @@ public abstract class ItemMapLikeRenderer {
|
||||
if (!minecraft.player.isInvisible()) {
|
||||
transform.pushPose();
|
||||
transform.mulPose(Axis.ZP.rotationDegrees(offset * 10f));
|
||||
minecraft.getEntityRenderDispatcher().getItemInHandRenderer().renderPlayerArm(transform, render, combinedLight, equipProgress, swingProgress, side);
|
||||
minecraft.getEntityRenderDispatcher().getItemInHandRenderer().renderPlayerArm(transform, collector, combinedLight, equipProgress, swingProgress, side);
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public abstract class ItemMapLikeRenderer {
|
||||
transform.mulPose(Axis.XP.rotationDegrees(f2 * -45f));
|
||||
transform.mulPose(Axis.YP.rotationDegrees(offset * f2 * -30f));
|
||||
|
||||
renderItem(transform, render, stack, combinedLight);
|
||||
renderItem(transform, collector, stack, combinedLight);
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
@@ -99,15 +99,15 @@ public abstract class ItemMapLikeRenderer {
|
||||
* Render an item in the middle of the screen.
|
||||
*
|
||||
* @param transform The matrix transformation stack
|
||||
* @param render The buffer to render to
|
||||
* @param collector The buffer to render to
|
||||
* @param combinedLight The current light level
|
||||
* @param pitch The pitch of the player
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemInHandRenderer#renderTwoHandedMap(PoseStack, MultiBufferSource, int, float, float, float)
|
||||
* @see ItemInHandRenderer#renderTwoHandedMap(PoseStack, SubmitNodeCollector, int, float, float, float)
|
||||
*/
|
||||
private void renderItemFirstPersonCenter(PoseStack transform, MultiBufferSource render, int combinedLight, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
private void renderItemFirstPersonCenter(PoseStack transform, SubmitNodeCollector collector, int combinedLight, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var renderer = minecraft.getEntityRenderDispatcher().getItemInHandRenderer();
|
||||
|
||||
@@ -124,8 +124,8 @@ public abstract class ItemMapLikeRenderer {
|
||||
if (!minecraft.player.isInvisible()) {
|
||||
transform.pushPose();
|
||||
transform.mulPose(Axis.YP.rotationDegrees(90.0F));
|
||||
renderer.renderMapHand(transform, render, combinedLight, HumanoidArm.RIGHT);
|
||||
renderer.renderMapHand(transform, render, combinedLight, HumanoidArm.LEFT);
|
||||
renderer.renderMapHand(transform, collector, combinedLight, HumanoidArm.RIGHT);
|
||||
renderer.renderMapHand(transform, collector, combinedLight, HumanoidArm.LEFT);
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
@@ -133,6 +133,6 @@ public abstract class ItemMapLikeRenderer {
|
||||
transform.mulPose(Axis.XP.rotationDegrees(rX * 20.0F));
|
||||
transform.scale(2.0F, 2.0F, 2.0F);
|
||||
|
||||
renderItem(transform, render, stack, combinedLight);
|
||||
renderItem(transform, collector, stack, combinedLight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.metadata.gui.GuiMetadataSection;
|
||||
import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling;
|
||||
import net.minecraft.data.AtlasIds;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
|
||||
@@ -42,7 +44,7 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
|
||||
protected void renderItem(PoseStack transform, SubmitNodeCollector collector, ItemStack stack, int light) {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
var terminal = computer == null ? null : computer.getTerminal();
|
||||
|
||||
@@ -74,42 +76,41 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
var family = item.getFamily();
|
||||
var frameColour = DyedItemColor.getOrDefault(stack, -1);
|
||||
|
||||
var matrix = transform.last().pose();
|
||||
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
|
||||
renderFrame(transform, collector, family, frameColour, light, width, height);
|
||||
|
||||
// Render the light
|
||||
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||
renderLight(transform, bufferSource, lightColour, width, height);
|
||||
renderLight(transform, collector, lightColour, width, height);
|
||||
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
|
||||
if (terminal == null) {
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(transform, collector, 0, 0, width, height);
|
||||
} else {
|
||||
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
|
||||
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) ->
|
||||
FixedWidthFontRenderer.drawTerminal(pose.pose(), buffer, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN));
|
||||
}
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
private static void renderFrame(Matrix4f transform, MultiBufferSource render, ComputerFamily family, int colour, int light, int width, int height) {
|
||||
private static void renderFrame(PoseStack transform, SubmitNodeCollector submit, ComputerFamily family, int colour, int light, int width, int height) {
|
||||
var textures = colour != -1 ? GuiSprites.COMPUTER_COLOUR : GuiSprites.getComputerTextures(family);
|
||||
var spriteRenderer = new SpriteRenderer(transform, render, 0, light, colour);
|
||||
var spriteRenderer = new SpriteRenderer(transform, submit, 0, light, colour);
|
||||
renderBorder(spriteRenderer, textures, width, height);
|
||||
}
|
||||
|
||||
private static void renderBorder(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int width, int height) {
|
||||
var sprites = Minecraft.getInstance().getGuiSprites();
|
||||
var sprites = Minecraft.getInstance().getAtlasManager().getAtlasOrThrow(AtlasIds.GUI);
|
||||
|
||||
// Find our border, forcing it to be a nine-sliced texture.
|
||||
var borderSprite = sprites.getSprite(textures.border());
|
||||
var borderSlice = getSlice(sprites.getSpriteScaling(borderSprite), DEFAULT_BORDER);
|
||||
var borderSlice = getSlice(borderSprite, DEFAULT_BORDER);
|
||||
var borderBounds = borderSlice.border();
|
||||
|
||||
// And take the separate bottom bit of the pocket computer.
|
||||
var bottomTexture = textures.pocketBottom();
|
||||
if (bottomTexture == null) throw new NullPointerException(textures + " has no pocket texture");
|
||||
var bottomSprite = sprites.getSprite(bottomTexture);
|
||||
var bottomSlice = getSlice(sprites.getSpriteScaling(bottomSprite), DEFAULT_BOTTOM);
|
||||
var bottomSlice = getSlice(bottomSprite, DEFAULT_BOTTOM);
|
||||
var bottomBounds = bottomSlice.border();
|
||||
|
||||
// Now draw a nine-sliced texture, by stitching together the top parts of the border with the pocket bottom.
|
||||
@@ -157,13 +158,12 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
);
|
||||
}
|
||||
|
||||
private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
|
||||
var buffer = render.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
FixedWidthFontRenderer.drawQuad(
|
||||
FixedWidthFontRenderer.toVertexConsumer(transform, buffer),
|
||||
private static void renderLight(PoseStack transform, SubmitNodeCollector render, int colour, int width, int height) {
|
||||
render.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> FixedWidthFontRenderer.drawQuad(
|
||||
pose.pose(), buffer,
|
||||
width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT,
|
||||
ARGB.opaque(colour), LightTexture.FULL_BRIGHT
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
private static final GuiSpriteScaling.NineSlice DEFAULT_BORDER = new GuiSpriteScaling.NineSlice(
|
||||
@@ -177,4 +177,8 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
private static GuiSpriteScaling.NineSlice getSlice(GuiSpriteScaling scaling, GuiSpriteScaling.NineSlice fallback) {
|
||||
return scaling instanceof GuiSpriteScaling.NineSlice slice ? slice : fallback;
|
||||
}
|
||||
|
||||
private static GuiSpriteScaling.NineSlice getSlice(TextureAtlasSprite sprite, GuiSpriteScaling.NineSlice fallback) {
|
||||
return getSlice(sprite.contents().getAdditionalMetadata(GuiMetadataSection.TYPE).orElse(GuiMetadataSection.DEFAULT).scaling(), fallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -28,26 +29,26 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {
|
||||
protected void renderItem(PoseStack transform, SubmitNodeCollector collector, ItemStack stack, int light) {
|
||||
transform.mulPose(Axis.XP.rotationDegrees(180f));
|
||||
transform.scale(0.42f, 0.42f, -0.42f);
|
||||
transform.translate(-0.5f, -0.48f, 0.0f);
|
||||
|
||||
drawPrintout(transform, render, PrintoutData.getOrEmpty(stack), stack.getItem() == ModRegistry.Items.PRINTED_BOOK.get(), light);
|
||||
drawPrintout(transform, collector, PrintoutData.getOrEmpty(stack), stack.getItem() == ModRegistry.Items.PRINTED_BOOK.get(), light);
|
||||
}
|
||||
|
||||
public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrameRenderState frame, PrintoutData data, boolean isBook, int packedLight) {
|
||||
public static void onRenderInFrame(PoseStack transform, SubmitNodeCollector collector, ItemFrameRenderState frame, PrintoutData data, boolean isBook) {
|
||||
// Move a little bit forward to ensure we're not clipping with the frame
|
||||
transform.translate(0.0f, 0.0f, -0.001f);
|
||||
transform.mulPose(Axis.ZP.rotationDegrees(180f));
|
||||
transform.scale(0.95f, 0.95f, -0.95f);
|
||||
transform.translate(-0.5f, -0.5f, 0.0f);
|
||||
|
||||
var light = frame.isGlowFrame ? 0xf000d2 : packedLight; // See getLightCoords.
|
||||
drawPrintout(transform, render, data, isBook, light);
|
||||
var light = frame.isGlowFrame ? 0xf000d2 : frame.lightCoords; // See getLightCoords.
|
||||
drawPrintout(transform, collector, data, isBook, light);
|
||||
}
|
||||
|
||||
private static void drawPrintout(PoseStack transform, MultiBufferSource render, PrintoutData pageData, boolean book, int light) {
|
||||
private static void drawPrintout(PoseStack transform, SubmitNodeCollector collector, PrintoutData pageData, boolean book, int light) {
|
||||
var pages = pageData.pages();
|
||||
|
||||
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
|
||||
@@ -71,7 +72,7 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
|
||||
transform.scale(scale, scale, scale);
|
||||
transform.translate((max - width) / 2.0, (max - height) / 2.0, 0.0);
|
||||
|
||||
drawBorder(transform, render, 0, 0, -0.01f, 0, pages, book, light);
|
||||
drawText(transform, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, pageData.lines());
|
||||
collector.submitCustomGeometry(transform, BACKGROUND, (matrix, buffer) -> drawBorder(matrix.pose(), buffer, 0, 0, -0.01f, 0, pages, book, light));
|
||||
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (matrix, buffer) -> drawText(matrix.pose(), buffer, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, pageData.lines()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class PrintoutRenderer {
|
||||
* Printout's background texture. {@link RenderType#text(ResourceLocation)} is a <em>little</em> questionable, but
|
||||
* it is what maps use, so should behave the same as vanilla in both item frames and in-hand.
|
||||
*/
|
||||
private static final RenderType BACKGROUND = RenderType.text(ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printout.png"));
|
||||
public static final RenderType BACKGROUND = RenderType.text(ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printout.png"));
|
||||
|
||||
private static final float BG_SIZE = 256.0f;
|
||||
|
||||
@@ -74,21 +74,20 @@ public final class PrintoutRenderer {
|
||||
|
||||
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours) {
|
||||
var buffer = bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
|
||||
FixedWidthFontRenderer.drawString(emitter,
|
||||
FixedWidthFontRenderer.drawString(
|
||||
transform.last().pose(), buffer,
|
||||
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line],
|
||||
Palette.DEFAULT, light
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, List<PrintoutData.Line> lines) {
|
||||
var buffer = bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||
public static void drawText(Matrix4f matrix4f, VertexConsumer buffer, int x, int y, int start, int light, List<PrintoutData.Line> lines) {
|
||||
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
|
||||
var lineContents = lines.get(start + line);
|
||||
FixedWidthFontRenderer.drawString(emitter,
|
||||
FixedWidthFontRenderer.drawString(
|
||||
matrix4f, buffer,
|
||||
x, y + line * FONT_HEIGHT,
|
||||
new TextBuffer(lineContents.text()), new TextBuffer(lineContents.foreground()),
|
||||
Palette.DEFAULT, light
|
||||
@@ -96,13 +95,10 @@ public final class PrintoutRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawBorder(PoseStack transform, MultiBufferSource bufferSource, float x, float y, float z, int page, int pages, boolean isBook, int light) {
|
||||
var matrix = transform.last().pose();
|
||||
public static void drawBorder(Matrix4f matrix, VertexConsumer buffer, float x, float y, float z, int page, int pages, boolean isBook, int light) {
|
||||
var leftPages = page;
|
||||
var rightPages = pages - page - 1;
|
||||
|
||||
var buffer = bufferSource.getBuffer(BACKGROUND);
|
||||
|
||||
if (isBook) {
|
||||
// Border
|
||||
var offset = offsetAt(pages);
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,17 +20,15 @@ import org.joml.Matrix4f;
|
||||
* sheet.
|
||||
*/
|
||||
public class SpriteRenderer {
|
||||
public static final ResourceLocation TEXTURE = ResourceLocation.withDefaultNamespace("textures/atlas/gui.png");
|
||||
|
||||
private final Matrix4f transform;
|
||||
private final MultiBufferSource buffers;
|
||||
private final PoseStack transform;
|
||||
private final SubmitNodeCollector submit;
|
||||
private final int light;
|
||||
private final int z;
|
||||
private final int colour;
|
||||
|
||||
public SpriteRenderer(Matrix4f transform, MultiBufferSource buffers, int z, int light, int colour) {
|
||||
public SpriteRenderer(PoseStack transform, SubmitNodeCollector submit, int z, int light, int colour) {
|
||||
this.transform = transform;
|
||||
this.buffers = buffers;
|
||||
this.submit = submit;
|
||||
this.z = z;
|
||||
this.light = light;
|
||||
this.colour = colour;
|
||||
@@ -47,11 +44,12 @@ public class SpriteRenderer {
|
||||
var v0 = sprite.getV((float) spriteY / spriteHeight);
|
||||
var v1 = sprite.getV((float) (spriteY + height) / spriteHeight);
|
||||
|
||||
var vertices = buffers.getBuffer(RenderType.text(sprite.atlasLocation()));
|
||||
vertices.addVertex(transform, x0, y1, z).setColor(colour).setUv(u0, v1).setLight(light);
|
||||
vertices.addVertex(transform, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||
vertices.addVertex(transform, x1, y0, z).setColor(colour).setUv(u1, v0).setLight(light);
|
||||
vertices.addVertex(transform, x0, y0, z).setColor(colour).setUv(u0, v0).setLight(light);
|
||||
submit.submitCustomGeometry(transform, RenderType.text(sprite.atlasLocation()), (t, vertices) -> {
|
||||
vertices.addVertex(t, x0, y1, z).setColor(colour).setUv(u0, v1).setLight(light);
|
||||
vertices.addVertex(t, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||
vertices.addVertex(t, x1, y0, z).setColor(colour).setUv(u1, v0).setLight(light);
|
||||
vertices.addVertex(t, x0, y0, z).setColor(colour).setUv(u0, v0).setLight(light);
|
||||
});
|
||||
}
|
||||
|
||||
public void blitTiled(
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.google.errorprone.annotations.concurrent.LazyInit;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
@@ -16,111 +19,147 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
|
||||
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.renderer.state.CameraRenderState;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.CommonColors;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity, TurtleBlockEntityRenderer.State> {
|
||||
public static final ResourceLocation NORMAL_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_normal");
|
||||
public static final ResourceLocation ADVANCED_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_advanced");
|
||||
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||
|
||||
private final BlockEntityRenderDispatcher renderer;
|
||||
private final Font font;
|
||||
private final ItemModelResolver itemModelResolver;
|
||||
|
||||
public TurtleBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
||||
renderer = context.getBlockEntityRenderDispatcher();
|
||||
font = context.getFont();
|
||||
itemModelResolver = context.itemModelResolver();
|
||||
}
|
||||
|
||||
public static final class State extends BlockEntityRenderState {
|
||||
private @Nullable String label;
|
||||
private Vec3 offset = Vec3.ZERO;
|
||||
private int colour;
|
||||
private float yaw;
|
||||
private @LazyInit StandaloneModel model;
|
||||
private @Nullable StandaloneModel overlay;
|
||||
private @Nullable StandaloneModel elfOverlay;
|
||||
|
||||
private float leftAngle;
|
||||
private final ItemStackRenderState leftUpgrade = new ItemStackRenderState();
|
||||
|
||||
private float rightAngle;
|
||||
private final ItemStackRenderState rightUpgrade = new ItemStackRenderState();
|
||||
|
||||
private State() {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, Vec3 camera) {
|
||||
public State createRenderState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractRenderState(TurtleBlockEntity turtle, State state, float partialTicks, Vec3 camera, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
|
||||
BlockEntityRenderer.super.extractRenderState(turtle, state, partialTicks, camera, crumblingOverlay);
|
||||
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
|
||||
var hit = Minecraft.getInstance().hitResult;
|
||||
state.label = hit != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())
|
||||
? turtle.getLabel() : null;
|
||||
state.colour = turtle.getColour();
|
||||
state.offset = turtle.getRenderOffset(partialTicks);
|
||||
state.yaw = turtle.getRenderYaw(partialTicks);
|
||||
|
||||
var modelLocation = state.colour == -1
|
||||
? (turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL)
|
||||
: COLOUR_TURTLE_MODEL;
|
||||
state.model = ClientRegistry.getModel(modelManager, modelLocation);
|
||||
|
||||
var overlay = TurtleOverlayManager.get(modelManager, turtle.getOverlay());
|
||||
state.overlay = overlay == null ? null : overlay.model();
|
||||
state.elfOverlay = Holiday.getCurrent() == Holiday.CHRISTMAS && (overlay == null || overlay.showElfOverlay())
|
||||
? ClientRegistry.getModel(modelManager, TurtleOverlay.ELF_MODEL)
|
||||
: null;
|
||||
|
||||
state.leftAngle = turtle.getToolRenderAngle(TurtleSide.LEFT, partialTicks);
|
||||
extractUpgrade(turtle.getAccess(), TurtleSide.LEFT, state.leftUpgrade);
|
||||
|
||||
state.rightAngle = turtle.getToolRenderAngle(TurtleSide.RIGHT, partialTicks);
|
||||
extractUpgrade(turtle.getAccess(), TurtleSide.RIGHT, state.rightUpgrade);
|
||||
}
|
||||
|
||||
private void extractUpgrade(ITurtleAccess turtle, TurtleSide side, ItemStackRenderState state) {
|
||||
state.clear();
|
||||
var upgrade = turtle.getUpgradeWithData(side);
|
||||
if (upgrade == null) return;
|
||||
|
||||
TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder())
|
||||
.renderForItem(upgrade, side, state, itemModelResolver, ItemTransform.NO_TRANSFORM, 31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(State state, PoseStack transform, SubmitNodeCollector collector, CameraRenderState camera) {
|
||||
transform.pushPose();
|
||||
|
||||
// Translate the turtle first, so the label moves with it.
|
||||
var offset = turtle.getRenderOffset(partialTicks);
|
||||
transform.translate(offset.x, offset.y, offset.z);
|
||||
transform.translate(state.offset);
|
||||
|
||||
// Render the label
|
||||
var label = turtle.getLabel();
|
||||
var hit = renderer.cameraHitResult;
|
||||
if (label != null && hit != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var font = this.font;
|
||||
|
||||
transform.pushPose();
|
||||
transform.translate(0.5, 1.2, 0.5);
|
||||
transform.mulPose(mc.getEntityRenderDispatcher().cameraOrientation());
|
||||
transform.scale(0.025f, -0.025f, 0.025f);
|
||||
|
||||
var matrix = transform.last().pose();
|
||||
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
|
||||
var width = -font.width(label) / 2.0f;
|
||||
font.drawInBatch(label, width, 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
|
||||
font.drawInBatch(label, width, 0, CommonColors.WHITE, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
|
||||
|
||||
transform.popPose();
|
||||
if (state.label != null) {
|
||||
collector.submitNameTag(
|
||||
transform, new Vec3(0.5, 1.2, 0.5), 0, Component.literal(state.label), false, state.lightCoords,
|
||||
camera.pos.distanceToSqr(Vec3.atCenterOf(state.blockPos)), // TODO: Should we read camera from the render state instead?
|
||||
camera
|
||||
);
|
||||
}
|
||||
|
||||
// Then apply rotation and flip if needed.
|
||||
transform.translate(0.5f, 0.5f, 0.5f);
|
||||
var yaw = turtle.getRenderYaw(partialTicks);
|
||||
transform.mulPose(Axis.YP.rotationDegrees(180.0f - yaw));
|
||||
transform.mulPose(Axis.YP.rotationDegrees(180.0f - state.yaw));
|
||||
transform.translate(-0.5f, -0.5f, -0.5f);
|
||||
|
||||
// Render the turtle
|
||||
var colour = turtle.getColour();
|
||||
var overlay = TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), turtle.getOverlay());
|
||||
state.model.submit(transform, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, state.colour == -1 ? null : new int[]{ ARGB.opaque(state.colour) }, state.breakProgress);
|
||||
|
||||
if (colour == -1) {
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null);
|
||||
} else {
|
||||
// Otherwise render it using the colour item.
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ ARGB.opaque(colour) });
|
||||
if (state.overlay != null) {
|
||||
state.overlay.submit(transform, collector, state.lightCoords, OverlayTexture.NO_OVERLAY);
|
||||
}
|
||||
if (state.elfOverlay != null) {
|
||||
state.elfOverlay.submit(transform, collector, state.lightCoords, OverlayTexture.NO_OVERLAY);
|
||||
}
|
||||
|
||||
// Render the overlay
|
||||
if (overlay != null) overlay.model().render(transform, buffers, lightmapCoord, overlayLight);
|
||||
|
||||
// And the Christmas overlay.
|
||||
var showChristmas = Holiday.getCurrent() == Holiday.CHRISTMAS && (overlay == null || overlay.showElfOverlay());
|
||||
if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null);
|
||||
|
||||
// Render the upgrades
|
||||
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
|
||||
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
|
||||
submitUpgrade(transform, collector, state.lightCoords, state.leftAngle, state.leftUpgrade);
|
||||
submitUpgrade(transform, collector, state.lightCoords, state.rightAngle, state.rightUpgrade);
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
||||
var upgrade = turtle.getAccess().getUpgradeWithData(side);
|
||||
if (upgrade == null) return;
|
||||
private void submitUpgrade(PoseStack transform, SubmitNodeCollector collector, int lightmapCoord, float angle, ItemStackRenderState state) {
|
||||
if (state.isEmpty()) return;
|
||||
transform.pushPose();
|
||||
|
||||
var toolAngle = turtle.getToolRenderAngle(side, f);
|
||||
// Swing the tool
|
||||
transform.translate(0.0f, 0.5f, 0.5f);
|
||||
transform.mulPose(Axis.XN.rotationDegrees(toolAngle));
|
||||
transform.mulPose(Axis.XN.rotationDegrees(angle));
|
||||
transform.translate(0.0f, -0.5f, -0.5f);
|
||||
|
||||
TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder())
|
||||
.renderForLevel(upgrade, side, turtle.getAccess(), transform, buffers, lightmapCoord, overlayLight);
|
||||
// Then reposition for rendering the item
|
||||
transform.translate(0.5f, 0.5f, 0.5f);
|
||||
state.submit(transform, collector, lightmapCoord, OverlayTexture.NO_OVERLAY, 0);
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, int @Nullable [] tints) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
ClientRegistry.getModel(modelManager, modelLocation).render(transform, buffers, lightmapCoord, overlayLight, tints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,15 +20,20 @@ import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
|
||||
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
|
||||
import net.minecraft.client.renderer.fog.FogRenderer;
|
||||
import net.minecraft.client.renderer.state.CameraRenderState;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
@@ -40,7 +45,7 @@ import java.util.OptionalInt;
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
|
||||
public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBlockEntity> {
|
||||
public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBlockEntity, MonitorBlockEntityRenderer.State> {
|
||||
/**
|
||||
* {@link MonitorBlockEntity#RENDER_MARGIN}, but a tiny bit of additional padding to ensure that there is no space between
|
||||
* the monitor frame and contents.
|
||||
@@ -53,55 +58,48 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(MonitorBlockEntity monitor, float partialTicks, PoseStack transform, MultiBufferSource bufferSource, int lightmapCoord, int overlayLight, Vec3 camera) {
|
||||
// Render from the origin monitor
|
||||
var originTerminal = monitor.getOriginClientMonitor();
|
||||
if (originTerminal == null) return;
|
||||
public State createRenderState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
var origin = originTerminal.getOrigin();
|
||||
var renderState = originTerminal.getRenderState(MonitorRenderState::new);
|
||||
var monitorPos = monitor.getBlockPos();
|
||||
@Override
|
||||
public void extractRenderState(MonitorBlockEntity monitor, State state, float f, Vec3 camera, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
|
||||
BlockEntityRenderer.super.extractRenderState(monitor, state, f, camera, crumblingOverlay);
|
||||
|
||||
// Ensure each monitor terminal is rendered only once. We allow rendering a specific tile
|
||||
// multiple times in a single frame to ensure compatibility with shaders which may run a
|
||||
// pass multiple times.
|
||||
var renderFrame = FrameInfo.getRenderFrame();
|
||||
if (renderState.lastRenderFrame == renderFrame && !monitorPos.equals(renderState.lastRenderPos)) {
|
||||
return;
|
||||
}
|
||||
state.direction = monitor.getDirection();
|
||||
state.front = monitor.getFront();
|
||||
state.width = monitor.getWidth();
|
||||
state.height = monitor.getHeight();
|
||||
state.terminal = monitor.getOriginClientMonitor();
|
||||
}
|
||||
|
||||
renderState.lastRenderFrame = renderFrame;
|
||||
renderState.lastRenderPos = monitorPos;
|
||||
|
||||
var originPos = origin.getBlockPos();
|
||||
@Override
|
||||
public void submit(State state, PoseStack transform, SubmitNodeCollector collector, CameraRenderState camera) {
|
||||
if (state.terminal == null) return;
|
||||
|
||||
// Determine orientation
|
||||
var dir = origin.getDirection();
|
||||
var front = origin.getFront();
|
||||
var dir = state.direction;
|
||||
var front = state.front;
|
||||
var yaw = dir.toYRot();
|
||||
var pitch = DirectionUtil.toPitchAngle(front);
|
||||
|
||||
// Setup initial transform
|
||||
transform.pushPose();
|
||||
transform.translate(
|
||||
originPos.getX() - monitorPos.getX() + 0.5,
|
||||
originPos.getY() - monitorPos.getY() + 0.5,
|
||||
originPos.getZ() - monitorPos.getZ() + 0.5
|
||||
);
|
||||
transform.translate(0.5, 0.5, 0.5);
|
||||
|
||||
transform.mulPose(Axis.YN.rotationDegrees(yaw));
|
||||
transform.mulPose(Axis.XP.rotationDegrees(pitch));
|
||||
transform.translate(
|
||||
-0.5 + MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN,
|
||||
origin.getHeight() - 0.5 - (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN) + 0,
|
||||
state.height - 0.5 - (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN) + 0,
|
||||
0.5
|
||||
);
|
||||
var xSize = origin.getWidth() - 2.0 * (MonitorBlockEntity.RENDER_MARGIN + MonitorBlockEntity.RENDER_BORDER);
|
||||
var ySize = origin.getHeight() - 2.0 * (MonitorBlockEntity.RENDER_MARGIN + MonitorBlockEntity.RENDER_BORDER);
|
||||
var xSize = state.width - 2.0 * (MonitorBlockEntity.RENDER_MARGIN + MonitorBlockEntity.RENDER_BORDER);
|
||||
var ySize = state.height - 2.0 * (MonitorBlockEntity.RENDER_MARGIN + MonitorBlockEntity.RENDER_BORDER);
|
||||
|
||||
// Draw the contents
|
||||
var terminal = originTerminal.getTerminal();
|
||||
if (terminal != null && !ShaderMod.get().isRenderingShadowPass()) {
|
||||
var terminal = state.terminal.getTerminal();
|
||||
if (terminal != null) {
|
||||
// Draw a terminal
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
@@ -110,17 +108,20 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
transform.pushPose();
|
||||
transform.scale((float) xScale, (float) -yScale, 1.0f);
|
||||
|
||||
var matrix = transform.last().pose();
|
||||
var xMargin = (float) (MARGIN / xScale);
|
||||
var yMargin = (float) (MARGIN / yScale);
|
||||
|
||||
renderTerminal(matrix, originTerminal, renderState, terminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale));
|
||||
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
|
||||
FixedWidthFontRenderer.drawTerminalBackground(pose.pose(), buffer, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin);
|
||||
});
|
||||
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, (pose, buffer) -> {
|
||||
FixedWidthFontRenderer.drawTerminalForeground(pose.pose(), buffer, 0, 0, terminal);
|
||||
FixedWidthFontRenderer.drawCursor(pose.pose(), buffer, 0, 0, terminal);
|
||||
});
|
||||
|
||||
transform.popPose();
|
||||
} else {
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(
|
||||
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
|
||||
-MARGIN, MARGIN,
|
||||
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
|
||||
);
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(transform, collector, -MARGIN, MARGIN, (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2));
|
||||
}
|
||||
|
||||
transform.popPose();
|
||||
@@ -224,7 +225,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
var transforms = RenderSystem.getDynamicUniforms().writeTransform(
|
||||
RenderSystem.getModelViewMatrix(),
|
||||
new Vector4f(1.0F, 1.0F, 1.0F, 1.0F),
|
||||
RenderSystem.getModelOffset(),
|
||||
new Vector3f(),
|
||||
RenderSystem.getTextureMatrix(),
|
||||
RenderSystem.getShaderLineWidth()
|
||||
);
|
||||
@@ -272,6 +273,11 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
return monitor.getRenderBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRender(MonitorBlockEntity monitor, Vec3 camera) {
|
||||
return BlockEntityRenderer.super.shouldRender(monitor, camera) && monitor.getXIndex() == 0 && monitor.getYIndex() == 0;
|
||||
}
|
||||
|
||||
private static ByteBuffer getBuffer(int capacity) {
|
||||
var buffer = backingBuffer;
|
||||
if (buffer == null || buffer.capacity() < capacity) {
|
||||
@@ -281,4 +287,15 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
buffer.clear();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static final class State extends BlockEntityRenderState {
|
||||
private Direction direction = Direction.NORTH;
|
||||
private Direction front = Direction.NORTH;
|
||||
private int width;
|
||||
private int height;
|
||||
private @Nullable ClientMonitor terminal;
|
||||
|
||||
private State() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.client.render.BlockOutlineRenderer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -25,14 +25,14 @@ public final class MonitorHighlightRenderer {
|
||||
private MonitorHighlightRenderer() {
|
||||
}
|
||||
|
||||
public static boolean drawHighlight(PoseStack transformStack, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||
public static BlockOutlineRenderer.@Nullable Renderer drawHighlight(Camera camera, BlockHitResult hit) {
|
||||
// Preserve normal behaviour when crouching.
|
||||
if (camera.getEntity().isCrouching()) return false;
|
||||
if (camera.getEntity().isCrouching()) return null;
|
||||
|
||||
var world = camera.getEntity().level();
|
||||
var pos = hit.getBlockPos();
|
||||
|
||||
if (!(world.getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return false;
|
||||
if (!(world.getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return null;
|
||||
|
||||
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
|
||||
var faces = EnumSet.allOf(Direction.class);
|
||||
@@ -44,14 +44,16 @@ public final class MonitorHighlightRenderer {
|
||||
if (monitor.getYIndex() != monitor.getHeight() - 1) faces.remove(monitor.getDown());
|
||||
|
||||
var cameraPos = camera.getPosition();
|
||||
transformStack.pushPose();
|
||||
transformStack.translate(pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z());
|
||||
var xOffset = pos.getX() - cameraPos.x();
|
||||
var yOffset = pos.getY() - cameraPos.y();
|
||||
var zOffset = pos.getZ() - cameraPos.z();
|
||||
|
||||
var transform = transformStack.last();
|
||||
BlockOutlineRenderer.render(bufferSource, (buffer, colour) -> draw(buffer, transform, faces, colour));
|
||||
|
||||
transformStack.popPose();
|
||||
return true;
|
||||
return (transform, buffer, colour) -> {
|
||||
transform.pushPose();
|
||||
transform.translate(xOffset, yOffset, zOffset);
|
||||
draw(buffer, transform.last(), faces, colour);
|
||||
transform.popPose();
|
||||
};
|
||||
}
|
||||
|
||||
private static void draw(VertexConsumer buffer, PoseStack.Pose transform, EnumSet<Direction> faces, int colour) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -67,7 +68,7 @@ public final class FixedWidthFontRenderer {
|
||||
return 15 - Terminal.getColour(c, def);
|
||||
}
|
||||
|
||||
private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour, int light) {
|
||||
private static void drawChar(Matrix4f matrix, VertexConsumer buffer, float x, float y, int index, int colour, int light) {
|
||||
// Short circuit to avoid the common case - the texture should be blank here after all.
|
||||
if (index == '\0' || index == ' ') return;
|
||||
|
||||
@@ -78,30 +79,30 @@ public final class FixedWidthFontRenderer {
|
||||
var yStart = 1 + row * (FONT_HEIGHT + 2);
|
||||
|
||||
quad(
|
||||
emitter, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, 0, colour,
|
||||
matrix, buffer, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, 0, colour,
|
||||
xStart / WIDTH, yStart / WIDTH, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light
|
||||
);
|
||||
}
|
||||
|
||||
public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, int colour, int light) {
|
||||
quad(emitter, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light);
|
||||
public static void drawQuad(Matrix4f matrix, VertexConsumer buffer, float x, float y, float z, float width, float height, int colour, int light) {
|
||||
quad(matrix, buffer, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light);
|
||||
}
|
||||
|
||||
private static void drawQuad(QuadEmitter emitter, float x, float y, float width, float height, Palette palette, char colourIndex, int light) {
|
||||
private static void drawQuad(Matrix4f matrix, VertexConsumer buffer, float x, float y, float width, float height, Palette palette, char colourIndex, int light) {
|
||||
var colour = palette.getRenderColours(getColour(colourIndex, Colour.BLACK));
|
||||
drawQuad(emitter, x, y, 0, width, height, colour, light);
|
||||
drawQuad(matrix, buffer, x, y, 0, width, height, colour, light);
|
||||
}
|
||||
|
||||
private static void drawBackground(
|
||||
QuadEmitter emitter, float x, float y, TextBuffer backgroundColour, Palette palette,
|
||||
Matrix4f matrix, VertexConsumer buffer, float x, float y, TextBuffer backgroundColour, Palette palette,
|
||||
float leftMarginSize, float rightMarginSize, float height, int light
|
||||
) {
|
||||
if (leftMarginSize > 0) {
|
||||
drawQuad(emitter, x - leftMarginSize, y, leftMarginSize, height, palette, backgroundColour.charAt(0), light);
|
||||
drawQuad(matrix, buffer, x - leftMarginSize, y, leftMarginSize, height, palette, backgroundColour.charAt(0), light);
|
||||
}
|
||||
|
||||
if (rightMarginSize > 0) {
|
||||
drawQuad(emitter, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, backgroundColour.charAt(backgroundColour.length() - 1), light);
|
||||
drawQuad(matrix, buffer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, backgroundColour.charAt(backgroundColour.length() - 1), light);
|
||||
}
|
||||
|
||||
// Batch together runs of identical background cells.
|
||||
@@ -112,7 +113,7 @@ public final class FixedWidthFontRenderer {
|
||||
if (colourIndex == blockColour) continue;
|
||||
|
||||
if (blockColour != '\0') {
|
||||
drawQuad(emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, blockColour, light);
|
||||
drawQuad(matrix, buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, blockColour, light);
|
||||
}
|
||||
|
||||
blockColour = colourIndex;
|
||||
@@ -120,22 +121,22 @@ public final class FixedWidthFontRenderer {
|
||||
}
|
||||
|
||||
if (blockColour != '\0') {
|
||||
drawQuad(emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, blockColour, light);
|
||||
drawQuad(matrix, buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, blockColour, light);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawString(QuadEmitter emitter, float x, float y, TextBuffer text, TextBuffer textColour, Palette palette, int light) {
|
||||
public static void drawString(Matrix4f matrix, VertexConsumer buffer, float x, float y, TextBuffer text, TextBuffer textColour, Palette palette, int light) {
|
||||
for (var i = 0; i < text.length(); i++) {
|
||||
var colour = palette.getRenderColours(getColour(textColour.charAt(i), Colour.BLACK));
|
||||
|
||||
int index = text.charAt(i);
|
||||
if (index > 255) index = '?';
|
||||
drawChar(emitter, x + i * FONT_WIDTH, y, index, colour, light);
|
||||
drawChar(matrix, buffer, x + i * FONT_WIDTH, y, index, colour, light);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void drawTerminalForeground(QuadEmitter emitter, float x, float y, Terminal terminal) {
|
||||
public static void drawTerminalForeground(Matrix4f matrix, VertexConsumer buffer, float x, float y, Terminal terminal) {
|
||||
var palette = terminal.getPalette();
|
||||
var height = terminal.getHeight();
|
||||
|
||||
@@ -143,14 +144,14 @@ public final class FixedWidthFontRenderer {
|
||||
for (var i = 0; i < height; i++) {
|
||||
var rowY = y + FONT_HEIGHT * i;
|
||||
drawString(
|
||||
emitter, x, rowY, terminal.getLine(i), terminal.getTextColourLine(i),
|
||||
matrix, buffer, x, rowY, terminal.getLine(i), terminal.getTextColourLine(i),
|
||||
palette, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawTerminalBackground(
|
||||
QuadEmitter emitter, float x, float y, Terminal terminal,
|
||||
Matrix4f matrix, VertexConsumer buffer, float x, float y, Terminal terminal,
|
||||
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
|
||||
) {
|
||||
var palette = terminal.getPalette();
|
||||
@@ -158,12 +159,12 @@ public final class FixedWidthFontRenderer {
|
||||
|
||||
// Top and bottom margins
|
||||
drawBackground(
|
||||
emitter, x, y - topMarginSize, terminal.getBackgroundColourLine(0), palette,
|
||||
matrix, buffer, x, y - topMarginSize, terminal.getBackgroundColourLine(0), palette,
|
||||
leftMarginSize, rightMarginSize, topMarginSize, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
|
||||
drawBackground(
|
||||
emitter, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine(height - 1), palette,
|
||||
matrix, buffer, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine(height - 1), palette,
|
||||
leftMarginSize, rightMarginSize, bottomMarginSize, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
|
||||
@@ -171,7 +172,7 @@ public final class FixedWidthFontRenderer {
|
||||
for (var i = 0; i < height; i++) {
|
||||
var rowY = y + FONT_HEIGHT * i;
|
||||
drawBackground(
|
||||
emitter, x, rowY, terminal.getBackgroundColourLine(i), palette,
|
||||
matrix, buffer, x, rowY, terminal.getBackgroundColourLine(i), palette,
|
||||
leftMarginSize, rightMarginSize, FONT_HEIGHT, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
}
|
||||
@@ -185,52 +186,40 @@ public final class FixedWidthFontRenderer {
|
||||
return cursorX >= 0 && cursorX < terminal.getWidth() && cursorY >= 0 && cursorY < terminal.getHeight();
|
||||
}
|
||||
|
||||
public static void drawCursor(QuadEmitter emitter, float x, float y, Terminal terminal) {
|
||||
public static void drawCursor(Matrix4f matrix, VertexConsumer buffer, float x, float y, Terminal terminal) {
|
||||
if (isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()) {
|
||||
var colour = terminal.getPalette().getRenderColours(15 - terminal.getTextColour());
|
||||
drawChar(emitter, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, LightTexture.FULL_BRIGHT);
|
||||
drawChar(matrix, buffer, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, LightTexture.FULL_BRIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawTerminal(
|
||||
QuadEmitter emitter, float x, float y, Terminal terminal,
|
||||
Matrix4f matrix, VertexConsumer buffer, float x, float y, Terminal terminal,
|
||||
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
|
||||
) {
|
||||
drawTerminalBackground(
|
||||
emitter, x, y, terminal,
|
||||
matrix, buffer, x, y, terminal,
|
||||
topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize
|
||||
);
|
||||
|
||||
// Render the foreground with a slight offset. By calling .translate() on the matrix itself, we're translating
|
||||
// in screen space, rather than in model/view space.
|
||||
// It's definitely not perfect, but better than z fighting!
|
||||
var transformBackup = new Matrix4f(emitter.poseMatrix());
|
||||
emitter.poseMatrix().translate(new Vector3f(0, 0, Z_OFFSET));
|
||||
var offsetMatrix = new Matrix4f(matrix).translate(new Vector3f(0, 0, Z_OFFSET));
|
||||
|
||||
drawTerminalForeground(emitter, x, y, terminal);
|
||||
drawCursor(emitter, x, y, terminal);
|
||||
|
||||
emitter.poseMatrix().set(transformBackup);
|
||||
drawTerminalForeground(offsetMatrix, buffer, x, y, terminal);
|
||||
drawCursor(offsetMatrix, buffer, x, y, terminal);
|
||||
}
|
||||
|
||||
public static void drawEmptyTerminal(QuadEmitter emitter, float x, float y, float width, float height) {
|
||||
drawQuad(emitter, x, y, 0, width, height, BLACK, LightTexture.FULL_BRIGHT);
|
||||
public static void drawEmptyTerminal(PoseStack transform, SubmitNodeCollector collector, float x, float y, float width, float height) {
|
||||
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) ->
|
||||
drawQuad(pose.pose(), buffer, x, y, 0, width, height, BLACK, LightTexture.FULL_BRIGHT));
|
||||
}
|
||||
|
||||
public record QuadEmitter(Matrix4f poseMatrix, VertexConsumer consumer) {
|
||||
}
|
||||
|
||||
public static QuadEmitter toVertexConsumer(PoseStack transform, VertexConsumer consumer) {
|
||||
return new QuadEmitter(transform.last().pose(), consumer);
|
||||
}
|
||||
|
||||
private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) {
|
||||
var poseMatrix = c.poseMatrix();
|
||||
var consumer = c.consumer();
|
||||
|
||||
consumer.addVertex(poseMatrix, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x1, y2, z).setColor(colour).setUv(u1, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y2, z).setColor(colour).setUv(u2, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y1, z).setColor(colour).setUv(u2, v1).setLight(light);
|
||||
private static void quad(Matrix4f matrix, VertexConsumer buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) {
|
||||
buffer.addVertex(matrix, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||
buffer.addVertex(matrix, x1, y2, z).setColor(colour).setUv(u1, v2).setLight(light);
|
||||
buffer.addVertex(matrix, x2, y2, z).setColor(colour).setUv(u2, v2).setLight(light);
|
||||
buffer.addVertex(matrix, x2, y1, z).setColor(colour).setUv(u2, v1).setLight(light);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import net.minecraft.client.data.models.ItemModelGenerators;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
||||
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
|
||||
import net.minecraft.client.resources.model.AtlasIds;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.RegistrySetBuilder;
|
||||
import net.minecraft.data.AtlasIds;
|
||||
import net.minecraft.data.DataProvider;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.registries.RegistryPatchGenerator;
|
||||
@@ -71,9 +71,9 @@ public final class DataProviders {
|
||||
|
||||
generator.addFromCodec("Block atlases", PackOutput.Target.RESOURCE_PACK, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||
out.accept(AtlasIds.BLOCKS, makeSprites(Stream.of(
|
||||
LecternPrintoutModel.TEXTURE,
|
||||
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
|
||||
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
|
||||
LecternPrintoutModel.MATERIAL.texture(),
|
||||
LecternPocketModel.MATERIAL_NORMAL.texture(), LecternPocketModel.MATERIAL_ADVANCED.texture(),
|
||||
LecternPocketModel.MATERIAL_COLOUR.texture(), LecternPocketModel.MATERIAL_FRAME.texture(), LecternPocketModel.MATERIAL_LIGHT.texture()
|
||||
)));
|
||||
|
||||
out.accept(AtlasIds.GUI, makeSprites(
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
|
||||
import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction;
|
||||
import net.minecraft.world.level.storage.loot.functions.CopyNameFunction;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
|
||||
@@ -92,7 +93,7 @@ class LootTableProvider {
|
||||
private static void namedBlockDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
|
||||
blockDrop(
|
||||
add, wrapper,
|
||||
LootItem.lootTableItem(wrapper.get()).apply(CopyNameFunction.copyName(CopyNameFunction.NameSource.BLOCK_ENTITY)),
|
||||
LootItem.lootTableItem(wrapper.get()).apply(CopyNameFunction.copyName(new CopyNameFunction.Source(LootContextParams.BLOCK_ENTITY))),
|
||||
ExplosionCondition.survivesExplosion()
|
||||
);
|
||||
}
|
||||
@@ -100,7 +101,7 @@ class LootTableProvider {
|
||||
private static void computerDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> block) {
|
||||
blockDrop(
|
||||
add, block,
|
||||
LootItem.lootTableItem(block.get()).apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY)),
|
||||
LootItem.lootTableItem(block.get()).apply(CopyComponentsFunction.copyComponentsFromBlockEntity(LootContextParams.BLOCK_ENTITY)),
|
||||
AnyOfCondition.anyOf(
|
||||
BlockNamedEntityLootCondition.BUILDER,
|
||||
HasComputerIdLootCondition.BUILDER,
|
||||
|
||||
@@ -447,7 +447,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
}
|
||||
|
||||
private static ItemStack playerHead(String name, String uuid) {
|
||||
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name)));
|
||||
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, ResolvableProfile.createResolved(new GameProfile(UUID.fromString(uuid), name)));
|
||||
}
|
||||
|
||||
private ShapedSpecBuilder customShaped(RecipeCategory category, ItemStack result) {
|
||||
|
||||
@@ -59,11 +59,11 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||
var player = source.getPlayer();
|
||||
return server.isDedicatedServer()
|
||||
? source.getEntity() == null && source.hasPermission(4) && source.getTextName().equals("Server")
|
||||
: player != null && server.isSingleplayerOwner(player.getGameProfile());
|
||||
: player != null && server.isSingleplayerOwner(player.nameAndId());
|
||||
}
|
||||
|
||||
public static boolean isOwner(ServerPlayer player) {
|
||||
var server = player.getServer();
|
||||
return server != null && server.isSingleplayerOwner(player.getGameProfile());
|
||||
var server = player.level().getServer();
|
||||
return server != null && server.isSingleplayerOwner(player.nameAndId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock {
|
||||
|
||||
@Override
|
||||
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
|
||||
if (level.isClientSide) return InteractionResult.SUCCESS;
|
||||
if (level.isClientSide()) return InteractionResult.SUCCESS;
|
||||
|
||||
if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
|
||||
player.openMenu(container);
|
||||
@@ -65,7 +65,7 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) {
|
||||
protected final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos, Direction direction) {
|
||||
return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos));
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
|
||||
if (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
|
||||
// Regular right click to activate computer
|
||||
if (!level.isClientSide && computer.isUsable(player)) {
|
||||
if (!level.isClientSide() && computer.isUsable(player)) {
|
||||
var serverComputer = computer.createServerComputer();
|
||||
serverComputer.turnOn();
|
||||
|
||||
@@ -143,7 +143,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
@Override
|
||||
@Nullable
|
||||
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
||||
return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, this.type.get(), serverTicker);
|
||||
return level.isClientSide() ? null : BlockEntityHelpers.createTickerHelper(type, this.type.get(), serverTicker);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -67,7 +67,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
}
|
||||
|
||||
protected void unload() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
|
||||
var computer = getServerComputer();
|
||||
if (computer != null) computer.close();
|
||||
@@ -91,7 +91,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
}
|
||||
|
||||
protected void serverTick() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
if (computerID < 0 && !startOn) return; // Don't tick if we don't need a computer!
|
||||
|
||||
var computer = createServerComputer();
|
||||
@@ -157,7 +157,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
@Override
|
||||
public final void loadAdditional(ValueInput nbt) {
|
||||
super.loadAdditional(nbt);
|
||||
if (level != null && level.isClientSide) {
|
||||
if (level != null && level.isClientSide()) {
|
||||
loadClient(nbt);
|
||||
} else {
|
||||
loadServer(nbt);
|
||||
@@ -340,14 +340,14 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
}
|
||||
|
||||
public final void setComputerID(int id) {
|
||||
if (getLevel().isClientSide || computerID == id) return;
|
||||
if (getLevel().isClientSide() || computerID == id) return;
|
||||
|
||||
computerID = id;
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
public final void setLabel(@Nullable String label) {
|
||||
if (getLevel().isClientSide || Objects.equals(this.label, label)) return;
|
||||
if (getLevel().isClientSide() || Objects.equals(this.label, label)) return;
|
||||
|
||||
this.label = label;
|
||||
var computer = getServerComputer();
|
||||
@@ -386,7 +386,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
|
||||
@Nullable
|
||||
public ServerComputer getServerComputer() {
|
||||
return getLevel().isClientSide || getLevel().getServer() == null ? null : ServerContext.get(getLevel().getServer()).registry().get(instanceID);
|
||||
return getLevel().isClientSide() || getLevel().getServer() == null ? null : ServerContext.get(getLevel().getServer()).registry().get(instanceID);
|
||||
}
|
||||
|
||||
// Networking stuff
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum ComputerFamily {
|
||||
}
|
||||
|
||||
private static boolean checkCommandUsable(Player player) {
|
||||
var server = player.getServer();
|
||||
var server = player.level().getServer();
|
||||
if (server == null || !server.isCommandBlockEnabled()) {
|
||||
player.displayClientMessage(Component.translatable("advMode.notEnabled"), true);
|
||||
return false;
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
package dan200.computercraft.shared.container;
|
||||
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.ContainerUser;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -69,12 +72,12 @@ public interface InventoryDelegate extends Container {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void startOpen(Player player) {
|
||||
default void startOpen(ContainerUser player) {
|
||||
getInventory().startOpen(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void stopOpen(Player player) {
|
||||
default void stopOpen(ContainerUser player) {
|
||||
getInventory().stopOpen(player);
|
||||
}
|
||||
|
||||
@@ -102,4 +105,24 @@ public interface InventoryDelegate extends Container {
|
||||
default boolean hasAnyMatching(Predicate<ItemStack> predicate) {
|
||||
return getInventory().hasAnyMatching(predicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getMaxStackSize(ItemStack stack) {
|
||||
return getInventory().getMaxStackSize(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
default List<ContainerUser> getEntitiesWithContainerOpen() {
|
||||
return getInventory().getEntitiesWithContainerOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean canTakeItem(Container target, int slot, ItemStack stack) {
|
||||
return getInventory().canTakeItem(target, slot, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Iterator<ItemStack> iterator() {
|
||||
return getInventory().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.util.RandomSource;
|
||||
@@ -55,7 +56,7 @@ public class CustomLecternBlock extends LecternBlock {
|
||||
*/
|
||||
public static InteractionResult tryPlaceItem(Player player, Level level, BlockPos pos, BlockState blockState, ItemStack item) {
|
||||
if (item.getItem() instanceof PrintoutItem || item.getItem() instanceof PocketComputerItem) {
|
||||
if (!level.isClientSide) replaceLectern(player, level, pos, blockState, item);
|
||||
if (!level.isClientSide()) replaceLectern(player, level, pos, blockState, item);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@@ -129,13 +130,13 @@ public class CustomLecternBlock extends LecternBlock {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
|
||||
protected int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos, Direction direction) {
|
||||
return level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern ? lectern.getRedstoneSignal() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
if (!level.isClientSide() && level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
if (player.isSecondaryUseActive()) {
|
||||
// When shift+clicked with an empty hand, drop the item and replace with the normal lectern.
|
||||
clearLectern(level, pos, state);
|
||||
@@ -152,7 +153,7 @@ public class CustomLecternBlock extends LecternBlock {
|
||||
|
||||
@Override
|
||||
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
|
||||
return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.LECTERN.get(), serverTicker);
|
||||
return level.isClientSide() ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.LECTERN.get(), serverTicker);
|
||||
}
|
||||
|
||||
private static final BlockEntityTicker<CustomLecternBlockEntity> serverTicker = (level, pos, state, lectern) -> lectern.tick();
|
||||
|
||||
@@ -23,7 +23,7 @@ public class PrintoutItem extends Item {
|
||||
@Override
|
||||
public InteractionResult use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
if (!world.isClientSide()) {
|
||||
var title = PrintoutData.getOrEmpty(stack).title();
|
||||
var displayTitle = Strings.isNullOrEmpty(title) ? stack.getDisplayName() : Component.literal(title);
|
||||
player.openMenu(new SimpleMenuProvider((id, playerInventory, p) -> PrintoutMenu.createInHand(id, p, hand), displayTitle));
|
||||
|
||||
@@ -58,7 +58,7 @@ public class DiskDriveBlock extends HorizontalContainerBlock {
|
||||
var blockPos = context.getClickedPos();
|
||||
var blockState = level.getBlockState(blockPos);
|
||||
if (blockState.is(ModRegistry.Blocks.DISK_DRIVE.get()) && blockState.getValue(STATE) == DiskDriveState.EMPTY) {
|
||||
if (!level.isClientSide && level.getBlockEntity(blockPos) instanceof DiskDriveBlockEntity drive && drive.getDiskStack().isEmpty()) {
|
||||
if (!level.isClientSide() && level.getBlockEntity(blockPos) instanceof DiskDriveBlockEntity drive && drive.getDiskStack().isEmpty()) {
|
||||
drive.setDiskStack(context.getItemInHand().split(1));
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
@@ -76,6 +76,6 @@ public class DiskDriveBlock extends HorizontalContainerBlock {
|
||||
@Override
|
||||
@Nullable
|
||||
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
||||
return level.isClientSide ? null : BaseEntityBlock.createTickerHelper(type, ModRegistry.BlockEntities.DISK_DRIVE.get(), serverTicker);
|
||||
return level.isClientSide() ? null : BaseEntityBlock.createTickerHelper(type, ModRegistry.BlockEntities.DISK_DRIVE.get(), serverTicker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity imp
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
if (level != null && !level.isClientSide) updateMedia();
|
||||
if (level != null && !level.isClientSide()) updateMedia();
|
||||
super.setChanged();
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity imp
|
||||
}
|
||||
|
||||
private void ejectContents() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
|
||||
var stack = getDiskStack();
|
||||
if (stack.isEmpty()) return;
|
||||
|
||||
@@ -40,7 +40,7 @@ public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
|
||||
* @return The energy stored in this block, in FE.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergy(T energy);
|
||||
public abstract long getEnergy(T energy);
|
||||
|
||||
/**
|
||||
* Get the maximum amount of energy this block can store.
|
||||
@@ -49,5 +49,5 @@ public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
|
||||
* @return The energy capacity of this block.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergyCapacity(T energy);
|
||||
public abstract long getEnergyCapacity(T energy);
|
||||
}
|
||||
|
||||
@@ -96,13 +96,13 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
|
||||
}
|
||||
|
||||
@ForgeOverride
|
||||
public boolean onDestroyedByPlayer(BlockState state, Level world, BlockPos pos, Player player, boolean willHarvest, FluidState fluid) {
|
||||
public boolean onDestroyedByPlayer(BlockState state, Level world, BlockPos pos, Player player, ItemStack toolStack, boolean willHarvest, FluidState fluid) {
|
||||
playerWillDestroy(world, pos, state, player);
|
||||
if (onCustomDestroyBlock(state, world, pos, player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return world.setBlock(pos, fluid.createLegacyBlock(), world.isClientSide ? UPDATE_ALL_IMMEDIATE : UPDATE_ALL);
|
||||
return world.setBlock(pos, fluid.createLegacyBlock(), world.isClientSide() ? UPDATE_ALL_IMMEDIATE : UPDATE_ALL);
|
||||
}
|
||||
|
||||
public boolean onCustomDestroyBlock(BlockState state, Level world, BlockPos pos, Player player) {
|
||||
@@ -130,7 +130,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
|
||||
world.setBlockAndUpdate(pos, correctConnections(world, pos, newState));
|
||||
|
||||
cable.connectionsChanged();
|
||||
if (!world.isClientSide && !player.getAbilities().instabuild) {
|
||||
if (!world.isClientSide() && !player.getAbilities().instabuild) {
|
||||
Block.popResource(world, pos, item);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
public void setRemoved() {
|
||||
super.setRemoved();
|
||||
modem.removed();
|
||||
if (level == null || !level.isClientSide) node.remove();
|
||||
if (level == null || !level.isClientSide()) node.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +108,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
|
||||
void neighborChanged() {
|
||||
var dir = getModemDirection();
|
||||
if (!getLevel().isClientSide && dir != null && isPeripheralOn()) queueRefreshPeripheral();
|
||||
if (!getLevel().isClientSide() && dir != null && isPeripheralOn()) queueRefreshPeripheral();
|
||||
}
|
||||
|
||||
void queueRefreshPeripheral() {
|
||||
@@ -119,7 +119,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
InteractionResult use(Player player) {
|
||||
if (!canAttachPeripheral()) return InteractionResult.FAIL;
|
||||
|
||||
if (getLevel().isClientSide) return InteractionResult.SUCCESS;
|
||||
if (getLevel().isClientSide()) return InteractionResult.SUCCESS;
|
||||
|
||||
var oldName = peripheral.getConnectedName();
|
||||
if (isPeripheralOn()) {
|
||||
@@ -167,7 +167,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
void blockTick() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
|
||||
if (refreshPeripheral) {
|
||||
refreshPeripheral = false;
|
||||
@@ -185,7 +185,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
void connectionsChanged() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
refreshConnections = false;
|
||||
|
||||
var state = getBlockState();
|
||||
|
||||
@@ -97,7 +97,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
for (var modem : modems) {
|
||||
if (modem != null) modem.removed();
|
||||
}
|
||||
if (level == null || !level.isClientSide) node.remove();
|
||||
if (level == null || !level.isClientSide()) node.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,7 +120,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
|
||||
public InteractionResult use(Player player) {
|
||||
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
|
||||
if (getLevel().isClientSide) return InteractionResult.SUCCESS;
|
||||
if (getLevel().isClientSide()) return InteractionResult.SUCCESS;
|
||||
|
||||
// On server, we interacted if a peripheral was found
|
||||
var oldPeriphNames = getConnectedPeripheralNames();
|
||||
@@ -167,7 +167,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
void blockTick() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
|
||||
if (invalidSides != 0) {
|
||||
var oldInvalidSides = invalidSides;
|
||||
@@ -194,7 +194,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
private void connectionsChanged() {
|
||||
if (getLevel().isClientSide) return;
|
||||
if (getLevel().isClientSide()) return;
|
||||
refreshConnections = false;
|
||||
|
||||
var world = getLevel();
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBl
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
if (!level.isClientSide) {
|
||||
if (!level.isClientSide()) {
|
||||
monitor.monitorTouched(
|
||||
(float) (hit.getLocation().x - hit.getBlockPos().getX()),
|
||||
(float) (hit.getLocation().y - hit.getBlockPos().getY()),
|
||||
@@ -111,7 +111,7 @@ public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBl
|
||||
super.setPlacedBy(world, pos, blockState, livingEntity, itemStack);
|
||||
|
||||
var entity = world.getBlockEntity(pos);
|
||||
if (entity instanceof MonitorBlockEntity monitor && !world.isClientSide) {
|
||||
if (entity instanceof MonitorBlockEntity monitor && !world.isClientSide()) {
|
||||
// Defer the block update if we're being placed by another TE. See #691
|
||||
if (livingEntity == null || (livingEntity instanceof ServerPlayer player && PlatformHelper.get().isFakePlayer(player))) {
|
||||
monitor.updateNeighborsDeferred();
|
||||
|
||||
@@ -99,7 +99,7 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
public void preRemoveSideEffects(BlockPos blockPos, BlockState blockState) {
|
||||
super.preRemoveSideEffects(blockPos, blockState);
|
||||
isRemoving = true;
|
||||
if (level != null && !getLevel().isClientSide) contractNeighbours();
|
||||
if (level != null && !getLevel().isClientSide()) contractNeighbours();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,7 +129,7 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
width = nbt.getIntOr(NBT_WIDTH, 1);
|
||||
height = nbt.getIntOr(NBT_HEIGHT, 1);
|
||||
|
||||
if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex);
|
||||
if (level != null && level.isClientSide()) onClientLoad(oldXIndex, oldYIndex);
|
||||
}
|
||||
|
||||
void blockTick() {
|
||||
|
||||
@@ -50,7 +50,7 @@ public class SpeakerBlock extends HorizontalDirectionalBlock implements EntityBl
|
||||
@Override
|
||||
@Nullable
|
||||
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
||||
return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker);
|
||||
return level.isClientSide() ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -31,7 +31,7 @@ public class SpeakerBlockEntity extends BlockEntity {
|
||||
@Override
|
||||
public void setRemoved() {
|
||||
super.setRemoved();
|
||||
if (level != null && !level.isClientSide) {
|
||||
if (level != null && !level.isClientSide()) {
|
||||
ServerNetworking.sendToAllPlayers(new SpeakerStopClientMessage(peripheral.getSource()), Nullability.assertNonNull(getLevel().getServer()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class PocketComputerItem extends Item {
|
||||
@ForgeOverride
|
||||
public boolean onEntityItemUpdate(ItemStack stack, ItemEntity entity) {
|
||||
var level = entity.level();
|
||||
if (level.isClientSide || level.getServer() == null) return false;
|
||||
if (level.isClientSide() || level.getServer() == null) return false;
|
||||
|
||||
// If we're an item entity, tick an already existing computer (as to update the position), but do not keep the
|
||||
// computer alive.
|
||||
@@ -123,7 +123,7 @@ public class PocketComputerItem extends Item {
|
||||
@Override
|
||||
public InteractionResult use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
if (!world.isClientSide()) {
|
||||
var holder = new PocketHolder.PlayerHolder((ServerPlayer) player, InventoryUtil.getHandSlot(player, hand));
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
var computer = brain.computer();
|
||||
|
||||
@@ -125,7 +125,7 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity entity, ItemStack stack) {
|
||||
super.setPlacedBy(level, pos, state, entity, stack);
|
||||
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle && entity instanceof Player player) {
|
||||
if (!level.isClientSide() && level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle && entity instanceof Player player) {
|
||||
turtle.setOwningPlayer(player.getGameProfile());
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
protected InteractionResult useItemOn(ItemStack currentItem, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
if (currentItem.getItem() == Items.NAME_TAG && currentItem.has(DataComponents.CUSTOM_NAME) && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
|
||||
// Label to rename computer
|
||||
if (!level.isClientSide) {
|
||||
if (!level.isClientSide()) {
|
||||
computer.setLabel(currentItem.getHoverName().getString());
|
||||
currentItem.shrink(1);
|
||||
}
|
||||
@@ -157,6 +157,6 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
@Override
|
||||
@Nullable
|
||||
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
||||
return level.isClientSide ? BlockEntityHelpers.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type);
|
||||
return level.isClientSide() ? BlockEntityHelpers.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package dan200.computercraft.shared.turtle.core;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -28,7 +27,6 @@ import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.UUIDUtil;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -65,15 +63,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
private static final String NBT_SLOT = "Slot";
|
||||
|
||||
/**
|
||||
* {@link net.minecraft.world.item.component.ResolvableProfile#CODEC}, but resolving to a {@link GameProfile}
|
||||
* directly. We don't use {@link ExtraCodecs#GAME_PROFILE}, as that encodes the UUID as a string, not an int array.
|
||||
*/
|
||||
private static final Codec<GameProfile> GAME_PROFILE_CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
UUIDUtil.CODEC.fieldOf("id").forGetter(GameProfile::getId),
|
||||
ExtraCodecs.PLAYER_NAME.fieldOf("name").forGetter(GameProfile::getName)
|
||||
)
|
||||
.apply(instance, GameProfile::new));
|
||||
private static final Codec<GameProfile> GAME_PROFILE_CODEC = ExtraCodecs.STORED_GAME_PROFILE.codec();
|
||||
|
||||
private static final int ANIM_DURATION = 8;
|
||||
|
||||
@@ -121,7 +111,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
public void update() {
|
||||
var world = getLevel();
|
||||
if (!world.isClientSide) {
|
||||
if (!world.isClientSide()) {
|
||||
// Advance movement
|
||||
updateCommands();
|
||||
|
||||
@@ -221,7 +211,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public boolean teleportTo(Level world, BlockPos pos) {
|
||||
if (world.isClientSide || getLevel().isClientSide) {
|
||||
if (world.isClientSide() || getLevel().isClientSide()) {
|
||||
throw new UnsupportedOperationException("Cannot teleport on the client");
|
||||
}
|
||||
|
||||
@@ -335,7 +325,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public void setSelectedSlot(int slot) {
|
||||
if (getLevel().isClientSide) throw new UnsupportedOperationException("Cannot set the slot on the client");
|
||||
if (getLevel().isClientSide()) throw new UnsupportedOperationException("Cannot set the slot on the client");
|
||||
|
||||
if (slot >= 0 && slot < owner.getContainerSize()) {
|
||||
selectedSlot = slot;
|
||||
@@ -371,7 +361,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public boolean consumeFuel(int fuel) {
|
||||
if (getLevel().isClientSide) throw new UnsupportedOperationException("Cannot consume fuel on the client");
|
||||
if (getLevel().isClientSide()) throw new UnsupportedOperationException("Cannot consume fuel on the client");
|
||||
|
||||
if (!isFuelNeeded()) return true;
|
||||
|
||||
@@ -385,7 +375,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public void addFuel(int fuel) {
|
||||
if (getLevel().isClientSide) throw new UnsupportedOperationException("Cannot add fuel on the client");
|
||||
if (getLevel().isClientSide()) throw new UnsupportedOperationException("Cannot add fuel on the client");
|
||||
|
||||
var addition = Math.max(fuel, 0);
|
||||
setFuelLevel(getFuelLevel() + addition);
|
||||
@@ -393,7 +383,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public MethodResult executeCommand(TurtleCommand command) {
|
||||
if (getLevel().isClientSide) throw new UnsupportedOperationException("Cannot run commands on the client");
|
||||
if (getLevel().isClientSide()) throw new UnsupportedOperationException("Cannot run commands on the client");
|
||||
if (commandQueue.size() > 16) return MethodResult.of(false, "Too many ongoing turtle commands");
|
||||
|
||||
commandQueue.offer(new TurtleCommandQueueEntry(++commandsIssued, command));
|
||||
@@ -403,7 +393,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public void playAnimation(TurtleAnimation animation) {
|
||||
if (getLevel().isClientSide) throw new UnsupportedOperationException("Cannot play animations on the client");
|
||||
if (getLevel().isClientSide()) throw new UnsupportedOperationException("Cannot play animations on the client");
|
||||
|
||||
this.animation = animation;
|
||||
if (this.animation == TurtleAnimation.SHORT_WAIT) {
|
||||
@@ -489,7 +479,9 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
instance.setUpgrade(upgrade);
|
||||
|
||||
// Create peripherals
|
||||
if (owner.getLevel() != null && !owner.getLevel().isClientSide) updatePeripherals(owner.createServerComputer());
|
||||
if (owner.getLevel() != null && !owner.getLevel().isClientSide()) {
|
||||
updatePeripherals(owner.createServerComputer());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -681,7 +673,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
}
|
||||
|
||||
// Advance valentines day easter egg
|
||||
if (world.isClientSide && animation == TurtleAnimation.MOVE_FORWARD && animationProgress == 4) {
|
||||
if (world.isClientSide() && animation == TurtleAnimation.MOVE_FORWARD && animationProgress == 4) {
|
||||
// Spawn love pfx if valentines day
|
||||
var currentHoliday = Holiday.getCurrent();
|
||||
if (currentHoliday == Holiday.VALENTINES) {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TurtleItem extends BlockItem {
|
||||
|
||||
public static final CauldronInteraction CAULDRON_INTERACTION = (blockState, level, pos, player, hand, stack) -> {
|
||||
if (!stack.has(DataComponents.DYED_COLOR)) return InteractionResult.TRY_WITH_EMPTY_HAND;
|
||||
if (!level.isClientSide) {
|
||||
if (!level.isClientSide()) {
|
||||
stack.remove(DataComponents.DYED_COLOR);
|
||||
LayeredCauldronBlock.lowerFillLevel(blockState, level, pos);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class TurtleModem extends AbstractTurtleUpgrade {
|
||||
@Override
|
||||
public void update(ITurtleAccess turtle, TurtleSide side) {
|
||||
// Advance the modem
|
||||
if (!turtle.getLevel().isClientSide) {
|
||||
if (!turtle.getLevel().isClientSide()) {
|
||||
var peripheral = turtle.getPeripheral(side);
|
||||
if (peripheral instanceof Peripheral modem) {
|
||||
var state = modem.getModemState();
|
||||
|
||||
@@ -29,6 +29,7 @@ import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityReference;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
@@ -218,7 +219,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
|
||||
// If this is a projectile, attempt to deflect it instead.
|
||||
if (entity.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && entity instanceof Projectile projectile &&
|
||||
projectile.deflect(ProjectileDeflection.AIM_DEFLECT, player, player, true)
|
||||
projectile.deflect(ProjectileDeflection.AIM_DEFLECT, player, EntityReference.of(player), true)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public final class TickScheduler {
|
||||
*/
|
||||
public static void schedule(Token token) {
|
||||
var world = token.owner.getLevel();
|
||||
if (world != null && !world.isClientSide && Token.STATE.compareAndSet(token, State.IDLE, State.SCHEDULED)) {
|
||||
if (world != null && !world.isClientSide() && Token.STATE.compareAndSet(token, State.IDLE, State.SCHEDULED)) {
|
||||
toTick.add(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ accessible field net/minecraft/client/gui/components/ChatComponent allMessages L
|
||||
|
||||
# ItemPocketRenderer/ItemPrintoutRenderer
|
||||
accessible method net/minecraft/client/renderer/ItemInHandRenderer calculateMapTilt (F)F
|
||||
accessible method net/minecraft/client/renderer/ItemInHandRenderer renderMapHand (Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/entity/HumanoidArm;)V
|
||||
accessible method net/minecraft/client/renderer/ItemInHandRenderer renderPlayerArm (Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFFLnet/minecraft/world/entity/HumanoidArm;)V
|
||||
accessible method net/minecraft/client/renderer/ItemInHandRenderer renderMapHand (Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V
|
||||
accessible method net/minecraft/client/renderer/ItemInHandRenderer renderPlayerArm (Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFFLnet/minecraft/world/entity/HumanoidArm;)V
|
||||
|
||||
# SpeakerInstance/SpeakerManager
|
||||
accessible method com/mojang/blaze3d/audio/Channel pumpBuffers (I)V
|
||||
|
||||
@@ -31,8 +31,8 @@ public @interface WithMinecraft {
|
||||
}
|
||||
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
ServiceLoader.load(SetupHook.class, SetupHook.class.getClassLoader()).forEach(SetupHook::run);
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import net.minecraft.gametest.framework.GameTestServer;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.Services;
|
||||
import net.minecraft.server.WorldStem;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
|
||||
import net.minecraft.server.level.progress.LevelLoadListener;
|
||||
import net.minecraft.server.packs.repository.PackRepository;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -22,7 +22,10 @@ import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
@Mixin(GameTestServer.class)
|
||||
abstract class GameTestServerMixin extends MinecraftServer {
|
||||
GameTestServerMixin(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory) {
|
||||
GameTestServerMixin(
|
||||
Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository,
|
||||
WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, LevelLoadListener progressListenerFactory
|
||||
) {
|
||||
super(serverThread, storageSource, packRepository, worldStem, proxy, fixerUpper, services, progressListenerFactory);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import dan200.computercraft.gametest.api.*
|
||||
import dan200.computercraft.shared.ModRegistry
|
||||
import dan200.computercraft.test.core.assertArrayEquals
|
||||
import dan200.computercraft.test.core.computer.getApi
|
||||
import net.minecraft.client.input.KeyEvent
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.gametest.framework.GameTestHelper
|
||||
@@ -155,8 +156,8 @@ class Computer_Test {
|
||||
// Press a key on the client
|
||||
thenOnClient {
|
||||
val screen = minecraft.screen as AbstractComputerScreen<*>
|
||||
screen.keyPressed(GLFW.GLFW_KEY_A, 0, 0)
|
||||
screen.keyReleased(GLFW.GLFW_KEY_A, 0, 0)
|
||||
screen.keyPressed(KeyEvent(GLFW.GLFW_KEY_A, 0, 0))
|
||||
screen.keyReleased(KeyEvent(GLFW.GLFW_KEY_A, 0, 0))
|
||||
}
|
||||
// And assert it is handled and sent back to the client
|
||||
thenIdle(2)
|
||||
|
||||
@@ -122,7 +122,7 @@ class Disk_Drive_Test {
|
||||
thenWaitUntil {
|
||||
val drive = helper.getBlockEntity(drivePos, DiskDriveBlockEntity::class.java)
|
||||
if (!drive.getItem(0).has(ModRegistry.DataComponents.DISK_ID.get())) {
|
||||
helper.fail("Disk has no item", drivePos)
|
||||
helper.abort("Disk has no item", drivePos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class Monitor_Test {
|
||||
val tile = context.getBlockEntity(pos, MonitorBlockEntity::class.java)
|
||||
|
||||
if (tile.width != 1 || tile.height != 1) {
|
||||
context.fail("Tile has width and height of ${tile.width}x${tile.height}, but should be 1x1", pos)
|
||||
context.abort("Tile has width and height of ${tile.width}x${tile.height}, but should be 1x1", pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ class Recipe_Test {
|
||||
|
||||
val profile = GameProfile(UUID.fromString("f3c8d69b-0776-4512-8434-d1b2165909eb"), "dan200")
|
||||
|
||||
val tag = DataComponentPatch.builder().set(DataComponents.PROFILE, ResolvableProfile(profile)).build()
|
||||
val tag =
|
||||
DataComponentPatch.builder().set(DataComponents.PROFILE, ResolvableProfile.createResolved(profile))
|
||||
.build()
|
||||
assertEquals(tag, result.componentsPatch, "Expected NBT tags to be the same")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,7 +680,7 @@ class Turtle_Test {
|
||||
|
||||
val villager = helper.getEntity(EntityType.VILLAGER)
|
||||
val expectedY = helper.absolutePos(pos).y - 0.125
|
||||
if (villager.y < expectedY) helper.fail("Expected villager at y>=$expectedY, but at ${villager.y}", pos)
|
||||
if (villager.y < expectedY) helper.abort("Expected villager at y>=$expectedY, but at ${villager.y}", pos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +697,7 @@ class Turtle_Test {
|
||||
helper.assertEntityNotPresent(EntityType.SHEEP)
|
||||
val count = helper.getBlockEntity(turtlePos, TurtleBlockEntity::class.java)
|
||||
.countItem(Items.WHITE_WOOL)
|
||||
if (count == 0) helper.fail("Expected turtle to have white wool", turtlePos)
|
||||
if (count == 0) helper.abort("Expected turtle to have white wool", turtlePos)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ fun Minecraft.isRenderingStable(): Boolean = (this as MinecraftExtensions).`comp
|
||||
fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSequence {
|
||||
var future: CompletableFuture<Void>? = null
|
||||
thenExecute { future = Minecraft.getInstance().submit { task(ClientTestHelper()) } }
|
||||
thenWaitUntil { if (!future!!.isDone) fail("Not done task yet") }
|
||||
thenWaitUntil { if (!future!!.isDone) abort("Not done task yet") }
|
||||
thenExecute {
|
||||
try {
|
||||
future!!.get()
|
||||
@@ -59,10 +59,10 @@ fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = fal
|
||||
thenWaitUntil {
|
||||
if (Minecraft.getInstance().isRenderingStable()) {
|
||||
val idleFor = ++counter
|
||||
if (idleFor <= 20) fail("Only idle for $idleFor ticks")
|
||||
if (idleFor <= 20) abort("Only idle for $idleFor ticks")
|
||||
} else {
|
||||
counter = 0
|
||||
fail("Waiting for client to finish rendering")
|
||||
abort("Waiting for client to finish rendering")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = fal
|
||||
// Take a screenshot and wait for it to have finished.
|
||||
val hasScreenshot = AtomicBoolean()
|
||||
thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } }
|
||||
thenWaitUntil { if (!hasScreenshot.get()) fail("Screenshot does not exist") }
|
||||
thenWaitUntil { if (!hasScreenshot.get()) abort("Screenshot does not exist") }
|
||||
thenOnClient { minecraft.options.hideGui = false }
|
||||
|
||||
return this
|
||||
@@ -92,7 +92,7 @@ fun ServerPlayer.setupForTest() {
|
||||
*/
|
||||
fun GameTestHelper.positionAtArmorStand() {
|
||||
val stand = getEntity(EntityType.ARMOR_STAND)
|
||||
val player = level.randomPlayer ?: fail("Player does not exist")
|
||||
val player = level.randomPlayer ?: abort("Player does not exist")
|
||||
|
||||
player.setupForTest()
|
||||
player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot)
|
||||
@@ -103,7 +103,7 @@ fun GameTestHelper.positionAtArmorStand() {
|
||||
*/
|
||||
fun GameTestHelper.positionAt(pos: BlockPos, yRot: Float = 0.0f, xRot: Float = 0.0f) {
|
||||
val absolutePos = absolutePos(pos)
|
||||
val player = level.randomPlayer ?: fail("Player does not exist")
|
||||
val player = level.randomPlayer ?: abort("Player does not exist")
|
||||
|
||||
player.setupForTest()
|
||||
player.connection.teleport(absolutePos.x + 0.5, absolutePos.y + 0.5, absolutePos.z + 0.5, yRot, xRot)
|
||||
|
||||
@@ -97,11 +97,11 @@ fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = Compu
|
||||
|
||||
thenWaitUntil {
|
||||
val computer = ComputerState.get(label)
|
||||
if (computer == null || !computer.isDone(marker)) fail("Computer '$label' has not reached $marker yet.")
|
||||
if (computer == null || !computer.isDone(marker)) abort("Computer '$label' has not reached $marker yet.")
|
||||
}
|
||||
thenExecuteFailFast {
|
||||
val error = ComputerState.get(label)!!.check(marker)
|
||||
if (error != null) fail(error)
|
||||
if (error != null) abort(error)
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -128,7 +128,7 @@ fun GameTestSequence.thenOnComputer(name: String? = null, action: suspend LuaTas
|
||||
thenWaitUntil {
|
||||
if (!monitor!!.isFinished) {
|
||||
val runningFor = (test as GameTestInfoAccessor).`computercraft$getTick`() - self.lastTick
|
||||
fail("Computer '$label' has not finished yet (running for $runningFor ticks).")
|
||||
abort("Computer '$label' has not finished yet (running for $runningFor ticks).")
|
||||
}
|
||||
}
|
||||
thenExecuteFailFast { monitor!!.check() }
|
||||
@@ -155,25 +155,25 @@ fun GameTestHelper.immediate(run: () -> Unit) {
|
||||
// Helper functions for failing tests
|
||||
|
||||
/** Raise a [GameTestAssertException]. */
|
||||
fun GameTestHelper.fail(message: String): Nothing = throw assertionException(Component.literal(message))
|
||||
fun GameTestHelper.abort(message: String): Nothing = throw assertionException(Component.literal(message))
|
||||
|
||||
/** Raise a [GameTestAssertException] at a position. */
|
||||
fun GameTestHelper.fail(message: String, pos: BlockPos): Nothing =
|
||||
fun GameTestHelper.abort(message: String, pos: BlockPos): Nothing =
|
||||
throw assertionException(pos, Component.literal(message))
|
||||
|
||||
/** Assert a condition is true, or raise a [GameTestAssertException] if not. */
|
||||
fun GameTestHelper.assertTrue(condition: Boolean, message: String) = assertTrue(condition, Component.literal(message))
|
||||
|
||||
/** Raise a [GameTestAssertException]. */
|
||||
fun GameTestSequence.fail(message: String): Nothing =
|
||||
fun GameTestSequence.abort(message: String): Nothing =
|
||||
throw GameTestAssertException(
|
||||
Component.literal(message),
|
||||
((this as GameTestSequenceAccessor).parent as GameTestInfoAccessor).`computercraft$getTick`(),
|
||||
)
|
||||
|
||||
/** Fail with an optional context message. */
|
||||
private fun GameTestHelper.fail(message: String?, detail: String, pos: BlockPos): Nothing {
|
||||
fail(if (message.isNullOrEmpty()) detail else "$message: $detail", pos)
|
||||
private fun GameTestHelper.abort(message: String?, detail: String, pos: BlockPos): Nothing {
|
||||
abort(if (message.isNullOrEmpty()) detail else "$message: $detail", pos)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +186,7 @@ fun GameTestHelper.assertBlockIs(pos: BlockPos, predicate: (BlockState) -> Boole
|
||||
*/
|
||||
fun GameTestHelper.assertBlockIs(pos: BlockPos, predicate: (BlockState) -> Boolean, message: String) {
|
||||
val state = getBlockState(pos)
|
||||
if (!predicate(state)) fail(message, state.toString(), pos)
|
||||
if (!predicate(state)) abort(message, state.toString(), pos)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,9 +196,9 @@ fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: P
|
||||
val state = getBlockState(pos)
|
||||
if (!state.hasProperty(property)) {
|
||||
val id = RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK, state.block)
|
||||
fail(message, "block $id does not have property ${property.name}", pos)
|
||||
abort(message, "block $id does not have property ${property.name}", pos)
|
||||
} else if (state.getValue(property) != value) {
|
||||
fail(message, "${property.name} is ${state.getValue(property)}, expected $value", pos)
|
||||
abort(message, "${property.name} is ${state.getValue(property)}, expected $value", pos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@ fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: P
|
||||
fun GameTestHelper.getContainerAt(pos: BlockPos): Container =
|
||||
when (val container: BlockEntity? = level.getBlockEntity(absolutePos(pos))) {
|
||||
is Container -> container
|
||||
null -> fail("Expected a container at $pos, found nothing", pos)
|
||||
else -> fail("Expected a container at $pos, found ${getName(container.type)}", pos)
|
||||
null -> abort("Expected a container at $pos, found nothing", pos)
|
||||
else -> abort("Expected a container at $pos, found ${getName(container.type)}", pos)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +254,7 @@ private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container:
|
||||
|
||||
if (slot >= 0) {
|
||||
val invItems = (0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty }
|
||||
fail(
|
||||
abort(
|
||||
"""
|
||||
Items do not match (first mismatch at slot $slot).
|
||||
Expected: ${formatItems(items)}
|
||||
@@ -278,15 +278,15 @@ fun GameTestHelper.assertPeripheral(pos: BlockPos, direction: Direction = Direct
|
||||
val peripheral = getPeripheralAt(pos, direction)
|
||||
val block = getBlockState(pos).block.name.string
|
||||
when {
|
||||
peripheral == null -> fail("No peripheral for '$block'", pos)
|
||||
peripheral.type != type -> fail("Peripheral for '$block' is of type ${peripheral.type}, expected $type", pos)
|
||||
peripheral == null -> abort("No peripheral for '$block'", pos)
|
||||
peripheral.type != type -> abort("Peripheral for '$block' is of type ${peripheral.type}, expected $type", pos)
|
||||
}
|
||||
}
|
||||
|
||||
fun GameTestHelper.assertNoPeripheral(pos: BlockPos, direction: Direction = Direction.UP) {
|
||||
val peripheral = getPeripheralAt(pos, direction)
|
||||
val block = getBlockState(pos).block.name
|
||||
if (peripheral != null) fail("Expected no peripheral for '$block', got a ${peripheral.type}", pos)
|
||||
if (peripheral != null) abort("Expected no peripheral for '$block', got a ${peripheral.type}", pos)
|
||||
}
|
||||
|
||||
fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) {
|
||||
@@ -295,7 +295,7 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin
|
||||
if (!matcher.matches(actual)) {
|
||||
val description = StringDescription()
|
||||
matcher.describeMismatch(actual, description)
|
||||
fail(if (message.isNullOrEmpty()) description.toString() else "$message: $description")
|
||||
abort(if (message.isNullOrEmpty()) description.toString() else "$message: $description")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin
|
||||
fun GameTestHelper.assertItemEntityCountIs(expected: Item, count: Int) {
|
||||
val actualCount = getEntities(EntityType.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 }
|
||||
if (actualCount != count) {
|
||||
fail("Expected $count ${ItemStack(expected).itemName.string} items to exist (found $actualCount)")
|
||||
abort("Expected $count ${ItemStack(expected).itemName.string} items to exist (found $actualCount)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,9 +318,9 @@ private fun getName(type: BlockEntityType<*>): ResourceLocation =
|
||||
fun <T : Entity> GameTestHelper.getEntity(type: EntityType<T>): T {
|
||||
val entities = getEntities(type)
|
||||
when (entities.size) {
|
||||
0 -> fail("No $type entities")
|
||||
0 -> abort("No $type entities")
|
||||
1 -> return entities[0]
|
||||
else -> fail("Multiple $type entities (${entities.size} in bounding box)")
|
||||
else -> abort("Multiple $type entities (${entities.size} in bounding box)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ fun GameTestHelper.assertNotCraftable(vararg items: ItemStack) {
|
||||
|
||||
val recipe = level.server.recipeManager.getRecipeFor(RecipeType.CRAFTING, input, level)
|
||||
|
||||
if (recipe.isPresent) fail("Expected no recipe to match $items")
|
||||
if (recipe.isPresent) abort("Expected no recipe to match $items")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,7 +381,7 @@ fun GameTestHelper.craftItem(vararg items: ItemStack): ItemStack {
|
||||
val input = CraftingInput.of(3, 3, container)
|
||||
|
||||
val recipe = level.server.recipeManager.getRecipeFor(RecipeType.CRAFTING, input, level).getOrNull()
|
||||
?: fail("No recipe matches $items")
|
||||
?: throw assertionException("No recipe matches $items")
|
||||
return recipe.value.assemble(input, level.registryAccess())
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ object TestHooks {
|
||||
}
|
||||
|
||||
fun getTestOrigin(server: MinecraftServer): BlockPos {
|
||||
val spawn = server.overworld().sharedSpawnPos
|
||||
val spawn = server.respawnData.pos()
|
||||
return BlockPos(spawn.x, -59, spawn.y)
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
modCompileOnly(libs.bundles.externalMods.fabric.compile) {
|
||||
exclude("net.fabricmc", "fabric-loader")
|
||||
exclude("net.fabricmc.fabric-api")
|
||||
|
||||
@@ -12,7 +12,7 @@ import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
|
||||
import dan200.computercraft.client.platform.ClientNetworkContextImpl;
|
||||
import dan200.computercraft.client.platform.FabricModelKey;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.client.render.BlockOutlineRenderer;
|
||||
import dan200.computercraft.shared.ComputerCraft;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.config.ConfigSpec;
|
||||
@@ -25,12 +25,14 @@ import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlu
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedExtraModel;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.BlockRenderLayerMap;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.SpecialGuiElementRegistry;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.item.ItemTintSources;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
|
||||
import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
|
||||
import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
@@ -49,8 +51,6 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
|
||||
public class ComputerCraftClient {
|
||||
public static void init() {
|
||||
var clientNetwork = new ClientNetworkContextImpl();
|
||||
@@ -67,9 +67,10 @@ public class ComputerCraftClient {
|
||||
ClientRegistry.registerItemColours(ItemTintSources.ID_MAPPER::put);
|
||||
ClientRegistry.registerSelectItemProperties(SelectItemModelProperties.ID_MAPPER::put);
|
||||
ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put);
|
||||
ClientRegistry.registerLayerDefinitions((id, factory) -> EntityModelLayerRegistry.registerModelLayer(id, factory::get));
|
||||
|
||||
PreparableModelLoadingPlugin.register(
|
||||
ClientRegistry::gatherExtraModels,
|
||||
(state, executor) -> ClientRegistry.gatherExtraModels(state.resourceManager(), executor),
|
||||
(state, context) -> ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() {
|
||||
@Override
|
||||
public <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake) {
|
||||
@@ -93,16 +94,23 @@ public class ComputerCraftClient {
|
||||
|
||||
ClientTickEvents.START_CLIENT_TICK.register(client -> ClientHooks.onTick());
|
||||
// This isn't 100% consistent with Forge, but not worth a mixin.
|
||||
WorldRenderEvents.START.register(context -> ClientHooks.onRenderTick());
|
||||
WorldRenderEvents.BLOCK_OUTLINE.register((context, hitResult) -> {
|
||||
WorldRenderEvents.START_MAIN.register(context -> ClientHooks.onRenderTick());
|
||||
WorldRenderEvents.BEFORE_BLOCK_OUTLINE.register((context, hitResult) -> {
|
||||
var hit = Minecraft.getInstance().hitResult;
|
||||
if (hit instanceof BlockHitResult blockHit && blockHit.getBlockPos().equals(hitResult.blockPos())) {
|
||||
return !ClientHooks.drawHighlight(Nullability.assertNonNull(context.matrixStack()), assertNonNull(context.consumers()), context.camera(), blockHit);
|
||||
} else {
|
||||
if (!(hit instanceof BlockHitResult blockHit) || !blockHit.getBlockPos().equals(hitResult.pos())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var camera = context.gameRenderer().getMainCamera();
|
||||
var renderer = ClientHooks.drawHighlight(camera, blockHit);
|
||||
if (renderer == null) return true;
|
||||
|
||||
BlockOutlineRenderer.render(context.matrices(), context.consumers(), renderer);
|
||||
return false;
|
||||
});
|
||||
|
||||
ClientRegistry.registerDebugScreenEntries(DebugScreenEntries::register);
|
||||
|
||||
// Register our open folder command
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
|
||||
dispatcher.register(LiteralArgumentBuilder.<FabricClientCommandSource>literal(ComputerCraft.CLIENT_OPEN_FOLDER)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.mixin.client;
|
||||
|
||||
import dan200.computercraft.client.ClientHooks;
|
||||
import net.minecraft.client.gui.components.DebugScreenOverlay;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(DebugScreenOverlay.class)
|
||||
class DebugScreenOverlayMixin {
|
||||
@Inject(method = "getSystemInformation", at = @At("RETURN"))
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void appendBlockDebugInfo(CallbackInfoReturnable<List<String>> cir) {
|
||||
ClientHooks.addBlockDebugInfo(cir.getReturnValue()::add);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,10 @@ package dan200.computercraft.mixin.client;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.client.ClientHooks;
|
||||
import dan200.computercraft.client.ExtendedItemFrameRenderStateHolder;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.client.renderer.state.CameraRenderState;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -21,14 +22,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
class ItemFrameRendererMixin {
|
||||
@Inject(
|
||||
method = "render(Lnet/minecraft/client/renderer/entity/state/ItemFrameRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
|
||||
method = "submit(Lnet/minecraft/client/renderer/entity/state/ItemFrameRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/CameraRenderState;)V",
|
||||
at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/state/ItemFrameRenderState;mapId:Lnet/minecraft/world/level/saveddata/maps/MapId;", opcode = Opcodes.GETFIELD, ordinal = 1),
|
||||
cancellable = true
|
||||
)
|
||||
@SuppressWarnings("unused")
|
||||
private void render(ItemFrameRenderState frame, PoseStack pose, MultiBufferSource buffers, int light, CallbackInfo ci) {
|
||||
private void submit(ItemFrameRenderState frame, PoseStack pose, SubmitNodeCollector buffers, CameraRenderState camera, CallbackInfo ci) {
|
||||
var state = ((ExtendedItemFrameRenderStateHolder) frame).computercraft$state();
|
||||
if (ClientHooks.onRenderItemFrame(pose, buffers, frame, state, light)) {
|
||||
if (ClientHooks.onRenderItemFrame(pose, buffers, frame, state)) {
|
||||
ci.cancel();
|
||||
pose.popPose();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.client.ClientHooks;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.ItemInHandRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -22,9 +22,9 @@ class ItemInHandRendererMixin {
|
||||
@SuppressWarnings("unused")
|
||||
private void onRenderItem(
|
||||
AbstractClientPlayer player, float partialTicks, float pitch, InteractionHand hand, float swingProgress, ItemStack stack,
|
||||
float equippedProgress, PoseStack transform, MultiBufferSource buffer, int combinedLight, CallbackInfo ci
|
||||
float equippedProgress, PoseStack transform, SubmitNodeCollector collector, int combinedLight, CallbackInfo ci
|
||||
) {
|
||||
if (ClientHooks.onRenderHeldItem(transform, buffer, combinedLight, hand, pitch, equippedProgress, swingProgress, stack)) {
|
||||
if (ClientHooks.onRenderHeldItem(transform, collector, combinedLight, hand, pitch, equippedProgress, swingProgress, stack)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"client": [
|
||||
"DebugScreenOverlayMixin",
|
||||
"ItemFrameRendererMixin",
|
||||
"ItemFrameRenderStateMixin",
|
||||
"ItemInHandRendererMixin",
|
||||
|
||||
@@ -40,18 +40,14 @@ import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup;
|
||||
import net.fabricmc.fabric.api.loot.v3.LootTableEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
import net.fabricmc.fabric.api.resource.v1.ResourceLoader;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ChunkLevel;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@@ -59,8 +55,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ComputerCraft {
|
||||
@@ -137,7 +131,7 @@ public class ComputerCraft {
|
||||
entries.getContext(), entries
|
||||
));
|
||||
|
||||
CommonHooks.onDatapackReload((name, listener) -> ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new ReloadListener(name, listener)));
|
||||
CommonHooks.onDatapackReload(ResourceLoader.get(PackType.SERVER_DATA)::registerReloader);
|
||||
|
||||
FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);
|
||||
|
||||
@@ -152,20 +146,6 @@ public class ComputerCraft {
|
||||
registry.register(type.type(), type.codec());
|
||||
}
|
||||
|
||||
private record ReloadListener(ResourceLocation name, PreparableReloadListener listener)
|
||||
implements IdentifiableResourceReloadListener {
|
||||
|
||||
@Override
|
||||
public ResourceLocation getFabricId() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> reload(PreparationBarrier preparationBarrier, ResourceManager resourceManager, Executor backgroundExecutor, Executor gameExecutor) {
|
||||
return listener.reload(preparationBarrier, resourceManager, backgroundExecutor, gameExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
private record BlockComponentImpl<T, C extends @Nullable Object>(
|
||||
BlockApiLookup<T, C> lookup
|
||||
) implements ModRegistry.BlockComponent<T, C> {
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
],
|
||||
"rei_client": [
|
||||
"dan200.computercraft.client.integration.rei.REIComputerCraftClient"
|
||||
],
|
||||
"emi": [
|
||||
"dan200.computercraft.client.integration.emi.EMIComputerCraft"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
@@ -49,9 +46,9 @@
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.16.14",
|
||||
"fabric-api": ">=0.128.0",
|
||||
"minecraft": "=1.21.8"
|
||||
"fabricloader": ">=0.17.3",
|
||||
"fabric-api": ">=0.135.0",
|
||||
"minecraft": "=1.21.10"
|
||||
},
|
||||
"accessWidener": "computercraft.accesswidener"
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ neoForge {
|
||||
|
||||
register("client") {
|
||||
client()
|
||||
sourceSet = sourceSets.client
|
||||
}
|
||||
|
||||
register("server") {
|
||||
@@ -77,6 +78,7 @@ neoForge {
|
||||
register("data") {
|
||||
configureForData("computercraft", sourceSets.main.get())
|
||||
loadedMods = listOf(computercraftDatagen.get())
|
||||
sourceSet = sourceSets.datagen
|
||||
}
|
||||
|
||||
fun RunModel.configureForGameTest() {
|
||||
@@ -87,6 +89,7 @@ neoForge {
|
||||
|
||||
programArgument("--mixin.config=computercraft-gametest.mixins.json")
|
||||
loadedMods.add(testMod)
|
||||
sourceSet = sourceSets.testMod
|
||||
|
||||
jvmArgument("-ea")
|
||||
}
|
||||
@@ -119,24 +122,12 @@ neoForge {
|
||||
register("exampleData") {
|
||||
configureForData("examplemod", sourceSets.examples.get())
|
||||
loadedMods.add(exampleMod.get())
|
||||
sourceSet = sourceSets.examples
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
additionalRuntimeClasspath { extendsFrom(jarJar.get()) }
|
||||
|
||||
val testAdditionalRuntimeClasspath by registering {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
// Prevent ending up with multiple versions of libraries on the classpath.
|
||||
shouldResolveConsistentlyWith(additionalRuntimeClasspath.get())
|
||||
}
|
||||
|
||||
for (testConfig in listOf("testClientAdditionalRuntimeClasspath", "gametestAdditionalRuntimeClasspath")) {
|
||||
named(testConfig) { extendsFrom(testAdditionalRuntimeClasspath.get()) }
|
||||
}
|
||||
|
||||
register("testWithIris") {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
@@ -156,7 +147,6 @@ dependencies {
|
||||
compileOnly(libs.jetbrainsAnnotations)
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
compileOnly(libs.bundles.externalMods.forge.compile)
|
||||
clientRuntimeOnly(libs.bundles.externalMods.forge.runtime)
|
||||
compileOnly(libs.create.forge) { isTransitive = false }
|
||||
@@ -168,11 +158,6 @@ dependencies {
|
||||
|
||||
jarJar(libs.cobalt)
|
||||
jarJar(libs.jzlib)
|
||||
// We don't jar-in-jar our additional netty dependencies (see the tasks.jarJar configuration), but still want them
|
||||
// on the legacy classpath.
|
||||
additionalRuntimeClasspath(libs.netty.http) { isTransitive = false }
|
||||
additionalRuntimeClasspath(libs.netty.socks) { isTransitive = false }
|
||||
additionalRuntimeClasspath(libs.netty.proxy) { isTransitive = false }
|
||||
|
||||
testFixturesApi(libs.bundles.test)
|
||||
testFixturesApi(libs.bundles.kotlin)
|
||||
@@ -185,10 +170,6 @@ dependencies {
|
||||
testModImplementation(testFixtures(project(":core")))
|
||||
testModImplementation(testFixtures(project(":forge")))
|
||||
|
||||
// Ensure our test fixture dependencies are on the classpath
|
||||
"testAdditionalRuntimeClasspath"(libs.bundles.kotlin)
|
||||
"testAdditionalRuntimeClasspath"(libs.bundles.test)
|
||||
|
||||
testFixturesImplementation(testFixtures(project(":core")))
|
||||
|
||||
"testWithIris"(libs.iris.forge)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.BlockOutlineRenderer;
|
||||
import dan200.computercraft.client.sound.SpeakerSound;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
@@ -44,21 +45,20 @@ public final class ForgeClientHooks {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void drawHighlight(RenderHighlightEvent.Block event) {
|
||||
if (ClientHooks.drawHighlight(event.getPoseStack(), event.getMultiBufferSource(), event.getCamera(), event.getTarget())) {
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
public static void drawHighlight(ExtractBlockOutlineRenderStateEvent event) {
|
||||
var renderer = ClientHooks.drawHighlight(event.getCamera(), event.getHitResult());
|
||||
if (renderer == null) return;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderText(CustomizeGuiOverlayEvent.DebugText event) {
|
||||
ClientHooks.addBlockDebugInfo(event.getRight()::add);
|
||||
event.addCustomRenderer((state, buffers, transform, translucentPass, renderState) -> {
|
||||
BlockOutlineRenderer.render(transform, buffers, renderer);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderInHand(RenderHandEvent event) {
|
||||
if (ClientHooks.onRenderHeldItem(
|
||||
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
|
||||
event.getPoseStack(), event.getSubmitNodeCollector(), event.getPackedLight(),
|
||||
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
|
||||
)) {
|
||||
event.setCanceled(true);
|
||||
@@ -70,7 +70,7 @@ public final class ForgeClientHooks {
|
||||
public static void onRenderInFrame(RenderItemInFrameEvent event) {
|
||||
var state = event.getItemFrameRenderState().getRenderData(ITEM_FRAME_STATE);
|
||||
if (state != null && ClientHooks.onRenderItemFrame(
|
||||
event.getPoseStack(), event.getMultiBufferSource(), event.getItemFrameRenderState(), state, event.getPackedLight()
|
||||
event.getPoseStack(), event.getSubmitNodeCollector(), event.getItemFrameRenderState(), state
|
||||
)) {
|
||||
event.setCanceled(true);
|
||||
}
|
||||
|
||||
@@ -107,11 +107,21 @@ public final class ForgeClientRegistry {
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
|
||||
ClientRegistry.registerLayerDefinitions(event::registerLayerDefinition);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerPictureInPictureRenderers(RegisterPictureInPictureRenderersEvent event) {
|
||||
ClientRegistry.registerPictureInPictureRenderers(event::register);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerDebugScreenEntries(RegisterDebugEntriesEvent event) {
|
||||
ClientRegistry.registerDebugScreenEntries(event::register);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void setupClient(FMLClientSetupEvent event) {
|
||||
ClientRegistry.register();
|
||||
|
||||
@@ -12,6 +12,7 @@ import dan200.computercraft.api.network.wired.WiredElementCapability;
|
||||
import dan200.computercraft.api.peripheral.PeripheralCapability;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.impl.Peripherals;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
@@ -51,13 +52,13 @@ import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.capabilities.ItemCapability;
|
||||
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
|
||||
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent;
|
||||
import net.neoforged.neoforge.items.wrapper.InvWrapper;
|
||||
import net.neoforged.neoforge.items.wrapper.SidedInvWrapper;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
import net.neoforged.neoforge.registries.DataPackRegistryEvent;
|
||||
import net.neoforged.neoforge.registries.NewRegistryEvent;
|
||||
import net.neoforged.neoforge.registries.RegistryBuilder;
|
||||
import net.neoforged.neoforge.transfer.item.VanillaContainerWrapper;
|
||||
import net.neoforged.neoforge.transfer.item.WorldlyContainerWrapper;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -112,9 +113,9 @@ public final class ComputerCraft {
|
||||
ComputerCraftAPI.registerGenericSource(new FluidMethods());
|
||||
ComputerCraftAPI.registerGenericSource(new EnergyMethods());
|
||||
|
||||
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.ItemHandler.BLOCK);
|
||||
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.FluidHandler.BLOCK);
|
||||
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.EnergyStorage.BLOCK);
|
||||
Peripherals.addGenericLookup(InventoryMethods::extractContainer);
|
||||
Peripherals.addGenericLookup(FluidMethods::extractContainer);
|
||||
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Energy.BLOCK);
|
||||
|
||||
ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill);
|
||||
|
||||
@@ -155,12 +156,12 @@ public final class ComputerCraft {
|
||||
ModRegistry.BlockEntities.DISK_DRIVE
|
||||
);
|
||||
for (var inv : unsidedContainers) {
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, inv.get(), (be, side) -> new InvWrapper(be));
|
||||
event.registerBlockEntity(Capabilities.Item.BLOCK, inv.get(), (be, side) -> VanillaContainerWrapper.of(be));
|
||||
}
|
||||
|
||||
event.registerBlockEntity(
|
||||
Capabilities.ItemHandler.BLOCK, ModRegistry.BlockEntities.PRINTER.get(),
|
||||
(be, side) -> side == null ? new InvWrapper(be) : new SidedInvWrapper(be, side)
|
||||
Capabilities.Item.BLOCK, ModRegistry.BlockEntities.PRINTER.get(),
|
||||
(be, side) -> side == null ? VanillaContainerWrapper.of(be) : new WorldlyContainerWrapper(be, side)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import net.neoforged.neoforge.energy.IEnergyStorage;
|
||||
import net.neoforged.neoforge.transfer.energy.EnergyHandler;
|
||||
|
||||
/**
|
||||
* Fluid methods for Forge's {@link IEnergyStorage}.
|
||||
* Fluid methods for Forge's {@link EnergyHandler}.
|
||||
*/
|
||||
public final class EnergyMethods extends AbstractEnergyMethods<IEnergyStorage> {
|
||||
public final class EnergyMethods extends AbstractEnergyMethods<EnergyHandler> {
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int getEnergy(IEnergyStorage energy) {
|
||||
return energy.getEnergyStored();
|
||||
public long getEnergy(EnergyHandler energy) {
|
||||
return energy.getAmountAsLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int getEnergyCapacity(IEnergyStorage energy) {
|
||||
return energy.getMaxEnergyStored();
|
||||
public long getEnergyCapacity(EnergyHandler energy) {
|
||||
return energy.getCapacityAsLong();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,31 +10,41 @@ import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import net.neoforged.neoforge.transfer.ResourceHandler;
|
||||
import net.neoforged.neoforge.transfer.ResourceHandlerUtil;
|
||||
import net.neoforged.neoforge.transfer.fluid.FluidResource;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static dan200.computercraft.shared.util.ArgumentHelpers.getRegistryEntry;
|
||||
|
||||
/**
|
||||
* Fluid methods for Forge's {@link IFluidHandler}.
|
||||
* Fluid methods for Forge's fluid {@link ResourceHandler}.
|
||||
*/
|
||||
public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
public final class FluidMethods extends AbstractFluidMethods<FluidMethods.StorageWrapper> {
|
||||
public record StorageWrapper(ResourceHandler<FluidResource> storage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<Integer, Map<String, ?>> tanks(IFluidHandler fluids) {
|
||||
public Map<Integer, Map<String, ?>> tanks(FluidMethods.StorageWrapper wrapper) {
|
||||
var storage = wrapper.storage();
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
var size = fluids.getTanks();
|
||||
var size = storage.size();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var stack = fluids.getFluidInTank(i);
|
||||
var stack = storage.getResource(i).toStack(storage.getAmountAsInt(i));
|
||||
if (!stack.isEmpty()) result.put(i + 1, ForgeDetailRegistries.FLUID_STACK.getBasicDetails(stack));
|
||||
}
|
||||
|
||||
@@ -44,7 +54,7 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pushFluid(
|
||||
IFluidHandler from, IComputerAccess computer,
|
||||
FluidMethods.StorageWrapper from, IComputerAccess computer,
|
||||
String toName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException {
|
||||
var fluid = fluidName.isPresent()
|
||||
@@ -61,15 +71,13 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
if (actualLimit <= 0) throw new LuaException("Limit must be > 0");
|
||||
|
||||
return fluid == null
|
||||
? moveFluid(from, actualLimit, to)
|
||||
: moveFluid(from, new FluidStack(fluid, actualLimit), to);
|
||||
return moveFluid(from.storage(), to, fluid, actualLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pullFluid(
|
||||
IFluidHandler to, IComputerAccess computer,
|
||||
FluidMethods.StorageWrapper to, IComputerAccess computer,
|
||||
String fromName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException {
|
||||
var fluid = fluidName.isPresent()
|
||||
@@ -86,13 +94,16 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
if (actualLimit <= 0) throw new LuaException("Limit must be > 0");
|
||||
|
||||
return fluid == null
|
||||
? moveFluid(from, actualLimit, to)
|
||||
: moveFluid(from, new FluidStack(fluid, actualLimit), to);
|
||||
return moveFluid(from, to.storage(), fluid, actualLimit);
|
||||
}
|
||||
|
||||
public static @Nullable StorageWrapper extractContainer(ServerLevel level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
|
||||
var storage = CapabilityUtil.getCapability(level, Capabilities.Fluid.BLOCK, pos, state, blockEntity, direction);
|
||||
return storage == null ? null : new StorageWrapper(storage);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IFluidHandler extractHandler(IPeripheral peripheral) {
|
||||
private static ResourceHandler<FluidResource> extractHandler(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
@@ -102,11 +113,10 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
var level = blockEntity.getLevel();
|
||||
if (!(level instanceof ServerLevel serverLevel)) return null;
|
||||
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.FluidHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.Fluid.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
if (object instanceof IFluidHandler handler) return handler;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -114,49 +124,14 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param fluid The fluid to extract.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid(IFluidHandler from, int limit, IFluidHandler to) {
|
||||
return moveFluid(from, from.drain(limit, IFluidHandler.FluidAction.SIMULATE), limit, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param fluid The fluid and limit to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid(IFluidHandler from, FluidStack fluid, IFluidHandler to) {
|
||||
return moveFluid(from, from.drain(fluid, IFluidHandler.FluidAction.SIMULATE), fluid.getAmount(), to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param extracted The fluid which is extracted from {@code from}.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid(IFluidHandler from, FluidStack extracted, int limit, IFluidHandler to) {
|
||||
if (extracted.getAmount() <= 0) return 0;
|
||||
|
||||
// Limit the amount to extract.
|
||||
extracted = extracted.copy();
|
||||
extracted.setAmount(Math.min(extracted.getAmount(), limit));
|
||||
|
||||
var inserted = to.fill(extracted.copy(), IFluidHandler.FluidAction.EXECUTE);
|
||||
if (inserted <= 0) return 0;
|
||||
|
||||
// Remove the item from the original inventory. Technically this could fail, but there's little we can do
|
||||
// about that.
|
||||
extracted.setAmount(inserted);
|
||||
from.drain(extracted, IFluidHandler.FluidAction.EXECUTE);
|
||||
return inserted;
|
||||
private static int moveFluid(ResourceHandler<FluidResource> from, ResourceHandler<FluidResource> to, @Nullable Fluid fluid, int limit) {
|
||||
Predicate<FluidResource> predicate = fluid == null ? x -> true : x -> x.is(fluid);
|
||||
var moved = ResourceHandlerUtil.moveFirst(from, to, predicate, limit, null);
|
||||
return moved == null ? 0 : moved.amount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,17 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.items.IItemHandler;
|
||||
import net.neoforged.neoforge.items.wrapper.InvWrapper;
|
||||
import net.neoforged.neoforge.transfer.ResourceHandler;
|
||||
import net.neoforged.neoforge.transfer.item.ItemResource;
|
||||
import net.neoforged.neoforge.transfer.item.VanillaContainerWrapper;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -26,22 +31,26 @@ import java.util.Optional;
|
||||
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
|
||||
|
||||
/**
|
||||
* Inventory methods for Forge's {@link IItemHandler}.
|
||||
* Inventory methods for Forge's {@link ResourceHandler}.
|
||||
*/
|
||||
public final class InventoryMethods extends AbstractInventoryMethods<IItemHandler> {
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int size(IItemHandler inventory) {
|
||||
return inventory.getSlots();
|
||||
public final class InventoryMethods extends AbstractInventoryMethods<InventoryMethods.StorageWrapper> {
|
||||
public record StorageWrapper(ResourceHandler<ItemResource> storage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
|
||||
public int size(StorageWrapper inventory) {
|
||||
return inventory.storage().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<Integer, Map<String, ?>> list(StorageWrapper wrapper) {
|
||||
var storage = wrapper.storage();
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
var size = inventory.getSlots();
|
||||
var size = storage.size();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var stack = inventory.getStackInSlot(i);
|
||||
var stack = storage.getResource(i).toStack(storage.getAmountAsInt(i));
|
||||
if (!stack.isEmpty()) result.put(i + 1, VanillaDetailRegistries.ITEM_STACK.getBasicDetails(stack));
|
||||
}
|
||||
|
||||
@@ -51,24 +60,29 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
@Override
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
|
||||
public Map<String, ?> getItemDetail(StorageWrapper wrapper, int slot) throws LuaException {
|
||||
var storage = wrapper.storage();
|
||||
assertBetween(slot, 1, storage.size(), "Slot out of range (%s)");
|
||||
|
||||
var stack = inventory.getStackInSlot(slot - 1);
|
||||
var stack = storage.getResource(slot - 1).toStack(storage.getAmountAsInt(slot - 1));
|
||||
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public long getItemLimit(IItemHandler inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
|
||||
return inventory.getSlotLimit(slot - 1);
|
||||
public long getItemLimit(StorageWrapper wrapper, int slot) throws LuaException {
|
||||
var storage = wrapper.storage();
|
||||
assertBetween(slot, 1, storage.size(), "Slot out of range (%s)");
|
||||
|
||||
// FIXME: The capacity will be 0 if the resource is empty (or not valid). If empty, we try with dirt.
|
||||
var item = storage.getResource(slot - 1);
|
||||
return storage.getCapacityAsLong(slot - 1, item.isEmpty() ? ItemResource.of(Items.DIRT) : item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pushItems(
|
||||
IItemHandler from, IComputerAccess computer,
|
||||
StorageWrapper from, IComputerAccess computer,
|
||||
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
@@ -80,17 +94,17 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
assertBetween(fromSlot, 1, from.getSlots(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)");
|
||||
assertBetween(fromSlot, 1, from.storage().size(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.size(), "To slot out of range (%s)");
|
||||
|
||||
if (actualLimit <= 0) return 0;
|
||||
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
|
||||
return moveItem(from.storage(), fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pullItems(
|
||||
IItemHandler to, IComputerAccess computer,
|
||||
StorageWrapper to, IComputerAccess computer,
|
||||
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
@@ -102,15 +116,20 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
assertBetween(fromSlot, 1, from.getSlots(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)");
|
||||
assertBetween(fromSlot, 1, from.size(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.storage().size(), "To slot out of range (%s)");
|
||||
|
||||
if (actualLimit <= 0) return 0;
|
||||
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
|
||||
return moveItem(from, fromSlot - 1, to.storage(), toSlot.orElse(0) - 1, actualLimit);
|
||||
}
|
||||
|
||||
public static @Nullable StorageWrapper extractContainer(ServerLevel level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
|
||||
var storage = CapabilityUtil.getCapability(level, Capabilities.Item.BLOCK, pos, state, blockEntity, direction);
|
||||
return storage == null ? null : new StorageWrapper(storage);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IItemHandler extractHandler(IPeripheral peripheral) {
|
||||
private static ResourceHandler<ItemResource> extractHandler(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
@@ -120,12 +139,11 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
var level = blockEntity.getLevel();
|
||||
if (!(level instanceof ServerLevel serverLevel)) return null;
|
||||
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.ItemHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.Item.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
if (object instanceof IItemHandler handler) return handler;
|
||||
if (object instanceof Container container) return new InvWrapper(container);
|
||||
if (object instanceof Container container) return VanillaContainerWrapper.of(container);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -139,7 +157,7 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
* @param limit The max number to move. {@link Integer#MAX_VALUE} for no limit.
|
||||
* @return The number of items moved.
|
||||
*/
|
||||
private static int moveItem(IItemHandler from, int fromSlot, IItemHandler to, int toSlot, final int limit) {
|
||||
private static int moveItem(ResourceHandler<ItemResource> from, int fromSlot, ResourceHandler<ItemResource> to, int toSlot, final int limit) {
|
||||
var fromWrapper = new ForgeContainerTransfer(from).singleSlot(fromSlot);
|
||||
var toWrapper = new ForgeContainerTransfer(to);
|
||||
if (toSlot >= 0) toWrapper = toWrapper.singleSlot(toSlot);
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.entity.Pose;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -35,11 +34,6 @@ class FakePlayerExt extends FakePlayer {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startRiding(Entity vehicle, boolean force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityDimensions getDefaultDimensions(Pose pose) {
|
||||
return DIMENSIONS;
|
||||
|
||||
@@ -4,20 +4,21 @@
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.items.IItemHandler;
|
||||
import net.neoforged.neoforge.transfer.ResourceHandler;
|
||||
import net.neoforged.neoforge.transfer.item.ItemResource;
|
||||
import net.neoforged.neoforge.transfer.transaction.Transaction;
|
||||
|
||||
public class ForgeContainerTransfer implements ContainerTransfer.Slotted {
|
||||
private final IItemHandler handler;
|
||||
private final ResourceHandler<ItemResource> handler;
|
||||
private final int offset;
|
||||
private final int limit;
|
||||
private final int slots;
|
||||
|
||||
public ForgeContainerTransfer(IItemHandler handler) {
|
||||
this(handler, 0, handler.getSlots(), handler.getSlots());
|
||||
public ForgeContainerTransfer(ResourceHandler<ItemResource> handler) {
|
||||
this(handler, 0, handler.size(), handler.size());
|
||||
}
|
||||
|
||||
public ForgeContainerTransfer(IItemHandler handler, int offset, int limit, int slots) {
|
||||
public ForgeContainerTransfer(ResourceHandler<ItemResource> handler, int offset, int limit, int slots) {
|
||||
this.handler = handler;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
@@ -48,43 +49,45 @@ public class ForgeContainerTransfer implements ContainerTransfer.Slotted {
|
||||
}
|
||||
|
||||
public static int moveItem(ForgeContainerTransfer src, ForgeContainerTransfer dest, int maxAmount) {
|
||||
var targetSlot = 0;
|
||||
var hasItem = false;
|
||||
|
||||
var movedStack = ItemStack.EMPTY;
|
||||
var moved = 0;
|
||||
|
||||
outer:
|
||||
for (var srcSlot = 0; srcSlot < src.limit; srcSlot++) {
|
||||
var actualSrcSlot = src.mapSlot(srcSlot);
|
||||
var stack = src.handler.extractItem(actualSrcSlot, maxAmount, true);
|
||||
if (stack.isEmpty()) continue;
|
||||
var resource = src.handler.getResource(actualSrcSlot);
|
||||
if (resource.isEmpty()) continue;
|
||||
|
||||
// Pick the first item in the inventory to be the one we transfer, skipping those that match.
|
||||
if (movedStack.isEmpty()) {
|
||||
movedStack = stack.copy();
|
||||
if (stack.getMaxStackSize() < maxAmount) maxAmount = stack.getMaxStackSize();
|
||||
} else if (!ItemStack.isSameItemSameComponents(stack, movedStack)) {
|
||||
continue;
|
||||
// Check how much can be extracted and inserted.
|
||||
int maxExtracted;
|
||||
try (var transaction = Transaction.openRoot()) {
|
||||
maxExtracted = src.handler.extract(actualSrcSlot, resource, maxAmount, transaction);
|
||||
}
|
||||
if (maxExtracted == 0) continue;
|
||||
|
||||
hasItem = true;
|
||||
|
||||
try (var transaction = Transaction.openRoot()) {
|
||||
// check how much can be inserted
|
||||
var accepted = dest.insert(resource, maxExtracted, transaction);
|
||||
if (accepted == 0) continue;
|
||||
|
||||
// Extract or rollback.
|
||||
if (src.handler.extract(actualSrcSlot, resource, accepted, transaction) == accepted) {
|
||||
transaction.commit();
|
||||
return accepted;
|
||||
}
|
||||
}
|
||||
|
||||
for (; targetSlot < dest.limit; targetSlot++) {
|
||||
var oldCount = stack.getCount();
|
||||
stack = dest.handler.insertItem(dest.mapSlot(targetSlot), stack, false);
|
||||
|
||||
var transferred = oldCount - stack.getCount();
|
||||
var extracted = src.handler.extractItem(actualSrcSlot, transferred, false);
|
||||
|
||||
moved += transferred;
|
||||
|
||||
// We failed to extract as much as we should have. This should never happen, but goodness knows.
|
||||
if (extracted.getCount() < transferred) break outer;
|
||||
|
||||
if (moved >= maxAmount) return moved;
|
||||
if (stack.isEmpty()) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (moved == 0) return movedStack.isEmpty() ? NO_ITEMS : NO_SPACE;
|
||||
return moved;
|
||||
return hasItem ? NO_SPACE : NO_ITEMS;
|
||||
}
|
||||
|
||||
private int insert(ItemResource item, int amount, Transaction transaction) {
|
||||
var inserted = 0;
|
||||
for (var i = 0; i < limit; i++) {
|
||||
inserted += handler.insert(mapSlot(i), item, amount - inserted, transaction);
|
||||
if (inserted == amount) break;
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ import net.neoforged.neoforge.common.ItemAbilities;
|
||||
import net.neoforged.neoforge.common.Tags;
|
||||
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
|
||||
import net.neoforged.neoforge.event.EventHooks;
|
||||
import net.neoforged.neoforge.items.wrapper.InvWrapper;
|
||||
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.neoforged.neoforge.transfer.item.VanillaContainerWrapper;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -128,17 +128,17 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrapContainer(Container container) {
|
||||
return new ForgeContainerTransfer(new InvWrapper(container));
|
||||
return new ForgeContainerTransfer(VanillaContainerWrapper.of(container));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var inventory = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, side);
|
||||
var inventory = level.getCapability(Capabilities.Item.BLOCK, pos, side);
|
||||
if (inventory != null) return new ForgeContainerTransfer(inventory);
|
||||
|
||||
var entity = InventoryUtil.getEntityContainer(level, pos, side);
|
||||
return entity == null ? null : new ForgeContainerTransfer(new InvWrapper(entity));
|
||||
return entity == null ? null : new ForgeContainerTransfer(VanillaContainerWrapper.of(entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,8 +7,8 @@ public net.minecraft.client.gui.components.ChatComponent allMessages
|
||||
|
||||
# ItemPocketRenderer/ItemPrintoutRenderer
|
||||
public net.minecraft.client.renderer.ItemInHandRenderer calculateMapTilt(F)F
|
||||
public net.minecraft.client.renderer.ItemInHandRenderer renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/entity/HumanoidArm;)V
|
||||
public net.minecraft.client.renderer.ItemInHandRenderer renderPlayerArm(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFFLnet/minecraft/world/entity/HumanoidArm;)V
|
||||
public net.minecraft.client.renderer.ItemInHandRenderer renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V
|
||||
public net.minecraft.client.renderer.ItemInHandRenderer renderPlayerArm(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFFLnet/minecraft/world/entity/HumanoidArm;)V
|
||||
|
||||
# SpeakerInstance/SpeakerManager
|
||||
public com.mojang.blaze3d.audio.Channel pumpBuffers(I)V
|
||||
|
||||
@@ -26,7 +26,7 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
|
||||
[[dependencies.computercraft]]
|
||||
modId="neoforge"
|
||||
type="required"
|
||||
versionRange="[${neoVersion},21.9)"
|
||||
versionRange="[${neoVersion},21.11)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ package dan200.computercraft.shared.platform;
|
||||
import dan200.computercraft.test.shared.WithMinecraft;
|
||||
import dan200.computercraft.test.shared.platform.ContainerTransferContract;
|
||||
import net.minecraft.world.Container;
|
||||
import net.neoforged.neoforge.items.wrapper.InvWrapper;
|
||||
import net.neoforged.neoforge.transfer.item.VanillaContainerWrapper;
|
||||
|
||||
@WithMinecraft
|
||||
public class ForgeContainerTransferTest implements ContainerTransferContract {
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrap(Container container) {
|
||||
return new ForgeContainerTransfer(new InvWrapper(container));
|
||||
return new ForgeContainerTransfer(VanillaContainerWrapper.of(container));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
package dan200.computercraft.test.shared;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.fml.loading.FMLLoader;
|
||||
import net.neoforged.fml.loading.LoadingModList;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -17,7 +20,18 @@ import java.util.Map;
|
||||
public final class NeoSetupHook implements WithMinecraft.SetupHook {
|
||||
@Override
|
||||
public void run() {
|
||||
// Feature flags require the loaded mod list to be available, so populate it with some empty data.
|
||||
LoadingModList.of(List.of(), List.of(), List.of(), List.of(), Map.of());
|
||||
// Bits of Minecraft depend on the loader being present. Do some nasty things to inject it.
|
||||
// TODO: Switch to using NF's JUnit support instead.
|
||||
try {
|
||||
var ctor = FMLLoader.class.getDeclaredConstructor(ClassLoader.class, String[].class, Dist.class, boolean.class, Path.class);
|
||||
ctor.setAccessible(true);
|
||||
var loader = ctor.newInstance(null, new String[0], Dist.CLIENT, false, Path.of("."));
|
||||
|
||||
var modListField = FMLLoader.class.getDeclaredField("loadingModList");
|
||||
modListField.setAccessible(true);
|
||||
modListField.set(loader, LoadingModList.of(List.of(), List.of(), List.of(), List.of(), List.of(), Map.of()));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class TestMod {
|
||||
if (TestHooks.onBeforeDestroyBlock(e.getLevel(), e.getPos(), e.getState())) e.setCanceled(true);
|
||||
});
|
||||
|
||||
if (FMLEnvironment.dist == Dist.CLIENT) TestMod.onInitializeClient();
|
||||
if (FMLEnvironment.getDist() == Dist.CLIENT) TestMod.onInitializeClient();
|
||||
|
||||
var tests = TestHooks.loadTests();
|
||||
modBus.addListener((RegisterEvent event) -> {
|
||||
|
||||
Reference in New Issue
Block a user