mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +00:00 
			
		
		
		
	Support placing pocket computers on lecterns (#2098)
This allows shift+clicking a pocket computer on to a lectern. These computers can be right clicked, opening the no-term computer GUI. Terminal contents is rendered in-world, and broadcast to everyone in range. - Add a new lectern PocketHolder. - Refactor some of the `PocketItemComputer` code to allow ticking pocket computers from a non-player/entity source. - Add a new model for pocket computers. This requires several new textures (somewhat mirroring the item ones), which is a little unfortunate, but looks much better than reusing the map renderer or item form.
This commit is contained in:
		| @@ -0,0 +1,96 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.client.model; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.blaze3d.vertex.VertexConsumer; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.client.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.resources.model.Material; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.FastColor; | ||||
| import net.minecraft.world.inventory.InventoryMenu; | ||||
| 
 | ||||
| /** | ||||
|  * A model for {@linkplain PocketComputerItem pocket computers} placed on a lectern. | ||||
|  * | ||||
|  * @see CustomLecternRenderer | ||||
|  */ | ||||
| public class LecternPocketModel { | ||||
|     public static final ResourceLocation TEXTURE_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_normal"); | ||||
|     public static final ResourceLocation TEXTURE_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_advanced"); | ||||
|     public static final ResourceLocation TEXTURE_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_colour"); | ||||
|     public static final ResourceLocation TEXTURE_FRAME = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_frame"); | ||||
|     public static final ResourceLocation TEXTURE_LIGHT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_light"); | ||||
| 
 | ||||
|     private static final Material MATERIAL_NORMAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_NORMAL); | ||||
|     private static final Material MATERIAL_ADVANCED = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_ADVANCED); | ||||
|     private static final Material MATERIAL_COLOUR = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_COLOUR); | ||||
|     private static final Material MATERIAL_FRAME = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_FRAME); | ||||
|     private static final Material MATERIAL_LIGHT = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_LIGHT); | ||||
| 
 | ||||
|     // The size of the terminal within the model. | ||||
|     public static final float TERM_WIDTH = 12.0f / 32.0f; | ||||
|     public static final float TERM_HEIGHT = 14.0f / 32.0f; | ||||
| 
 | ||||
|     // The size of the texture. The texture is 36x36, but is at 2x resolution. | ||||
|     private static final int TEXTURE_WIDTH = 36 / 2; | ||||
|     private static final int TEXTURE_HEIGHT = 36 / 2; | ||||
| 
 | ||||
|     private final ModelPart root; | ||||
| 
 | ||||
|     public LecternPocketModel() { | ||||
|         root = buildPages(); | ||||
|     } | ||||
| 
 | ||||
|     private static ModelPart buildPages() { | ||||
|         var mesh = new MeshDefinition(); | ||||
|         var parts = mesh.getRoot(); | ||||
|         parts.addOrReplaceChild( | ||||
|             "root", | ||||
|             CubeListBuilder.create().texOffs(0, 0).addBox(0f, -5.0f, -4.0f, 1f, 10.0f, 8.0f), | ||||
|             PartPose.ZERO | ||||
|         ); | ||||
|         return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT); | ||||
|     } | ||||
| 
 | ||||
|     private void render(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int colour) { | ||||
|         int red = FastColor.ARGB32.red(colour), green = FastColor.ARGB32.green(colour), blue = FastColor.ARGB32.blue(colour), alpha = FastColor.ARGB32.alpha(colour); | ||||
|         root.render(poseStack, buffer, packedLight, packedOverlay, red / 255.0f, green / 255.0f, blue / 255.0f, alpha / 255.0f); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Render the pocket computer model. | ||||
|      * | ||||
|      * @param poseStack     The current pose stack. | ||||
|      * @param bufferSource  The buffer source to draw to. | ||||
|      * @param packedLight   The current light level. | ||||
|      * @param packedOverlay The overlay texture (used for entity hurt animation). | ||||
|      * @param family        The computer family. | ||||
|      * @param frameColour   The pocket computer's {@linkplain PocketComputerItem#getColour(ItemStack) 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) { | ||||
|         if (frameColour != -1) { | ||||
|             root.render(poseStack, MATERIAL_FRAME.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, 1, 1, 1, 1); | ||||
|             render(poseStack, MATERIAL_COLOUR.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, frameColour); | ||||
|         } else { | ||||
|             var buffer = (family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL).buffer(bufferSource, RenderType::entityCutout); | ||||
|             root.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1); | ||||
|         } | ||||
| 
 | ||||
|         render(poseStack, MATERIAL_LIGHT.buffer(bufferSource, RenderType::entityCutout), LightTexture.FULL_BRIGHT, packedOverlay, lightColour); | ||||
|     } | ||||
| } | ||||
| @@ -6,15 +6,28 @@ package dan200.computercraft.client.render; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.math.Axis; | ||||
| import dan200.computercraft.client.model.LecternPocketModel; | ||||
| 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.lectern.CustomLecternBlockEntity; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.util.ARGB32; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.RenderType; | ||||
| 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.LecternRenderer; | ||||
| import net.minecraft.world.level.block.LecternBlock; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| /** | ||||
|  * A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}. | ||||
| @@ -22,10 +35,17 @@ import net.minecraft.world.level.block.LecternBlock; | ||||
|  * This largely follows {@link LecternRenderer}, but with support for multiple types of item. | ||||
|  */ | ||||
| public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> { | ||||
|     private static final int POCKET_TERMINAL_RENDER_DISTANCE = 32; | ||||
| 
 | ||||
|     private final BlockEntityRenderDispatcher berDispatcher; | ||||
|     private final LecternPrintoutModel printoutModel; | ||||
|     private final LecternPocketModel pocketModel; | ||||
| 
 | ||||
|     public CustomLecternRenderer(BlockEntityRendererProvider.Context context) { | ||||
|         berDispatcher = context.getBlockEntityRenderDispatcher(); | ||||
| 
 | ||||
|         printoutModel = new LecternPrintoutModel(); | ||||
|         pocketModel = new LecternPocketModel(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -44,8 +64,46 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB | ||||
|             } else { | ||||
|                 printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item)); | ||||
|             } | ||||
|         } else if (item.getItem() instanceof PocketComputerItem pocket) { | ||||
|             var computer = ClientPocketComputers.get(item); | ||||
| 
 | ||||
|             pocketModel.render( | ||||
|                 poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), pocket.getColour(item), | ||||
|                 ARGB32.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState()) | ||||
|             ); | ||||
| 
 | ||||
|             // Jiggle the terminal about a bit, so (0, 0) is in the top left of the model's terminal hole. | ||||
|             poseStack.mulPose(Axis.YP.rotationDegrees(90f)); | ||||
|             poseStack.translate(-0.5 * LecternPocketModel.TERM_WIDTH, 0.5 * LecternPocketModel.TERM_HEIGHT + 1f / 32.0f, 1 / 16.0f); | ||||
|             poseStack.mulPose(Axis.XP.rotationDegrees(180)); | ||||
| 
 | ||||
|             // Either render the terminal or a black screen, depending on how close we are. | ||||
|             var terminal = computer == null ? null : computer.getTerminal(); | ||||
|             var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(RenderTypes.TERMINAL)); | ||||
|             if (terminal != null && Vec3.atCenterOf(lectern.getBlockPos()).closerThan(berDispatcher.camera.getPosition(), POCKET_TERMINAL_RENDER_DISTANCE)) { | ||||
|                 renderPocketTerminal(poseStack, quadEmitter, terminal); | ||||
|             } else { | ||||
|                 FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         poseStack.popPose(); | ||||
|     } | ||||
| 
 | ||||
|     private static void renderPocketTerminal(PoseStack poseStack, FixedWidthFontRenderer.QuadEmitter quadEmitter, Terminal terminal) { | ||||
|         var width = terminal.getWidth() * FONT_WIDTH; | ||||
|         var height = terminal.getHeight() * FONT_HEIGHT; | ||||
| 
 | ||||
|         // Scale the terminal down to fit in the available space. | ||||
|         var scaleX = LecternPocketModel.TERM_WIDTH / (width + MARGIN * 2); | ||||
|         var scaleY = LecternPocketModel.TERM_HEIGHT / (height + MARGIN * 2); | ||||
|         var scale = Math.min(scaleX, scaleY); | ||||
|         poseStack.scale(scale, scale, -1.0f); | ||||
| 
 | ||||
|         // Convert the model dimensions to terminal space, then find out how large the margin should be. | ||||
|         var marginX = ((LecternPocketModel.TERM_WIDTH / scale) - width) / 2; | ||||
|         var marginY = ((LecternPocketModel.TERM_HEIGHT / scale) - height) / 2; | ||||
| 
 | ||||
|         FixedWidthFontRenderer.drawTerminal(quadEmitter, marginX, marginY, terminal, marginY, marginY, marginX, marginX); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -43,7 +43,7 @@ public final class FixedWidthFontRenderer { | ||||
|     static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH; | ||||
| 
 | ||||
|     private static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR())); | ||||
|     private static final float Z_OFFSET = 1e-3f; | ||||
|     private static final float Z_OFFSET = 1e-4f; | ||||
| 
 | ||||
|     private FixedWidthFontRenderer() { | ||||
|     } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.data; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import dan200.computercraft.client.gui.GuiSprites; | ||||
| import dan200.computercraft.client.model.LecternPocketModel; | ||||
| import dan200.computercraft.client.model.LecternPrintoutModel; | ||||
| import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; | ||||
| import net.minecraft.client.renderer.texture.atlas.SpriteSource; | ||||
| @@ -54,7 +55,9 @@ public final class DataProviders { | ||||
|             out.accept(new ResourceLocation("blocks"), makeSprites(Stream.of( | ||||
|                 UpgradeSlot.LEFT_UPGRADE, | ||||
|                 UpgradeSlot.RIGHT_UPGRADE, | ||||
|                 LecternPrintoutModel.TEXTURE | ||||
|                 LecternPrintoutModel.TEXTURE, | ||||
|                 LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED, | ||||
|                 LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT | ||||
|             ))); | ||||
|             out.accept(GuiSprites.SPRITE_SHEET, makeSprites( | ||||
|                 Stream.of(GuiSprites.TURTLE_NORMAL_SELECTED_SLOT, GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT), | ||||
|   | ||||
| @@ -2,6 +2,11 @@ | ||||
|   "sources": [ | ||||
|     {"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/printout"} | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/printout"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_normal"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_advanced"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_colour"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_frame"}, | ||||
|     {"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_light"} | ||||
|   ] | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.shared.lectern; | ||||
| 
 | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.stats.Stats; | ||||
| @@ -14,6 +15,7 @@ import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.item.ItemEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| import net.minecraft.world.item.context.UseOnContext; | ||||
| @@ -21,8 +23,12 @@ import net.minecraft.world.level.BlockGetter; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.block.Blocks; | ||||
| import net.minecraft.world.level.block.LecternBlock; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityTicker; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Extends {@link LecternBlock} with support for {@linkplain PrintoutItem printouts}. | ||||
| @@ -55,6 +61,27 @@ public class CustomLecternBlock extends LecternBlock { | ||||
|         if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity be) be.setItem(item.split(1)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A default implementation of {@link Item#useOn(UseOnContext)} for items that can be placed on a lectern. | ||||
|      * | ||||
|      * @param context The context of this item usage action. | ||||
|      * @return Whether the item was placed or not. | ||||
|      */ | ||||
|     public static InteractionResult defaultUseItemOn(UseOnContext context) { | ||||
|         var level = context.getLevel(); | ||||
|         var blockPos = context.getClickedPos(); | ||||
|         var blockState = level.getBlockState(blockPos); | ||||
|         if (blockState.is(Blocks.LECTERN) && !blockState.getValue(LecternBlock.HAS_BOOK)) { | ||||
|             // If we have an empty lectern, place our book into it. | ||||
|             if (!level.isClientSide) { | ||||
|                 CustomLecternBlock.replaceLectern(level, blockPos, blockState, context.getItemInHand()); | ||||
|             } | ||||
|             return InteractionResult.sidedSuccess(level.isClientSide); | ||||
|         } else { | ||||
|             return InteractionResult.PASS; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove a custom lectern and replace it with an empty vanilla one. | ||||
|      * | ||||
| @@ -131,7 +158,7 @@ public class CustomLecternBlock extends LecternBlock { | ||||
|                 clearLectern(level, pos, state); | ||||
|             } else { | ||||
|                 // Otherwise open the screen. | ||||
|                 player.openMenu(lectern); | ||||
|                 lectern.openMenu(player); | ||||
|             } | ||||
| 
 | ||||
|             player.awardStat(Stats.INTERACT_WITH_LECTERN); | ||||
| @@ -139,4 +166,11 @@ public class CustomLecternBlock extends LecternBlock { | ||||
| 
 | ||||
|         return InteractionResult.sidedSuccess(level.isClientSide); | ||||
|     } | ||||
| 
 | ||||
|     @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); | ||||
|     } | ||||
| 
 | ||||
|     private static final BlockEntityTicker<CustomLecternBlockEntity> serverTicker = (level, pos, state, lectern) -> lectern.tick(); | ||||
| } | ||||
|   | ||||
| @@ -9,27 +9,25 @@ import dan200.computercraft.shared.container.BasicContainer; | ||||
| import dan200.computercraft.shared.container.SingleContainerData; | ||||
| import dan200.computercraft.shared.media.PrintoutMenu; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.pocket.core.PocketHolder; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.nbt.Tag; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.protocol.Packet; | ||||
| import net.minecraft.network.protocol.game.ClientGamePacketListener; | ||||
| import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; | ||||
| import net.minecraft.util.Mth; | ||||
| import net.minecraft.world.Container; | ||||
| import net.minecraft.world.MenuProvider; | ||||
| import net.minecraft.world.entity.player.Inventory; | ||||
| import net.minecraft.world.SimpleMenuProvider; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| import net.minecraft.world.inventory.ContainerData; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.level.block.LecternBlock; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.LecternBlockEntity; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import java.util.AbstractList; | ||||
| import java.util.List; | ||||
| @@ -39,7 +37,7 @@ import java.util.List; | ||||
|  * | ||||
|  * @see LecternBlockEntity | ||||
|  */ | ||||
| public final class CustomLecternBlockEntity extends BlockEntity implements MenuProvider { | ||||
| public final class CustomLecternBlockEntity extends BlockEntity { | ||||
|     private static final String NBT_ITEM = "Item"; | ||||
|     private static final String NBT_PAGE = "Page"; | ||||
| 
 | ||||
| @@ -81,6 +79,12 @@ public final class CustomLecternBlockEntity extends BlockEntity implements MenuP | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void tick() { | ||||
|         if (item.getItem() instanceof PocketComputerItem pocket) { | ||||
|             pocket.tick(item, new PocketHolder.LecternHolder(this), false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the current page, emitting a redstone pulse if needed. | ||||
|      * | ||||
| @@ -123,24 +127,17 @@ public final class CustomLecternBlockEntity extends BlockEntity implements MenuP | ||||
|         return tag; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) { | ||||
|     void openMenu(Player player) { | ||||
|         var item = getItem(); | ||||
|         if (item.getItem() instanceof PrintoutItem) { | ||||
|             return new PrintoutMenu( | ||||
|                 containerId, new LecternContainer(), 0, | ||||
|                 p -> Container.stillValidBlockEntity(this, player, Container.DEFAULT_DISTANCE_LIMIT), | ||||
|             player.openMenu(new SimpleMenuProvider((id, inventory, entity) -> new PrintoutMenu( | ||||
|                 id, new LecternContainer(), 0, | ||||
|                 p -> Container.stillValidBlockEntity(this, p, Container.DEFAULT_DISTANCE_LIMIT), | ||||
|                 new PrintoutContainerData() | ||||
|             ); | ||||
|             ), getItem().getDisplayName())); | ||||
|         } else if (item.getItem() instanceof PocketComputerItem pocket) { | ||||
|             pocket.open(player, item, new PocketHolder.LecternHolder(this), true); | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Component getDisplayName() { | ||||
|         return getItem().getDisplayName(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|   | ||||
| @@ -19,8 +19,6 @@ import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.item.context.UseOnContext; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.block.Blocks; | ||||
| import net.minecraft.world.level.block.LecternBlock; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| @@ -56,18 +54,7 @@ public class PrintoutItem extends Item { | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResult useOn(UseOnContext context) { | ||||
|         var level = context.getLevel(); | ||||
|         var blockPos = context.getClickedPos(); | ||||
|         var blockState = level.getBlockState(blockPos); | ||||
|         if (blockState.is(Blocks.LECTERN) && !blockState.getValue(LecternBlock.HAS_BOOK)) { | ||||
|             // If we have an empty lectern, place our book into it. | ||||
|             if (!level.isClientSide) { | ||||
|                 CustomLecternBlock.replaceLectern(level, blockPos, blockState, context.getItemInHand()); | ||||
|             } | ||||
|             return InteractionResult.sidedSuccess(level.isClientSide); | ||||
|         } else { | ||||
|             return InteractionResult.PASS; | ||||
|         } | ||||
|         return CustomLecternBlock.defaultUseItemOn(context); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -5,7 +5,9 @@ | ||||
| package dan200.computercraft.shared.pocket.core; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.lectern.CustomLecternBlockEntity; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.server.level.ServerPlayer; | ||||
| @@ -51,6 +53,15 @@ public sealed interface PocketHolder { | ||||
|      */ | ||||
|     void setChanged(); | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the terminal is visible to all players in range, and so should be broadcast to everyone. | ||||
|      * | ||||
|      * @return Whether to send the terminal. | ||||
|      */ | ||||
|     default boolean isTerminalAlwaysVisible() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * An {@link Entity} holding a pocket computer. | ||||
|      */ | ||||
| @@ -112,4 +123,41 @@ public sealed interface PocketHolder { | ||||
|             entity.setItem(entity.getItem().copy()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A pocket computer in a {@link CustomLecternBlockEntity}. | ||||
|      * | ||||
|      * @param lectern The lectern holding this item. | ||||
|      */ | ||||
|     record LecternHolder(CustomLecternBlockEntity lectern) implements PocketHolder { | ||||
|         @Override | ||||
|         public ServerLevel level() { | ||||
|             return (ServerLevel) lectern.getLevel(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public Vec3 pos() { | ||||
|             return Vec3.atCenterOf(lectern.getBlockPos()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public BlockPos blockPos() { | ||||
|             return lectern.getBlockPos(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean isValid(ServerComputer computer) { | ||||
|             return !lectern().isRemoved() && PocketComputerItem.isServerComputer(computer, lectern.getItem()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setChanged() { | ||||
|             BlockEntityHelpers.updateBlock(lectern()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean isTerminalAlwaysVisible() { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public final class PocketServerComputer extends ServerComputer { | ||||
|             // Broadcast the state to new players. | ||||
|             var added = newTracking.stream().filter(x -> !tracking.contains(x)).toList(); | ||||
|             if (!added.isEmpty()) { | ||||
|                 ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added); | ||||
|                 ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, brain.holder().isTerminalAlwaysVisible()), added); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @@ -83,9 +83,15 @@ public final class PocketServerComputer extends ServerComputer { | ||||
|     protected void onTerminalChanged() { | ||||
|         super.onTerminalChanged(); | ||||
| 
 | ||||
|         if (brain.holder() instanceof PocketHolder.PlayerHolder holder && holder.isValid(this)) { | ||||
|             // Broadcast the terminal to the current player. | ||||
|             ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder.entity()); | ||||
|         var holder = brain.holder() instanceof PocketHolder.PlayerHolder h && h.isValid(this) ? h.entity() : null; | ||||
|         if (brain.holder().isTerminalAlwaysVisible() && !tracking.isEmpty()) { | ||||
|             // If the terminal is always visible, send it to all players *and* the holder. | ||||
|             var packet = new PocketComputerDataMessage(this, true); | ||||
|             ServerNetworking.sendToPlayers(packet, tracking); | ||||
|             if (holder != null && !tracking.contains(holder)) ServerNetworking.sendToPlayer(packet, holder); | ||||
|         } else if (holder != null) { | ||||
|             // Otherwise just send it to the holder. | ||||
|             ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -21,6 +21,7 @@ import dan200.computercraft.shared.computer.core.ServerContext; | ||||
| import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.lectern.CustomLecternBlock; | ||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import dan200.computercraft.shared.pocket.core.PocketBrain; | ||||
| @@ -44,6 +45,7 @@ import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.item.context.UseOnContext; | ||||
| import net.minecraft.world.level.Level; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -83,10 +85,18 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
|      * | ||||
|      * @param stack   The current pocket computer stack. | ||||
|      * @param holder  The entity holding the pocket item. | ||||
|      * @param brain  The pocket computer brain. | ||||
|      * @param passive If set, the pocket computer will not be created if it doesn't exist, and will not be kept alive. | ||||
|      */ | ||||
|     private void tick(ItemStack stack, PocketHolder holder, PocketBrain brain) { | ||||
|         brain.updateHolder(holder); | ||||
|     public void tick(ItemStack stack, PocketHolder holder, boolean passive) { | ||||
|         PocketBrain brain; | ||||
|         if (passive) { | ||||
|             var computer = getServerComputer(holder.level().getServer(), stack); | ||||
|             if (computer == null) return; | ||||
|             brain = computer.getBrain(); | ||||
|         } else { | ||||
|             brain = getOrCreateBrain(holder.level(), holder, stack); | ||||
|             brain.computer().keepAlive(); | ||||
|         } | ||||
| 
 | ||||
|         // Update pocket upgrade | ||||
|         var upgrade = brain.getUpgrade(); | ||||
| @@ -139,11 +149,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
|         if (slot < 0) return; | ||||
| 
 | ||||
|         // If we're in the inventory, create a computer and keep it alive. | ||||
|         var holder = new PocketHolder.PlayerHolder(player, slot); | ||||
|         var brain = getOrCreateBrain((ServerLevel) world, holder, stack); | ||||
|         brain.computer().keepAlive(); | ||||
| 
 | ||||
|         tick(stack, holder, brain); | ||||
|         tick(stack, new PocketHolder.PlayerHolder(player, slot), false); | ||||
|     } | ||||
| 
 | ||||
|     @ForgeOverride | ||||
| @@ -153,12 +159,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
| 
 | ||||
|         // If we're an item entity, tick an already existing computer (as to update the position), but do not keep the | ||||
|         // computer alive. | ||||
|         var computer = getServerComputer(level.getServer(), stack); | ||||
|         if (computer != null) tick(stack, new PocketHolder.ItemEntityHolder(entity), computer.getBrain()); | ||||
|         tick(stack, new PocketHolder.ItemEntityHolder(entity), true); | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResult useOn(UseOnContext context) { | ||||
|         return CustomLecternBlock.defaultUseItemOn(context); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) { | ||||
|         var stack = player.getItemInHand(hand); | ||||
| @@ -171,25 +181,39 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
|             var stop = false; | ||||
|             var upgrade = getUpgrade(stack); | ||||
|             if (upgrade != null) { | ||||
|                 brain.updateHolder(holder); | ||||
|                 stop = upgrade.onRightClick(world, brain, computer.getPeripheral(ComputerSide.BACK)); | ||||
|                 // Sync back just in case. We don't need to setChanged, as we'll return the item anyway. | ||||
|                 updateItem(stack, brain); | ||||
|             } | ||||
| 
 | ||||
|             if (!stop) { | ||||
|                 PlatformHelper.get().openMenu( | ||||
|                     player, stack.getHoverName(), | ||||
|                     (id, inventory, entity) -> new ComputerMenuWithoutInventory( | ||||
|                         hand == InteractionHand.OFF_HAND ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.COMPUTER.get(), | ||||
|                         id, inventory, p -> isServerComputer(computer, p.getItemInHand(hand)), computer | ||||
|                     ), | ||||
|                     new ComputerContainerData(computer, stack)); | ||||
|             } | ||||
|             if (!stop) openImpl(player, stack, holder, hand == InteractionHand.OFF_HAND, computer); | ||||
|         } | ||||
|         return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open a container for this pocket computer. | ||||
|      * | ||||
|      * @param player       The player to show the menu for. | ||||
|      * @param stack        The pocket computer stack. | ||||
|      * @param holder       The holder of the pocket computer. | ||||
|      * @param isTypingOnly Open the off-hand pocket screen (only supporting typing, with no visible terminal). | ||||
|      */ | ||||
|     public void open(Player player, ItemStack stack, PocketHolder holder, boolean isTypingOnly) { | ||||
|         var brain = getOrCreateBrain(holder.level(), holder, stack); | ||||
|         var computer = brain.computer(); | ||||
|         computer.turnOn(); | ||||
|         openImpl(player, stack, holder, isTypingOnly, computer); | ||||
|     } | ||||
| 
 | ||||
|     private static void openImpl(Player player, ItemStack stack, PocketHolder holder, boolean isTypingOnly, ServerComputer computer) { | ||||
|         PlatformHelper.get().openMenu(player, stack.getHoverName(), (id, inventory, entity) -> new ComputerMenuWithoutInventory( | ||||
|             isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.COMPUTER.get(), id, inventory, | ||||
|             p -> holder.isValid(computer), | ||||
|             computer | ||||
|         ), new ComputerContainerData(computer, stack)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Component getName(ItemStack stack) { | ||||
|         var baseString = getDescriptionId(stack); | ||||
| @@ -233,7 +257,11 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
|         var registry = ServerContext.get(level.getServer()).registry(); | ||||
|         { | ||||
|             var computer = getServerComputer(registry, stack); | ||||
|             if (computer != null) return computer.getBrain(); | ||||
|             if (computer != null) { | ||||
|                 var brain = computer.getBrain(); | ||||
|                 brain.updateHolder(holder); | ||||
|                 return brain; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var computerID = getComputerID(stack); | ||||
| @@ -252,8 +280,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I | ||||
|         tag.putInt(NBT_SESSION, registry.getSessionID()); | ||||
|         tag.putUUID(NBT_INSTANCE, computer.register()); | ||||
| 
 | ||||
|         // Only turn on when initially creating the computer, rather than each tick. | ||||
|         if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn(); | ||||
|         if (isMarkedOn(stack)) computer.turnOn(); | ||||
| 
 | ||||
|         updateItem(stack, brain); | ||||
| 
 | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 152 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 179 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 119 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 92 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 152 B | 
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates