1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-24 07:56:54 +00:00

Add a specialised menu for printouts

Rather than having a general "held-item" container, we now have a
specialised one for printouts. This now is a little more general,
supporting any container (not just the player inventory), and syncs the
current page via a data slot.

Currently this isn't especially useful, but should make it a little
easier to add lectern support in the future.
This commit is contained in:
Jonathan Coates 2024-07-27 14:21:09 +01:00
parent b7a8432cfb
commit 7e53c19d74
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
6 changed files with 228 additions and 141 deletions

View File

@ -6,15 +6,22 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.item.ItemStack;
import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.Objects;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
@ -23,40 +30,75 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
*
* @see dan200.computercraft.client.render.PrintoutRenderer
*/
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
private final boolean book;
private final int pages;
private final TextBuffer[] text;
private final TextBuffer[] colours;
private int page;
public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu> implements ContainerListener {
private PrintoutInfo printout = PrintoutInfo.DEFAULT;
private int page = 0;
public PrintoutScreen(HeldItemMenu container, Inventory player, Component title) {
public PrintoutScreen(PrintoutMenu container, Inventory player, Component title) {
super(container, player, title);
imageHeight = Y_SIZE;
}
var text = PrintoutItem.getText(container.getStack());
this.text = new TextBuffer[text.length];
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
private void setPrintout(ItemStack stack) {
var text = PrintoutItem.getText(stack);
var textBuffers = new TextBuffer[text.length];
for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
var colours = PrintoutItem.getColours(container.getStack());
this.colours = new TextBuffer[colours.length];
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
var colours = PrintoutItem.getColours(stack);
var colourBuffers = new TextBuffer[colours.length];
for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
page = 0;
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
}
@Override
protected void init() {
super.init();
menu.addSlotListener(this);
}
@Override
public void removed() {
menu.removeSlotListener(this);
}
@Override
public void slotChanged(AbstractContainerMenu menu, int slot, ItemStack stack) {
if (slot == 0) setPrintout(stack);
}
@Override
public void dataChanged(AbstractContainerMenu menu, int slot, int data) {
if (slot == PrintoutMenu.DATA_CURRENT_PAGE) page = data;
}
private void setPage(int page) {
this.page = page;
var gameMode = Objects.requireNonNull(Objects.requireNonNull(minecraft).gameMode);
gameMode.handleInventoryButtonClick(menu.containerId, PrintoutMenu.PAGE_BUTTON_OFFSET + page);
}
private void previousPage() {
if (page > 0) setPage(page - 1);
}
private void nextPage() {
if (page < printout.pages() - 1) setPage(page + 1);
}
@Override
public boolean keyPressed(int key, int scancode, int modifiers) {
if (key == GLFW.GLFW_KEY_RIGHT) {
if (page < pages - 1) page++;
nextPage();
return true;
}
if (key == GLFW.GLFW_KEY_LEFT) {
if (page > 0) page--;
previousPage();
return true;
}
@ -68,13 +110,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
if (super.mouseScrolled(x, y, delta)) return true;
if (delta < 0) {
// Scroll up goes to the next page
if (page < pages - 1) page++;
nextPage();
return true;
}
if (delta > 0) {
// Scroll down goes to the previous page
if (page > 0) page--;
previousPage();
return true;
}
@ -85,8 +127,9 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
// Draw the printout
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
renderer.endBatch();
}
@ -105,4 +148,18 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
// Skip rendering labels.
}
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
public static final PrintoutInfo DEFAULT;
static {
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
}
}
}

View File

@ -23,7 +23,6 @@ import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
@ -41,6 +40,7 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.integration.PermissionRegistry;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.media.items.RecordMedia;
@ -49,7 +49,6 @@ import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
@ -309,11 +308,8 @@ public final class ModRegistry {
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(
HeldItemContainerData::new,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
));
public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
() -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
}
static class ArgumentTypes {

View File

@ -1,68 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.common;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
public class HeldItemMenu extends AbstractContainerMenu {
private final ItemStack stack;
private final InteractionHand hand;
public HeldItemMenu(MenuType<? extends HeldItemMenu> type, int id, Player player, InteractionHand hand) {
super(type, id);
this.hand = hand;
stack = player.getItemInHand(hand).copy();
}
public ItemStack getStack() {
return stack;
}
@Override
public ItemStack quickMoveStack(Player player, int slot) {
return ItemStack.EMPTY;
}
@Override
public boolean stillValid(Player player) {
if (!player.isAlive()) return false;
var stack = player.getItemInHand(hand);
return stack == this.stack || !stack.isEmpty() && !this.stack.isEmpty() && stack.getItem() == this.stack.getItem();
}
public static class Factory implements MenuProvider {
private final MenuType<HeldItemMenu> type;
private final Component name;
private final InteractionHand hand;
public Factory(MenuType<HeldItemMenu> type, ItemStack stack, InteractionHand hand) {
this.type = type;
name = stack.getHoverName();
this.hand = hand;
}
@Override
public Component getDisplayName() {
return name;
}
@Nullable
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
return new HeldItemMenu(type, id, player, hand);
}
}
}

View File

@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.media;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.container.InvisibleSlot;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.util.Mth;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.*;
import net.minecraft.world.item.ItemStack;
import java.util.function.Predicate;
/**
* The menus for {@linkplain PrintoutItem printouts}.
* <p>
* This is a somewhat similar design to {@link LecternMenu}, which is used to read written books.
* <p>
* This holds a single slot (containing the printout), and a single data slot ({@linkplain #DATA_CURRENT_PAGE holding
* the current page}). The page is set by the client by sending a {@linkplain #clickMenuButton(Player, int) button
* press} with an index of {@link #PAGE_BUTTON_OFFSET} plus the current page.
* <p>
* The client-side screen uses {@linkplain ContainerListener container listeners} to subscribe to item and page changes.
* However, listeners aren't fired on the client, so we copy {@link LecternMenu}'s hack and call
* {@link #broadcastChanges()} whenever an item or data value are changed.
*/
public class PrintoutMenu extends AbstractContainerMenu {
public static final int DATA_CURRENT_PAGE = 0;
private static final int DATA_SIZE = 1;
public static final int PAGE_BUTTON_OFFSET = 100;
private final Predicate<Player> valid;
private final ContainerData currentPage;
public PrintoutMenu(
int containerId, Container container, int slotIdx, Predicate<Player> valid, ContainerData currentPage
) {
super(ModRegistry.Menus.PRINTOUT.get(), containerId);
this.valid = valid;
this.currentPage = currentPage;
addSlot(new InvisibleSlot(container, slotIdx) {
@Override
public void setChanged() {
super.setChanged();
slotsChanged(container); // Trigger listeners on the client.
}
});
addDataSlots(currentPage);
}
/**
* Create {@link PrintoutMenu} for use a remote (client).
*
* @param containerId The current container id.
* @return The constructed container.
*/
public static PrintoutMenu createRemote(int containerId) {
return new PrintoutMenu(containerId, new SimpleContainer(1), 0, p -> true, new SimpleContainerData(DATA_SIZE));
}
/**
* Create a {@link PrintoutMenu} for the printout in the current player's hand.
*
* @param containerId The current container id.
* @param player The player to open the container.
* @param hand The hand containing the item.
* @return The constructed container.
*/
public static PrintoutMenu createInHand(int containerId, Player player, InteractionHand hand) {
var currentStack = player.getItemInHand(hand);
var currentItem = currentStack.getItem();
var slot = switch (hand) {
case MAIN_HAND -> player.getInventory().selected;
case OFF_HAND -> Inventory.SLOT_OFFHAND;
};
return new PrintoutMenu(
containerId, player.getInventory(), slot,
p -> player.getItemInHand(hand).getItem() == currentItem, new SimpleContainerData(DATA_SIZE)
);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean stillValid(Player player) {
return valid.test(player);
}
@Override
public boolean clickMenuButton(Player player, int id) {
if (id >= PAGE_BUTTON_OFFSET) {
var page = Mth.clamp(id - PAGE_BUTTON_OFFSET, 0, PrintoutItem.getPageCount(getPrintout()) - 1);
setData(DATA_CURRENT_PAGE, page);
return true;
}
return super.clickMenuButton(player, id);
}
/**
* Get the current printout.
*
* @return The current printout.
*/
public ItemStack getPrintout() {
return getSlot(0).getItem();
}
/**
* Get the current page.
*
* @return The current page.
*/
public int getPage() {
return currentPage.get(DATA_CURRENT_PAGE);
}
@Override
public void setData(int id, int data) {
super.setData(id, data);
broadcastChanges(); // Trigger listeners on the client.
}
}

View File

@ -4,13 +4,14 @@
package dan200.computercraft.shared.media.items;
import com.google.common.base.Strings;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import dan200.computercraft.shared.media.PrintoutMenu;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -51,11 +52,13 @@ public class PrintoutItem extends Item {
@Override
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
var stack = player.getItemInHand(hand);
if (!world.isClientSide) {
new HeldItemContainerData(hand)
.open(player, new HeldItemMenu.Factory(ModRegistry.Menus.PRINTOUT.get(), player.getItemInHand(hand), hand));
var title = getTitle(stack);
var displayTitle = Strings.isNullOrEmpty(title) ? stack.getDisplayName() : Component.literal(title);
player.openMenu(new SimpleMenuProvider((id, playerInventory, p) -> PrintoutMenu.createInHand(id, p, hand), displayTitle));
}
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
}
private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {

View File

@ -1,37 +0,0 @@
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.network.container;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.InteractionHand;
/**
* Opens a printout GUI based on the currently held item.
*
* @see HeldItemMenu
* @see PrintoutItem
*/
public class HeldItemContainerData implements ContainerData {
private final InteractionHand hand;
public HeldItemContainerData(InteractionHand hand) {
this.hand = hand;
}
public HeldItemContainerData(FriendlyByteBuf buffer) {
hand = buffer.readEnum(InteractionHand.class);
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeEnum(hand);
}
public InteractionHand getHand() {
return hand;
}
}