Skip to content

Commit

Permalink
Pocket computer lecterns
Browse files Browse the repository at this point in the history
Surprisingly easy — got this done in <2 hours. It's nice when all the
refactoring you did before hand was worth it!
  • Loading branch information
SquidDev committed Feb 10, 2025
1 parent 88cb03b commit f211993
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
Expand Down Expand Up @@ -44,6 +45,10 @@ public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStac
} else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item));
}
} else if (item.getItem() instanceof PocketComputerItem) {
poseStack.mulPose(Axis.YP.rotationDegrees(-90f));
poseStack.mulPose(Axis.ZP.rotationDegrees(180f));
PocketItemRenderer.render(poseStack, buffer, item, packedLight);
}

poseStack.popPose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ private PocketItemRenderer() {

@Override
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
// Setup various transformations. Note that these are partially adapted from the corresponding method
// in ItemRenderer
transform.pushPose();
transform.mulPose(Axis.YP.rotationDegrees(180f));
transform.mulPose(Axis.ZP.rotationDegrees(180f));
transform.scale(0.5f, 0.5f, 0.5f);

render(transform, bufferSource, stack, light);

transform.popPose();
}

public static void render(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack);
var terminal = computer == null ? null : computer.getTerminal();

Expand All @@ -48,13 +61,6 @@ protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, I
var width = termWidth * FONT_WIDTH + MARGIN * 2;
var height = termHeight * FONT_HEIGHT + MARGIN * 2;

// Setup various transformations. Note that these are partially adapted from the corresponding method
// in ItemRenderer
transform.pushPose();
transform.mulPose(Axis.YP.rotationDegrees(180f));
transform.mulPose(Axis.ZP.rotationDegrees(180f));
transform.scale(0.5f, 0.5f, 0.5f);

var scale = 0.75f / Math.max(width + BORDER * 2, height + BORDER * 2 + LIGHT_HEIGHT);
transform.scale(scale, scale, -1.0f);
transform.translate(-0.5 * width, -0.5 * height, 0);
Expand All @@ -77,8 +83,6 @@ protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, I
} else {
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
}

transform.popPose();
}

private static void renderFrame(Matrix4f transform, MultiBufferSource render, ComputerFamily family, int colour, int light, int width, int height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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;
Expand All @@ -14,15 +15,20 @@
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;
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}.
Expand Down Expand Up @@ -55,6 +61,27 @@ public static void replaceLectern(Level level, BlockPos pos, BlockState blockSta
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.
*
Expand Down Expand Up @@ -131,12 +158,19 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player
clearLectern(level, pos, state);
} else {
// Otherwise open the screen.
player.openMenu(lectern);
lectern.openMenu(player);
}

player.awardStat(Stats.INTERACT_WITH_LECTERN);
}

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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,25 @@
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;
Expand All @@ -39,7 +37,7 @@
*
* @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";

Expand Down Expand Up @@ -81,6 +79,12 @@ private void itemChanged() {
}
}

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.
*
Expand Down Expand Up @@ -123,24 +127,17 @@ public CompoundTag getUpdateTag() {
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();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
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;
Expand Down Expand Up @@ -56,18 +54,7 @@ public void appendHoverText(ItemStack stack, @Nullable Level world, List<Compone

@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -112,4 +123,41 @@ public void setChanged() {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected void tickServer() {
// 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);
}
}

Expand All @@ -83,9 +83,16 @@ protected void tickServer() {
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.
System.out.println("Sending " + getInstanceUUID());
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);
}
}

Expand Down
Loading

0 comments on commit f211993

Please sign in to comment.