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 getTooltipImage(ItemStack stack) { + var id = getInstanceID(stack); + return id == null ? Optional.empty() : Optional.of(new PocketTooltipComponent(id, family)); + } @Override public void appendHoverText(ItemStack stack, @Nullable Level world, List list, TooltipFlag flag) { @@ -350,8 +356,4 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I compound.put(NBT_UPGRADE_INFO, upgrade.data().copy()); } } - - public static CompoundTag getUpgradeInfo(ItemStack stack) { - return stack.getOrCreateTagElement(NBT_UPGRADE_INFO); - } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketTooltipComponent.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketTooltipComponent.java new file mode 100644 index 000000000..c0eb60851 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketTooltipComponent.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.pocket.items; + +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.ItemStack; + +import java.util.UUID; + +/** + * A tooltip computer describing a pocket computer. + *

+ * 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); + } }