Let pocket computer on lecterns wrap the peripheral below

- Shuffle some of the accessors in PocketComputerItem around, to make
   them easier to use in the lectern code.

   I still don't think we've got a good interface here. We could
   possibly move more of the update code into PocketBrain, and just have
   PocketComputerItem responsible for syncing brain<->stack
   state. Something to fiddle with, but not sure it would be any
   cleaner.

 - Set the bottom peripheral when the pocket computer is placed in the
   lectern (and on block updates, etc...), and clear it when removed.

   Again, the code here is uglier than I would like (particularly with
   all the instanceofs). I did wonder about having some LecternBehaviour
   class, which holds the printout/pocket-specific state, but there's so
   many places we need to hook in (and they're so different between the
   two), I'm not sure it's worth it right now.

Fixes #2126, closes #2223.
This commit is contained in:
Jonathan Coates
2025-12-15 19:22:40 +00:00
parent 5f704b2c58
commit 9ad3b03946
3 changed files with 91 additions and 41 deletions
@@ -4,10 +4,14 @@
package dan200.computercraft.shared.lectern;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.ModRegistry;
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.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.stats.Stats;
import net.minecraft.util.RandomSource;
@@ -21,6 +25,7 @@ import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LecternBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -102,6 +107,16 @@ public class CustomLecternBlock extends LecternBlock {
return new ItemStack(Items.LECTERN);
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
if (!level.isClientSide() && pos.relative(Direction.DOWN).equals(neighbourPos)
&& level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern && lectern.getItem().getItem() instanceof PocketComputerItem
) {
lectern.markRefreshPeripheral();
}
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
// If we've no lectern, remove it.
@@ -133,6 +148,12 @@ public class CustomLecternBlock extends LecternBlock {
var entity = new ItemEntity(level, pos.getX() + 0.5 + dx, pos.getY() + 1, pos.getZ() + 0.5 + dz, stack);
entity.setDefaultPickUpDelay();
level.addFreshEntity(entity);
// If we're a pocket computer, update the holder and clear the peripheral.
if (stack.getItem() instanceof PocketComputerItem pocket) {
var brain = pocket.getBrain(new PocketHolder.ItemEntityHolder(entity), stack);
if (brain != null) brain.computer().setPeripheral(ComputerSide.BOTTOM, null);
}
}
@Override
@@ -4,17 +4,22 @@
package dan200.computercraft.shared.lectern;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.ModRegistry;
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.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.pocket.core.PocketBrain;
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.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.protocol.Packet;
@@ -30,6 +35,7 @@ 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.jspecify.annotations.Nullable;
import java.util.AbstractList;
import java.util.List;
@@ -46,10 +52,21 @@ public final class CustomLecternBlockEntity extends BlockEntity {
private ItemStack item = ItemStack.EMPTY;
private int page, pageCount;
private final PocketHolder.LecternHolder pocketHolder = new PocketHolder.LecternHolder(this);
private @Nullable PocketBrain activePocketBrain;
private final ComponentAccess<IPeripheral> peripherals = PlatformHelper.get().createPeripheralAccess(this, d -> markRefreshPeripheral());
private boolean refreshPeripheral;
public CustomLecternBlockEntity(BlockPos pos, BlockState blockState) {
super(ModRegistry.BlockEntities.LECTERN.get(), pos, blockState);
}
@Override
public void clearRemoved() {
refreshPeripheral = true;
super.clearRemoved();
}
public ItemStack getItem() {
return item;
}
@@ -79,11 +96,27 @@ public final class CustomLecternBlockEntity extends BlockEntity {
} else {
pageCount = page = 0;
}
activePocketBrain = null;
}
void markRefreshPeripheral() {
refreshPeripheral = true;
}
void tick() {
if (item.getItem() instanceof PocketComputerItem pocket) {
pocket.tick(item, new PocketHolder.LecternHolder(this), false);
// Get our pocket computer, and tick it.
var brain = pocket.getOrCreateBrain(pocketHolder, item);
brain.computer().keepAlive();
pocket.tick(item, pocketHolder, brain);
// Update the peripheral if the peripheral or brain has changed.
if (refreshPeripheral || brain != activePocketBrain) {
refreshPeripheral = false;
activePocketBrain = brain;
brain.computer().setPeripheral(ComputerSide.BOTTOM, peripherals.get(Direction.DOWN));
}
}
}
@@ -138,11 +171,11 @@ public final class CustomLecternBlockEntity extends BlockEntity {
new PrintoutContainerData()
), getItem().getDisplayName()));
} else if (item.getItem() instanceof PocketComputerItem pocket) {
var holder = new PocketHolder.LecternHolder(this);
var computer = pocket.getAndTurnOnServerComputer(item, holder);
var computer = pocket.getOrCreateBrain(pocketHolder, item).computer();
computer.turnOn();
PlatformHelper.get().openMenu(
player, item.getHoverName(),
(id, inv, entity) -> new PocketComputerLecternMenu(id, inv, holder, computer),
(id, inv, entity) -> new PocketComputerLecternMenu(id, inv, pocketHolder, computer),
new PocketComputerLecternMenu.Data(new ComputerContainerData(computer, item), getBlockPos())
);
}
@@ -31,7 +31,6 @@ 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;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
@@ -80,25 +79,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
/**
* Tick a pocket computer.
*
* @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.
* @param stack The current pocket computer stack.
* @param holder The entity holding the pocket item.
* @param brain The pocket brain.
*/
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();
}
public void tick(ItemStack stack, PocketHolder holder, PocketBrain brain) {
// Update pocket upgrade
var upgrade = brain.getUpgrade();
if (upgrade != null) upgrade.upgrade().update(brain, brain.computer().getPeripheral(ComputerSide.BACK));
// Sync pocket state back to the item
if (updateItem(stack, brain)) holder.setChanged();
}
@@ -150,7 +140,10 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
if (slot < 0) return;
// If we're in the inventory, create a computer and keep it alive.
tick(stack, new PocketHolder.PlayerHolder(player, slot), false);
var holder = new PocketHolder.PlayerHolder(player, slot);
var brain = getOrCreateBrain(holder, stack);
brain.computer().keepAlive();
tick(stack, holder, brain);
}
@ForgeOverride
@@ -160,7 +153,9 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
// If we're an item entity, tick an already existing computer (as to update the position), but do not keep the
// computer alive.
tick(stack, new PocketHolder.ItemEntityHolder(entity), true);
var holder = new PocketHolder.ItemEntityHolder(entity);
var brain = getBrain(holder, stack);
if (brain != null) tick(stack, holder, brain);
return false;
}
@@ -175,7 +170,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
var stack = player.getItemInHand(hand);
if (!world.isClientSide) {
var holder = new PocketHolder.PlayerHolder((ServerPlayer) player, InventoryUtil.getHandSlot(player, hand));
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
var brain = getOrCreateBrain(holder, stack);
var computer = brain.computer();
computer.turnOn();
@@ -239,8 +234,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
return ComputerCraftAPI.MOD_ID;
}
private PocketBrain getOrCreateBrain(ServerLevel level, PocketHolder holder, ItemStack stack) {
var registry = ServerContext.get(level.getServer()).registry();
/**
* Get (or create) the pocket brain and turn it on, ready for the player to interact with.
*
* @param stack The pocket computer stack.
* @param holder The holder of the pocket computer.
* @return The pocket brain.
*/
public PocketBrain getOrCreateBrain(PocketHolder holder, ItemStack stack) {
var server = holder.level().getServer();
var registry = ServerContext.get(server).registry();
{
var computer = getServerComputer(registry, stack);
if (computer != null) {
@@ -252,7 +255,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
var computerID = getComputerID(stack);
if (computerID < 0) {
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), IDAssigner.COMPUTER);
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(server, IDAssigner.COMPUTER);
setComputerID(stack, computerID);
}
@@ -275,18 +278,13 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
return brain;
}
/**
* Get (or create) the pocket server computer and turn it on, ready for the player to interact with.
*
* @param stack The pocket computer stack.
* @param holder The holder of the pocket computer.
* @return The pocket server computer.
*/
public ServerComputer getAndTurnOnServerComputer(ItemStack stack, PocketHolder holder) {
var brain = getOrCreateBrain(holder.level(), holder, stack);
var computer = brain.computer();
computer.turnOn();
return computer;
public @Nullable PocketBrain getBrain(PocketHolder holder, ItemStack stack) {
var computer = getServerComputer(holder.level().getServer(), stack);
if (computer == null) return null;
var brain = computer.getBrain();
brain.updateHolder(holder);
return brain;
}
public static boolean isServerComputer(ServerComputer computer, ItemStack stack) {
@@ -294,13 +292,11 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
&& getServerComputer(computer.getLevel().getServer(), stack) == computer;
}
@Nullable
public static PocketServerComputer getServerComputer(ServerComputerRegistry registry, ItemStack stack) {
private static @Nullable PocketServerComputer getServerComputer(ServerComputerRegistry registry, ItemStack stack) {
return (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack));
}
@Nullable
public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) {
private static @Nullable PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) {
return getServerComputer(ServerContext.get(server).registry(), stack);
}