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:
parent
0f123b5efd
commit
2e2f308ff3
@ -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;
|
||||
@ -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 |
Loading…
x
Reference in New Issue
Block a user