1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-26 11:27:38 +00:00

Initial update to 1.21.10

This commit is contained in:
Jonathan Coates
2025-10-13 21:15:54 +01:00
parent 4cccf1817c
commit ef1923dd3e
65 changed files with 579 additions and 681 deletions

View File

@@ -25,6 +25,7 @@ repositories {
name = "Fabric"
content {
includeGroup("net.fabricmc")
includeGroup("net.fabricmc.unpick")
}
}

View File

@@ -15,4 +15,4 @@ isUnstable=true
modVersion=1.116.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21.8
mcVersion=1.21.10

View File

@@ -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.135.0+1.21.10"
fabric-loader = "0.17.3"
neoForge = "21.10.6-beta"
neoMergeTool = "2.0.0"
mixin = "0.8.5"
parchment = "2025.06.29"
parchmentMc = "1.21.6"
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"
@@ -62,20 +62,20 @@ cctJavadoc = "1.8.5"
checkstyle = "10.23.1"
errorProne-core = "2.38.0"
errorProne-plugin = "4.1.0"
fabric-loom = "1.10.4"
fabric-loom = "1.11.8"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.3"
minotaur = "2.8.7"
modDevGradle = "2.0.99"
modDevGradle = "2.0.113"
nullAway = "0.12.7"
shadow = "8.3.1"
spotless = "7.0.2"
taskTree = "2.1.1"
teavm = "0.13.0-SQUID.1"
vanillaExtract = "0.2.1"
vanillaExtract = "0.3.0"
versionCatalogUpdate = "0.8.1"
[libraries]

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

6
gradlew vendored
View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -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,35 @@ 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 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.
*/
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()) {
collector.submitCustomGeometry(transform, ModelBakery.DESTROY_TYPES.get(crumblingOverlay.progress()), (pose, buffer) ->
// FIXME: This does not work. Should we have a custom hook for this instead?
render(pose, new SheetedDecalTextureGenerator(buffer, crumblingOverlay.cameraPose(), 1.0f), tints, 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();

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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.

View File

@@ -30,6 +30,7 @@ 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;
@@ -76,25 +77,25 @@ public final class ClientHooks {
}
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;
}
@@ -111,6 +112,7 @@ public final class ClientHooks {
* @param addText A callback which adds a single line of text.
*/
public static void addBlockDebugInfo(Consumer<String> addText) {
// TODO(1.21.9): Replacement for this
var minecraft = Minecraft.getInstance();
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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,10 @@ 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);
// TODO: This can probably be shifted into a separate one now.
drawText(
pose, bufferSource, X_TEXT_MARGIN, Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * state.page(), LightTexture.FULL_BRIGHT,
state.printout().text(), state.printout().colour()

View File

@@ -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,8 +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);
public void buildVertices(VertexConsumer vertexConsumer) {
var quads = new FixedWidthFontRenderer.QuadEmitter(new Matrix4f().mul(pose), vertexConsumer);
FixedWidthFontRenderer.drawTerminalBackground(quads, x, y, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
}
@@ -324,14 +326,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);
public void buildVertices(VertexConsumer vertexConsumer) {
var quads = new FixedWidthFontRenderer.QuadEmitter(new Matrix4f().mul(pose), vertexConsumer);
FixedWidthFontRenderer.drawTerminalForeground(quads, x, y, terminal);
FixedWidthFontRenderer.drawCursor(quads, 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);
vertexConsumer.addVertex(0, 0, 0).setColor(0x00ffffff).setUv(0, 0).setLight(LightTexture.FULL_BRIGHT);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -4,23 +4,16 @@
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.api.ComputerCraftAPI;
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.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
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.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.component.DyedItemColor;
/**
* A model for {@linkplain PocketComputerItem pocket computers} placed on a lectern.
@@ -76,7 +69,7 @@ public class LecternPocketModel {
* @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 render(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, 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);
@@ -86,5 +79,5 @@ public class LecternPocketModel {
}
root.render(poseStack, MATERIAL_LIGHT.buffer(bufferSource, RenderType::entityCutout), LightTexture.FULL_BRIGHT, packedOverlay, lightColour);
}
}*/
}

View File

@@ -13,6 +13,8 @@ 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.MeshDefinition;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
@@ -102,8 +104,8 @@ public class LecternPrintoutModel {
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 submitBook(PoseStack poseStack, SubmitNodeCollector collector, int packedLight, int packedOverlay) {
collector.submitModelPart(bookRoot, poseStack, RenderType.entitySolid(LecternPrintoutModel.MATERIAL.texture()), packedLight, packedOverlay, null);
}
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {

View File

@@ -11,21 +11,24 @@ import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.client.pocket.ClientPocketComputers;
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.util.ARGB;
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.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,7 +39,7 @@ 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 LecternPrintoutModel printoutModel;
@@ -48,25 +51,47 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
}
@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);
state.setPocket(
pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
computer == null ? -1 : computer.getLightState(),
computer == null || !Vec3.atCenterOf(lectern.getBlockPos()).closerThan(camera, POCKET_TERMINAL_RENDER_DISTANCE)
? null : computer.getTerminal()
);
} else {
state.setUnknown();
}
}
pocketModel.render(
@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) {
printoutModel.submitBook(poseStack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY);
} else {
// TODO: printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());
}
} else if (state.type == Type.POCKET_COMPUTER) {
// TODO: Pocket model rendering
/*pocketModel.render(
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
);
@@ -77,13 +102,12 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
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);
if (state.pocketTerminal != null) {
renderPocketTerminal(poseStack, quadEmitter, state.pocketTerminal);
} else {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
}
}*/
}
poseStack.popPose();
@@ -105,4 +129,45 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
FixedWidthFontRenderer.drawTerminal(quadEmitter, 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 int pages;
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.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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 submit, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack);
var terminal = computer == null ? null : computer.getTerminal();
@@ -75,41 +77,43 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
var frameColour = DyedItemColor.getOrDefault(stack, -1);
var matrix = transform.last().pose();
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
renderFrame(transform, submit, 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, submit, lightColour, width, height);
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
submit.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
var quadEmitter = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), buffer);
if (terminal == null) {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
} else {
FixedWidthFontRenderer.drawTerminal(quadEmitter, 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 +161,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(
new FixedWidthFontRenderer.QuadEmitter(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 +180,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);
}
}

View File

@@ -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()));
}
}

View File

@@ -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;
@@ -83,9 +83,8 @@ public final class PrintoutRenderer {
}
}
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) {
var emitter = new FixedWidthFontRenderer.QuadEmitter(matrix4f, buffer);
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
var lineContents = lines.get(start + line);
FixedWidthFontRenderer.drawString(emitter,
@@ -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);

View File

@@ -4,13 +4,13 @@
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;
/**
@@ -23,15 +23,15 @@ import org.joml.Matrix4f;
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 +47,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(

View File

@@ -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;
@@ -17,110 +20,152 @@ 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.SubmitNodeCollector;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
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 ItemModelResolver itemModelResolver;
private final Font font;
public TurtleBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
renderer = context.getBlockEntityRenderDispatcher();
font = context.getFont();
renderer = context.blockEntityRenderDispatcher();
itemModelResolver = context.itemModelResolver();
font = context.font();
}
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 ItemStackRenderState leftUpgrade = new ItemStackRenderState();
private float rightAngle;
private 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);
}
}

View File

@@ -4,104 +4,81 @@
package dan200.computercraft.client.render.monitor;
import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.integration.ShaderMod;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.config.Config;
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.fog.FogRenderer;
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.core.Direction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.OptionalDouble;
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.
*/
private static final float MARGIN = (float) (MonitorBlockEntity.RENDER_MARGIN * 1.1);
private static @Nullable ByteBuffer backingBuffer;
public MonitorBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
}
@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;
var origin = originTerminal.getOrigin();
var renderState = originTerminal.getRenderState(MonitorRenderState::new);
var monitorPos = monitor.getBlockPos();
// 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;
public State createRenderState() {
return new State();
}
renderState.lastRenderFrame = renderFrame;
renderState.lastRenderPos = monitorPos;
@Override
public void extractRenderState(MonitorBlockEntity monitor, State state, float f, Vec3 camera, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
BlockEntityRenderer.super.extractRenderState(monitor, state, f, camera, crumblingOverlay);
var originPos = origin.getBlockPos();
state.direction = monitor.getDirection();
state.front = monitor.getFront();
state.width = monitor.getWidth();
state.height = monitor.getHeight();
state.terminal = monitor.getOriginClientMonitor();
}
@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,158 +87,34 @@ 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 yMagin = (float) (MARGIN / yScale);
renderTerminal(matrix, originTerminal, renderState, terminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale));
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, consumer) -> {
FixedWidthFontRenderer.drawTerminalBackground(
new FixedWidthFontRenderer.QuadEmitter(pose.pose(), consumer),
0, 0, terminal, yMagin, yMagin, xMargin, xMargin
);
});
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, (pose, consumer) -> {
var sink = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), consumer);
FixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal);
FixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal);
});
transform.popPose();
} else {
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, consumer) -> {
FixedWidthFontRenderer.drawEmptyTerminal(
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
new FixedWidthFontRenderer.QuadEmitter(pose.pose(), consumer),
-MARGIN, MARGIN, (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
});
}
transform.popPose();
}
private static void renderTerminal(
Matrix4f matrix, ClientMonitor monitor, MonitorRenderState renderState, Terminal terminal, float xMargin, float yMargin
) {
var redraw = monitor.pollTerminalChanged();
if (renderState.vertexBuffer == null) redraw = true;
if (redraw) {
// Cursor, Foreground, Background+Margin
var maxQuadCount = 1 + (terminal.getWidth() * terminal.getHeight()) + ((terminal.getWidth() + 2) * (terminal.getHeight() + 2));
var maxVertexCount = 4 * maxQuadCount;
var sink = ShaderMod.get().getQuadEmitter(maxQuadCount, MonitorBlockEntityRenderer::getBuffer);
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin);
var vertexCountAfterBackground = sink.vertexCount();
DirectFixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal);
var vertexCountAfterForeground = sink.vertexCount();
DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal);
var vertexCountAfterCursor = sink.vertexCount();
if (vertexCountAfterCursor > maxVertexCount) {
throw new IllegalStateException("Drew too many vertices. Expected " + maxVertexCount + ", drew " + vertexCountAfterCursor);
}
if (vertexCountAfterCursor != 0) {
renderState.register();
var commandEncoder = RenderSystem.getDevice().createCommandEncoder();
var resultBuffer = sink.byteBuffer().flip();
// Ensure our buffer contains the correct number of vertices.
if (resultBuffer.remaining() != sink.format().getVertexSize() * vertexCountAfterCursor) {
throw new IllegalStateException(String.format(
"Mismatched vertex count. Buffer is %d bytes long, but was expected to be %d (vertex size) * %d (vertex count) = %d bytes.",
resultBuffer.limit(), sink.format().getVertexSize(), vertexCountAfterCursor, sink.format().getVertexSize() * vertexCountAfterCursor
));
}
// Upload the buffer, reallocating if required.
if (renderState.vertexBuffer == null || resultBuffer.remaining() > renderState.vertexBuffer.size()) {
if (renderState.vertexBuffer != null) {
renderState.vertexBuffer.close();
renderState.vertexBuffer = null;
}
renderState.vertexBuffer = RenderSystem.getDevice().createBuffer(
() -> "Monitor at " + monitor.getOrigin().getBlockPos(), GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_COPY_DST, resultBuffer
);
} else if (!renderState.vertexBuffer.isClosed()) {
commandEncoder.writeToBuffer(renderState.vertexBuffer.slice(), resultBuffer);
}
}
renderState.vertexCountAfterBackground = vertexCountAfterBackground;
renderState.vertexCountAfterForeground = vertexCountAfterForeground;
renderState.vertexCountAfterCursor = vertexCountAfterCursor;
}
if (renderState.vertexCountAfterCursor == 0) return;
// Our VBO renders coordinates in monitor-space rather than world space. A full sized monitor (8x6) will
// use positions from (0, 0) to (164*FONT_WIDTH, 81*FONT_HEIGHT) = (984, 729). This is far outside the
// normal render distance (~200), and the edges of the monitor fade out due to fog.
// There's not really a good way around this, at least without using a custom render type (which the VBO
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
// absurdly high value.
var oldFog = RenderSystem.getShaderFog();
RenderSystem.setShaderFog(Minecraft.getInstance().gameRenderer.fogRenderer.getBuffer(FogRenderer.FogMode.NONE));
// Compose the existing model view matrix with our transformation matrix.
RenderSystem.getModelViewStack().pushMatrix();
RenderSystem.getModelViewStack().mul(matrix);
// Render background geometry
drawWithShader(renderState, FixedWidthFontRenderer.TERMINAL_TEXT, RenderPipelines.TEXT, 0, renderState.vertexCountAfterBackground);
drawWithShader(
renderState, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, RenderPipelines.TEXT_POLYGON_OFFSET, renderState.vertexCountAfterBackground,
(
FixedWidthFontRenderer.isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()
? renderState.vertexCountAfterCursor : renderState.vertexCountAfterForeground
) - renderState.vertexCountAfterBackground
);
// Clear state
RenderSystem.getModelViewStack().popMatrix();
RenderSystem.setShaderFog(oldFog);
}
private static void drawWithShader(MonitorRenderState renderState, RenderType renderType, RenderPipeline pipeline, int vertexOffset, int vertexCount) {
if (renderState.vertexBuffer == null) {
throw new IllegalStateException("MonitorRenderState has not been initialised");
}
if (vertexCount == 0) return;
var transforms = RenderSystem.getDynamicUniforms().writeTransform(
RenderSystem.getModelViewMatrix(),
new Vector4f(1.0F, 1.0F, 1.0F, 1.0F),
RenderSystem.getModelOffset(),
RenderSystem.getTextureMatrix(),
RenderSystem.getShaderLineWidth()
);
renderType.setupRenderState();
var autoStorageBuffer = RenderSystem.getSequentialBuffer(renderType.mode());
var indexCount = FixedWidthFontRenderer.TERMINAL_TEXT.mode().indexCount(vertexCount);
var indexBuffer = autoStorageBuffer.getBuffer(indexCount);
var target = Minecraft.getInstance().getMainRenderTarget();
var colourTarget = RenderSystem.outputColorTextureOverride != null ? RenderSystem.outputColorTextureOverride : target.getColorTextureView();
var depthTarget = target.useDepth
? (RenderSystem.outputDepthTextureOverride != null ? RenderSystem.outputDepthTextureOverride : target.getDepthTextureView())
: null;
try (var renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(
() -> "Monitor", colourTarget, OptionalInt.empty(), depthTarget, OptionalDouble.empty()
)) {
renderPass.setPipeline(pipeline);
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", transforms);
renderPass.setVertexBuffer(0, renderState.vertexBuffer);
renderPass.setIndexBuffer(indexBuffer, autoStorageBuffer.type());
for (var j = 0; j < 12; j++) {
var gpuTexture = RenderSystem.getShaderTexture(j);
if (gpuTexture != null) renderPass.bindSampler("Sampler" + j, gpuTexture);
}
renderPass.drawIndexed(vertexOffset, 0, indexCount, 1);
}
renderType.clearRenderState();
}
@Override
public int getViewDistance() {
return Config.monitorDistance;
@@ -272,13 +125,19 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
return monitor.getRenderBoundingBox();
}
private static ByteBuffer getBuffer(int capacity) {
var buffer = backingBuffer;
if (buffer == null || buffer.capacity() < capacity) {
buffer = backingBuffer = buffer == null ? MemoryUtil.memAlloc(capacity) : MemoryUtil.memRealloc(buffer, capacity);
@Override
public boolean shouldRender(MonitorBlockEntity monitor, Vec3 camera) {
return BlockEntityRenderer.super.shouldRender(monitor, camera) && monitor.getXIndex() == 0 && monitor.getYIndex() == 0;
}
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() {
}
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -102,7 +102,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
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);
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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

View File

@@ -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()));
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -12,7 +12,6 @@ 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.shared.ComputerCraft;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
@@ -26,7 +25,6 @@ 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.SpecialGuiElementRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
@@ -41,7 +39,6 @@ import net.minecraft.client.renderer.item.properties.conditional.ConditionalItem
import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ResolvableModel;
import net.minecraft.world.phys.BlockHitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -49,8 +46,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();
@@ -69,7 +64,7 @@ public class ComputerCraftClient {
ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put);
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,6 +88,7 @@ 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) -> {
var hit = Minecraft.getInstance().hitResult;
@@ -102,6 +98,7 @@ public class ComputerCraftClient {
return true;
}
});
*/
// Register our open folder command
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -7,7 +7,6 @@
"defaultRequire": 1
},
"client": [
"DebugScreenOverlayMixin",
"ItemFrameRendererMixin",
"ItemFrameRenderStateMixin",
"ItemInHandRendererMixin",

View File

@@ -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> {

View File

@@ -49,9 +49,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"
}

View File

@@ -124,19 +124,6 @@ neoForge {
}
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
@@ -169,11 +156,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)
@@ -186,10 +168,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)

View File

@@ -51,13 +51,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 +112,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);
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Item.BLOCK);
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Fluid.BLOCK);
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Energy.BLOCK);
ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill);
@@ -155,12 +155,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)
);
}

View File

@@ -120,7 +120,7 @@ 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;
}