1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-21 02:03:13 +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:
Jonathan Coates 2025-02-13 17:31:08 +00:00 committed by GitHub
parent 0f123b5efd
commit 2e2f308ff3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 328 additions and 67 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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"}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
@ -81,12 +83,20 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
/**
* Tick a pocket computer.
*
* @param stack The current pocket computer stack.
* @param holder The entity holding the pocket item.
* @param brain The pocket computer brain.
* @param stack The current pocket computer stack.
* @param holder The entity holding the pocket item.
* @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