diff --git a/projects/common/src/client/java/dan200/computercraft/client/pocket/ClientPocketComputers.java b/projects/common/src/client/java/dan200/computercraft/client/pocket/ClientPocketComputers.java index 2d4eef3c7..db305490b 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/pocket/ClientPocketComputers.java +++ b/projects/common/src/client/java/dan200/computercraft/client/pocket/ClientPocketComputers.java @@ -56,4 +56,8 @@ public final class ClientPocketComputers { var id = PocketComputerItem.getInstanceID(stack); return id == null ? null : instances.get(id); } + + static @Nullable PocketComputerData get(UUID id) { + return instances.get(id); + } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/pocket/PocketClientTooltipComponent.java b/projects/common/src/client/java/dan200/computercraft/client/pocket/PocketClientTooltipComponent.java new file mode 100644 index 000000000..2e1f68717 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/pocket/PocketClientTooltipComponent.java @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.pocket; + +import com.mojang.blaze3d.vertex.PoseStack; +import dan200.computercraft.client.gui.GuiSprites; +import dan200.computercraft.client.render.ComputerBorderRenderer; +import dan200.computercraft.client.render.RenderTypes; +import dan200.computercraft.client.render.SpriteRenderer; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; +import dan200.computercraft.shared.pocket.items.PocketTooltipComponent; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.renderer.MultiBufferSource; + +import javax.annotation.Nullable; +import java.util.UUID; + +import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER; +import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; + +/** + * Renders the pocket computer's terminal in the item's tooltip. + *
+ * The rendered terminal is downscaled by a factor of {@link #SCALE}.
+ */
+public class PocketClientTooltipComponent implements ClientTooltipComponent {
+ private static final float SCALE = 0.5f;
+
+ private final UUID id;
+ private final ComputerFamily family;
+
+ public PocketClientTooltipComponent(PocketTooltipComponent component) {
+ this.id = component.id();
+ this.family = component.family();
+ }
+
+ private @Nullable PocketComputerData computer() {
+ return ClientPocketComputers.get(id);
+ }
+
+ private @Nullable NetworkedTerminal terminal() {
+ var computer = computer();
+ return computer == null ? null : computer.getTerminal();
+ }
+
+ @Override
+ public int getHeight() {
+ var terminal = terminal();
+ if (terminal == null) return 0;
+
+ return (int) Math.ceil(
+ (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT + ComputerBorderRenderer.BORDER * 2 + ComputerBorderRenderer.MARGIN * 2) * SCALE
+ );
+ }
+
+ @Override
+ public int getWidth(Font font) {
+ var terminal = terminal();
+ if (terminal == null) return 0;
+
+ return (int) Math.ceil(
+ (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH + ComputerBorderRenderer.BORDER * 2 + ComputerBorderRenderer.MARGIN * 2) * SCALE
+ );
+ }
+
+ @Override
+ public void renderImage(Font font, int x, int y, GuiGraphics guiGraphics) {
+ var terminal = terminal();
+ if (terminal == null) return;
+
+ var pose = guiGraphics.pose();
+ pose.pushPose();
+ pose.translate(x, y, 0);
+ pose.scale(SCALE, SCALE, 1);
+
+
+ render(pose, guiGraphics.bufferSource(), terminal);
+
+ pose.popPose();
+ }
+
+ private void render(PoseStack stack, MultiBufferSource buffers, Terminal terminal) {
+ var width = terminal.getWidth() * FONT_WIDTH + MARGIN * 2;
+ var height = terminal.getHeight() * FONT_HEIGHT + MARGIN * 2;
+
+ var renderer = SpriteRenderer.createForGui(stack.last().pose(), buffers.getBuffer(RenderTypes.GUI_SPRITES));
+ ComputerBorderRenderer.render(renderer, GuiSprites.getComputerTextures(family), BORDER, BORDER, width, height, false);
+
+ var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(stack, buffers.getBuffer(RenderTypes.TERMINAL));
+ FixedWidthFontRenderer.drawTerminal(quadEmitter, BORDER + MARGIN, BORDER + MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
+ }
+}
diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/SpriteRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/SpriteRenderer.java
index b7d0817c9..4f9e0b865 100644
--- a/projects/common/src/client/java/dan200/computercraft/client/render/SpriteRenderer.java
+++ b/projects/common/src/client/java/dan200/computercraft/client/render/SpriteRenderer.java
@@ -34,11 +34,12 @@ public class SpriteRenderer {
this.b = b;
}
+ public static SpriteRenderer createForGui(Matrix4f transform, VertexConsumer builder) {
+ return new SpriteRenderer(transform, builder, 0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255);
+ }
+
public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
- return new SpriteRenderer(
- graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
- 0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
- );
+ return createForGui(graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType));
}
/**
diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java
index 348c96dec..bed601b74 100644
--- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java
+++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java
@@ -28,7 +28,6 @@ import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
@@ -39,6 +38,7 @@ import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
@@ -47,6 +47,7 @@ import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem {
@@ -196,6 +197,11 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
}
}
+ @Override
+ public Optional
+ * This has no behaviour on its own. When rendering, this is converted to an equivalent client-side component,
+ * that renders the computer's terminal.
+ *
+ * @param id The instance ID of this pocket computer.
+ * @param family The family of this pocket computer.
+ * @see PocketComputerItem#getTooltipImage(ItemStack)
+ * @see dan200.computercraft.client.pocket.PocketClientTooltipComponent
+ */
+public record PocketTooltipComponent(UUID id, ComputerFamily family) implements TooltipComponent {
+}
diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java
index 51e99242b..41fb92c5d 100644
--- a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java
+++ b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java
@@ -7,6 +7,7 @@ package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
import dan200.computercraft.client.model.CustomModelLoader;
+import dan200.computercraft.client.pocket.PocketClientTooltipComponent;
import dan200.computercraft.impl.Services;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
@@ -15,6 +16,7 @@ import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.FabricMessageType;
+import dan200.computercraft.shared.pocket.items.PocketTooltipComponent;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
@@ -22,6 +24,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
+import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
import net.fabricmc.loader.api.FabricLoader;
@@ -74,6 +77,11 @@ public class ComputerCraftClient {
}
});
+ TooltipComponentCallback.EVENT.register(c -> {
+ if (c instanceof PocketTooltipComponent p) return new PocketClientTooltipComponent(p);
+ return null;
+ });
+
// Easier to hook in as an event than use BlockPickInteractionAware.
ClientPickBlockGatherCallback.EVENT.register((player, hit) -> {
if (hit.getType() != HitResult.Type.BLOCK) return ItemStack.EMPTY;
diff --git a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java
index cd6977a35..c7bbcd44c 100644
--- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java
+++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java
@@ -7,13 +7,12 @@ package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
+import dan200.computercraft.client.pocket.PocketClientTooltipComponent;
+import dan200.computercraft.shared.pocket.items.PocketTooltipComponent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraftforge.api.distmarker.Dist;
-import net.minecraftforge.client.event.ModelEvent;
-import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
-import net.minecraftforge.client.event.RegisterColorHandlersEvent;
-import net.minecraftforge.client.event.RegisterShadersEvent;
+import net.minecraftforge.client.event.*;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.common.Mod;
@@ -85,4 +84,9 @@ public final class ForgeClientRegistry {
ClientRegistry.register();
event.enqueueWork(() -> ClientRegistry.registerMainThread(ItemProperties::register));
}
+
+ @SubscribeEvent
+ public static void onTooltipComponent(RegisterClientTooltipComponentFactoriesEvent event) {
+ event.register(PocketTooltipComponent.class, PocketClientTooltipComponent::new);
+ }
}