diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java index 9bed31a55..555b4aab2 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java @@ -4,6 +4,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import li.cil.oc2.common.vm.tablet.TabletState; import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Optional; @@ -28,6 +29,13 @@ public interface ItemDeviceQuery { */ Optional getContainerEntity(); + /** + * The {@link TabletState} that holds the item this query is for. + * + * @return the {@link TabletState} hosting the device, if any. + */ + Optional getContainerTabletState(); + /** * The item stack this query is performed for. * diff --git a/src/main/java/li/cil/oc2/client/ClientSetup.java b/src/main/java/li/cil/oc2/client/ClientSetup.java index d277e22fe..d66541e47 100644 --- a/src/main/java/li/cil/oc2/client/ClientSetup.java +++ b/src/main/java/li/cil/oc2/client/ClientSetup.java @@ -2,6 +2,7 @@ package li.cil.oc2.client; +import li.cil.oc2.api.API; import li.cil.oc2.client.gui.*; import li.cil.oc2.client.item.CustomItemColors; import li.cil.oc2.client.item.CustomItemModelProperties; @@ -17,8 +18,13 @@ import li.cil.oc2.common.config.Config; import li.cil.oc2.common.container.Containers; import li.cil.oc2.common.entity.Entities; +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.item.TabletItem; import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; +import net.minecraft.client.renderer.item.ItemProperties; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.event.EntityRenderersEvent; import net.minecraftforge.client.event.ModelEvent.RegisterGeometryLoaders; import net.minecraftforge.client.event.RegisterColorHandlersEvent; @@ -53,8 +59,15 @@ public static void handleSetupEvent(final FMLClientSetupEvent event) { MenuScreens.register(Containers.MONITOR.get(), MonitorDisplayScreen::new); MenuScreens.register(Containers.ROBOT.get(), RobotContainerScreen::new); MenuScreens.register(Containers.ROBOT_TERMINAL.get(), RobotTerminalScreen::new); + MenuScreens.register(Containers.TABLET.get(), TabletContainerScreen::new); + MenuScreens.register(Containers.TABLET_TERMINAL.get(), TabletTerminalScreen::new); MenuScreens.register(Containers.NETWORK_TUNNEL.get(), NetworkTunnelScreen::new); + ItemProperties.register(Items.TABLET.get(), ResourceLocation.fromNamespaceAndPath(API.MOD_ID, TabletItem.TABLET_ON_TAG_NAME), (stack, level, living, id) -> { + if (stack.getTag() == null) return 0.0F; + return stack.getTag().getBoolean(TabletItem.TABLET_ON_TAG_NAME) ? 1.0F : 0.0F; + }); + // We need to register this manually, because static init throws errors when running data generation. MinecraftForge.EVENT_BUS.register(ProjectorDepthRenderer.class); }); diff --git a/src/main/java/li/cil/oc2/client/TabletStateCache.java b/src/main/java/li/cil/oc2/client/TabletStateCache.java new file mode 100644 index 000000000..c3b9c9d3e --- /dev/null +++ b/src/main/java/li/cil/oc2/client/TabletStateCache.java @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.client; + +import li.cil.oc2.common.vm.tablet.TabletState; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class TabletStateCache { + private static final Map CACHE = new HashMap<>(); + + public static TabletState getOrCreateTabletState(UUID id) { + if (CACHE.containsKey(id)) { + return CACHE.get(id); + } + + TabletState state = new TabletState(id, true); + CACHE.put(id, state); + return state; + } + + public static void remove(UUID id) { + CACHE.remove(id); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/TabletContainerScreen.java b/src/main/java/li/cil/oc2/client/gui/TabletContainerScreen.java new file mode 100644 index 000000000..e13177f0a --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/TabletContainerScreen.java @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.client.gui; + +import li.cil.oc2.common.container.TabletInventoryContainer; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class TabletContainerScreen extends AbstractMachineInventoryScreen { + public TabletContainerScreen(final TabletInventoryContainer container, final Inventory inventory, final Component title) { + super(container, inventory, title); + imageWidth = Sprites.COMPUTER_CONTAINER.width; + imageHeight = Sprites.COMPUTER_CONTAINER.height; + inventoryLabelY = imageHeight - 94; + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void renderBg(final GuiGraphics graphics, final float partialTicks, final int mouseX, final int mouseY) { + super.renderBg(graphics, partialTicks, mouseX, mouseY); + Sprites.COMPUTER_CONTAINER.draw(graphics, leftPos, topPos); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/TabletTerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/TabletTerminalScreen.java new file mode 100644 index 000000000..e88cc8ac6 --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/TabletTerminalScreen.java @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.client.gui; + +import li.cil.oc2.common.container.TabletTerminalContainer; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class TabletTerminalScreen extends AbstractMachineTerminalScreen { + @SuppressWarnings("all") private EditBox focusIndicatorEditBox; + + /////////////////////////////////////////////////////////////////// + + public TabletTerminalScreen(final TabletTerminalContainer container, final Inventory playerInventory, final Component title) { + super(container, playerInventory, title); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void setFocusIndicatorEditBox(final EditBox editBox) { + focusIndicatorEditBox = editBox; + } +} + diff --git a/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java index b5578f458..c866fc7f6 100644 --- a/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java +++ b/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java @@ -224,7 +224,7 @@ public void handleUpdateTag(final CompoundTag tag) { super.handleUpdateTag(tag); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); - + // Only update client-side state on the client if (level != null && level.isClientSide()) { virtualMachine.setBusStateClient(CommonDeviceBusController.BusState.values()[tag.getInt(AbstractVirtualMachine.BUS_STATE_TAG_NAME)]); diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/item/FileImportExportCardItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/item/FileImportExportCardItemDeviceProvider.java index 051e3218f..6b9dc332e 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/provider/item/FileImportExportCardItemDeviceProvider.java +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/item/FileImportExportCardItemDeviceProvider.java @@ -56,6 +56,13 @@ private Optional getTerminalUserProvider(final ItemDeviceQ } } + if (query.getContainerTabletState().isPresent()) { + final LazyOptional capability = + LazyOptional.of(() -> query.getContainerTabletState().get()).cast(); + + return capability.resolve(); + } + return Optional.empty(); } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/util/Devices.java b/src/main/java/li/cil/oc2/common/bus/device/util/Devices.java index 69ed68f66..5f88bdad0 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/util/Devices.java +++ b/src/main/java/li/cil/oc2/common/bus/device/util/Devices.java @@ -2,6 +2,7 @@ package li.cil.oc2.common.bus.device.util; +import li.cil.oc2.common.vm.tablet.TabletState; import li.cil.oc2.api.bus.device.Device; import li.cil.oc2.api.bus.device.ItemDevice; import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider; @@ -52,6 +53,10 @@ public static ItemDeviceQuery makeQuery(final Entity entity, final ItemStack sta return new ItemQuery(entity, stack); } + public static ItemDeviceQuery makeQuery(final TabletState item, final ItemStack stack) { + return new ItemQuery(item, stack); + } + public static Optional>> getDevices(final BlockDeviceQuery query) { final ChunkPos queryChunk = new ChunkPos(query.getQueryPosition()); if (!query.getLevel().hasChunk(queryChunk.x, queryChunk.z)) { @@ -134,18 +139,23 @@ public Direction getQuerySide() { private record ItemQuery( @Nullable BlockEntity blockEntity, @Nullable Entity entity, + @Nullable TabletState item, ItemStack stack ) implements ItemDeviceQuery { public ItemQuery(final ItemStack stack) { - this(null, null, stack); + this(null, null, null, stack); } public ItemQuery(final BlockEntity blockEntity, final ItemStack stack) { - this(blockEntity, null, stack); + this(blockEntity, null, null, stack); } public ItemQuery(final Entity entity, final ItemStack stack) { - this(null, entity, stack); + this(null, entity, null, stack); + } + + public ItemQuery(final TabletState item, final ItemStack stack) { + this(null, null, item, stack); } @Override @@ -158,6 +168,11 @@ public Optional getContainerEntity() { return Optional.ofNullable(entity); } + @Override + public Optional getContainerTabletState() { + return Optional.ofNullable(item); + } + @Override public ItemStack getItemStack() { return stack; diff --git a/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java b/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java index f285b50bf..c7529cb79 100644 --- a/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java +++ b/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java @@ -8,6 +8,7 @@ import li.cil.oc2.api.capabilities.RedstoneEmitter; import li.cil.oc2.api.capabilities.Robot; import li.cil.oc2.api.capabilities.TerminalUserProvider; +import li.cil.oc2.common.vm.tablet.ITabletData; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.energy.IEnergyStorage; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -52,6 +53,10 @@ public static Capability robot() { return CapabilityRegistry.ROBOT; } + public static Capability tabletData() { + return CapabilityRegistry.TABLET_DATA; + } + public static void registerCapabilities(final Consumer> registry) { registry.accept(DeviceBusElement.class); registry.accept(Device.class); diff --git a/src/main/java/li/cil/oc2/common/capabilities/CapabilityRegistry.java b/src/main/java/li/cil/oc2/common/capabilities/CapabilityRegistry.java index 8c1067e8f..98b660947 100644 --- a/src/main/java/li/cil/oc2/common/capabilities/CapabilityRegistry.java +++ b/src/main/java/li/cil/oc2/common/capabilities/CapabilityRegistry.java @@ -9,6 +9,7 @@ import li.cil.oc2.api.capabilities.RedstoneEmitter; import li.cil.oc2.api.capabilities.Robot; import li.cil.oc2.api.capabilities.TerminalUserProvider; +import li.cil.oc2.common.vm.tablet.ITabletData; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.common.capabilities.CapabilityToken; @@ -32,6 +33,8 @@ public final class CapabilityRegistry { static final Capability TERMINAL_USER_PROVIDER = CapabilityManager.get(new CapabilityToken<>() { }); static final Capability ROBOT = CapabilityManager.get(new CapabilityToken<>() { }); + static final Capability TABLET_DATA = CapabilityManager.get(new CapabilityToken<>() { }); + /////////////////////////////////////////////////////////////////// @SubscribeEvent diff --git a/src/main/java/li/cil/oc2/common/config/Config.java b/src/main/java/li/cil/oc2/common/config/Config.java index ae4a9898f..6ad0cc903 100644 --- a/src/main/java/li/cil/oc2/common/config/Config.java +++ b/src/main/java/li/cil/oc2/common/config/Config.java @@ -14,7 +14,7 @@ @SuppressWarnings("FieldMayBeFinal") public final class Config { - //TODO: Implement configuration of CPU MHzs + // TODO: Implement configuration of CPU MHzs public static long maxAllocatedMemory = 512 * Constants.MEGABYTE; public static int diskSizeFactor = 2 * Constants.MEGABYTE; @@ -36,6 +36,9 @@ public final class Config { public static int robotEnergyPerTick = 5; public static int robotEnergyStorage = 750000; + public static int tabletEnergyPerTick = 1; + public static int tabletEnergyStorage = 750000; + public static double memoryEnergyPerMegabytePerTick = 0.5; public static double hardDriveEnergyPerMegabytePerTick = 1; public static double cpuEnergyPerMegahertzPerTick = 0.1; @@ -65,8 +68,8 @@ public final class Config { public static int defaultSessionsNumberPerCardLimit = 10; public static int defaultSessionsNumberLimit = 100; public static int defaultEchoRequestTimeoutMs = 1000; - public static List deniedHosts = - Arrays.asList("127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "172.16.0.0/12", "192.168.0.0/16", "224.0.0.0/4"); + public static List deniedHosts = Arrays.asList("127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", + "172.16.0.0/12", "192.168.0.0/16", "224.0.0.0/4"); public static List allowedHosts = List.of(); public static String defaultNameServer = "1.1.1.1"; public static boolean useSynchronisedNAT = false; @@ -92,9 +95,14 @@ public static boolean robotsUseEnergy() { return robotEnergyPerTick > 0 && robotEnergyStorage > 0; } + public static boolean tabletsUseEnergy() { + return tabletEnergyPerTick > 0 && tabletEnergyStorage > 0; + } + public static boolean monitorsUseEnergy() { return computerEnergyPerTick > 0 && computerEnergyStorage > 0; } + public static boolean gatewayUseEnergy() { return gatewayEnergyPerPacket > 0 && gatewayEnergyStorage > 0; } diff --git a/src/main/java/li/cil/oc2/common/container/AbstractTabletContainer.java b/src/main/java/li/cil/oc2/common/container/AbstractTabletContainer.java new file mode 100644 index 000000000..bb145fd2b --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/AbstractTabletContainer.java @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.container; + +import static li.cil.oc2.common.network.MessageUtils.findItemByUUID; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import li.cil.oc2.client.ClientSetup; +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.bus.CommonDeviceBusController; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.config.Config; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.OpenTabletInventoryMessage; +import li.cil.oc2.common.network.message.OpenTabletTerminalMessage; +import li.cil.oc2.common.network.message.TabletPowerMessage; +import li.cil.oc2.common.network.message.TabletTerminalInputMessage; +import li.cil.oc2.common.vm.VirtualMachine; +import li.cil.oc2.common.vm.tablet.TabletState; +import li.cil.oc2.common.vm.terminal.Terminal; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraftforge.energy.IEnergyStorage; + +public abstract class AbstractTabletContainer extends AbstractMachineTerminalContainer { + private final UUID tabletId; + private final Player player; + private static boolean captureInputState = Config.captureInputDefaultState; + + /////////////////////////////////////////////////////////////////// + + public AbstractTabletContainer(final MenuType type, final int id, final UUID tabletId, final Player player, + final IntPrecisionContainerData energyInfo) { + super(type, id, energyInfo); + this.tabletId = tabletId; + this.player = player; + } + + /////////////////////////////////////////////////////////////////// + + public static TabletState getTabletState(UUID id, Player player) { + if (player.level() != null && !player.level().isClientSide() && player instanceof ServerPlayer serverPlayer) { + return serverPlayer.getServer().getLevel(Level.OVERWORLD).getCapability(Capabilities.tabletData()) + .map(tabletData -> tabletData.get(id)).orElse(null); + } + + return TabletStateCache.getOrCreateTabletState(id); + } + + @Override + public void switchToInventory() { + Network.sendToServer(new OpenTabletInventoryMessage(tabletId)); + } + + @Override + public void switchToTerminal() { + Network.sendToServer(new OpenTabletTerminalMessage(tabletId)); + } + + @Override + public VirtualMachine getVirtualMachine() { + return getTabletState(tabletId, player).getVirtualMachine(); + } + + @Override + public void sendPowerStateToServer(final boolean value) { + Network.sendToServer(new TabletPowerMessage(tabletId, value)); + } + + @Override + public Terminal getTerminal() { + return getTabletState(tabletId, player).getTerminal(); + } + + @Override + public boolean getCaptureInputState() { + return switch (Config.captureInputMode) { + case PER_BLOCK -> getTabletState(tabletId, player).getCaptureInputState(); + case SHARED_BETWEEN_TYPE -> captureInputState; + case GLOBAL_CAPTURE -> ClientSetup.getCaptureInputState(); + }; + } + + @Override + public void setCaptureInputState(final boolean state) { + switch (Config.captureInputMode) { + case PER_BLOCK -> getTabletState(tabletId, player).setCaptureInputState(state); + case SHARED_BETWEEN_TYPE -> captureInputState = state; + case GLOBAL_CAPTURE -> ClientSetup.setCaptureInputState(state); + } + } + + @Override + public void sendTerminalInputToServer(final ByteBuffer input) { + Network.sendToServer(new TabletTerminalInputMessage(tabletId, input)); + } + + @Override + public boolean stillValid(final Player player) { + return findItemByUUID(player, tabletId) != ItemStack.EMPTY; + } + + @Override + public void removed(final Player player) { + super.removed(player); + + getTabletState(tabletId, player).setTerminalUser(null); + } + + /////////////////////////////////////////////////////////////////// + + protected static IntPrecisionContainerData createEnergyInfo(final IEnergyStorage energy, + final CommonDeviceBusController busController) { + return new IntPrecisionContainerData.Server() { + @Override + public int getInt(final int index) { + return switch (index) { + case AbstractMachineContainer.ENERGY_STORED_INDEX -> energy.getEnergyStored(); + case AbstractMachineContainer.ENERGY_CAPACITY_INDEX -> energy.getMaxEnergyStored(); + case AbstractMachineContainer.ENERGY_CONSUMPTION_INDEX -> busController.getEnergyConsumption(); + default -> 0; + }; + } + + @Override + public int getIntCount() { + return ENERGY_INFO_SIZE; + } + }; + } +} diff --git a/src/main/java/li/cil/oc2/common/container/Containers.java b/src/main/java/li/cil/oc2/common/container/Containers.java index a63134c99..dee153988 100644 --- a/src/main/java/li/cil/oc2/common/container/Containers.java +++ b/src/main/java/li/cil/oc2/common/container/Containers.java @@ -20,6 +20,8 @@ public final class Containers { public static final RegistryObject> MONITOR = CONTAINERS.register("monitor", () -> IForgeMenuType.create(MonitorDisplayContainer::createClient)); public static final RegistryObject> ROBOT = CONTAINERS.register("robot", () -> IForgeMenuType.create(RobotInventoryContainer::createClient)); public static final RegistryObject> ROBOT_TERMINAL = CONTAINERS.register("robot_terminal", () -> IForgeMenuType.create(RobotTerminalContainer::createClient)); + public static final RegistryObject> TABLET = CONTAINERS.register("tablet", () -> IForgeMenuType.create(TabletInventoryContainer::createClient)); + public static final RegistryObject> TABLET_TERMINAL = CONTAINERS.register("tablet_terminal", () -> IForgeMenuType.create(TabletTerminalContainer::createClient)); public static final RegistryObject> NETWORK_TUNNEL = CONTAINERS.register("network_tunnel", () -> IForgeMenuType.create(NetworkTunnelContainer::createClient)); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/container/TabletInventoryContainer.java b/src/main/java/li/cil/oc2/common/container/TabletInventoryContainer.java new file mode 100644 index 000000000..6bea0e2c0 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/TabletInventoryContainer.java @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.container; + +import java.util.UUID; + +import li.cil.oc2.api.bus.device.DeviceTypes; +import li.cil.oc2.common.item.TabletItem; +import li.cil.oc2.common.vm.VMItemStackHandlers; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +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.item.ItemStack; +import net.minecraftforge.network.NetworkHooks; + +public final class TabletInventoryContainer extends AbstractTabletContainer { + private final UUID tabletId; + + public static void createServer(UUID tabletId, final ServerPlayer player) { + NetworkHooks.openScreen(player, new MenuProvider() { + @Override + public Component getDisplayName() { + return Component.translatable("item.oc2r.tablet"); + } + + @Override + public AbstractContainerMenu createMenu(final int id, final Inventory inventory, final Player player) { + TabletState state = getTabletState(tabletId, player); + + return new TabletInventoryContainer(id, tabletId, player, + createEnergyInfo(state.getEnergy(), state.getBusController())); + } + }, b -> b.writeUUID(tabletId)); + } + + public static TabletInventoryContainer createClient(final int id, final Inventory inventory, + final FriendlyByteBuf data) { + final UUID tabletId = data.readUUID(); + + return new TabletInventoryContainer(id, tabletId, inventory.player, createClientEnergyInfo()); + } + + /////////////////////////////////////////////////////////////////// + + private TabletInventoryContainer(final int id, final UUID tabletId, final Player player, + final IntPrecisionContainerData energyInfo) { + super(Containers.TABLET.get(), id, tabletId, player, energyInfo); + + this.tabletId = tabletId; + + final VMItemStackHandlers handlers = getTabletState(tabletId, player).getDeviceItems(); + + handlers.getItemHandler(DeviceTypes.FLASH_MEMORY).ifPresent(itemHandler -> { + if (itemHandler.getSlots() > 0) { + addSlot(new DeviceTypeSlotItemHandler(itemHandler, DeviceTypes.FLASH_MEMORY, 0, 64, 78)); + } + }); + + handlers.getItemHandler(DeviceTypes.MEMORY).ifPresent(itemHandler -> { + for (int slot = 0; slot < itemHandler.getSlots(); slot++) { + addSlot(new DeviceTypeSlotItemHandler(itemHandler, DeviceTypes.MEMORY, slot, 64 + slot * SLOT_SIZE, + 24)); + } + }); + + handlers.getItemHandler(DeviceTypes.HARD_DRIVE).ifPresent(itemHandler -> { + for (int slot = 0; slot < itemHandler.getSlots(); slot++) { + addSlot(new DeviceTypeSlotItemHandler(itemHandler, DeviceTypes.HARD_DRIVE, slot, + 100 + (slot % 2) * SLOT_SIZE, 60 + (slot / 2) * SLOT_SIZE)); + } + }); + + handlers.getItemHandler(DeviceTypes.CARD).ifPresent(itemHandler -> { + for (int slot = 0; slot < itemHandler.getSlots(); slot++) { + addSlot(new DeviceTypeSlotItemHandler(itemHandler, DeviceTypes.CARD, slot, 38, 24 + slot * SLOT_SIZE)); + } + }); + + handlers.getItemHandler(DeviceTypes.CPU).ifPresent(itemHandler -> { + if (itemHandler.getSlots() > 0) { + addSlot(new DeviceTypeSlotItemHandler(itemHandler, DeviceTypes.CPU, 0, 64, 52)); + } + }); + + createPlayerInventoryAndHotbarSlots(player.getInventory(), 8, 115); + } + + @Override + protected boolean isSlotLocked(final Inventory inventory, final int slot) { + ItemStack stack = inventory.getItem(slot); + if (stack != null) { + CompoundTag tag = stack.getOrCreateTag(); + return tag.hasUUID(TabletItem.UUID_TAG_NAME) && tag.getUUID(TabletItem.UUID_TAG_NAME).equals(tabletId); + } + return false; + } +} diff --git a/src/main/java/li/cil/oc2/common/container/TabletTerminalContainer.java b/src/main/java/li/cil/oc2/common/container/TabletTerminalContainer.java new file mode 100644 index 000000000..dfbf0a3d2 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/TabletTerminalContainer.java @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.container; + +import java.util.UUID; + +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +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.minecraftforge.network.NetworkHooks; + +public final class TabletTerminalContainer extends AbstractTabletContainer { + public static void createServer(UUID tabletId, final ServerPlayer player) { + NetworkHooks.openScreen(player, new MenuProvider() { + @Override + public Component getDisplayName() { + return Component.translatable("item.oc2r.tablet"); + } + + @Override + public AbstractContainerMenu createMenu(final int id, final Inventory inventory, final Player player) { + TabletState state = getTabletState(tabletId, player); + + return new TabletTerminalContainer(id, tabletId, player, + createEnergyInfo(state.getEnergy(), state.getBusController())); + } + }, b -> b.writeUUID(tabletId)); + } + + public static TabletTerminalContainer createClient(final int id, final Inventory inventory, final FriendlyByteBuf data) { + final UUID tabletId = data.readUUID(); + + return new TabletTerminalContainer(id, tabletId, inventory.player, createClientEnergyInfo()); + } + + /////////////////////////////////////////////////////////////////// + + private TabletTerminalContainer(final int id, final UUID tabletId, final Player player, final IntPrecisionContainerData energyInfo) { + super(Containers.TABLET_TERMINAL.get(), id, tabletId, player, energyInfo); + } +} + diff --git a/src/main/java/li/cil/oc2/common/energy/EnergyStorageItemStack.java b/src/main/java/li/cil/oc2/common/energy/EnergyStorageItemStack.java index c3a902ffc..d2852ca3e 100644 --- a/src/main/java/li/cil/oc2/common/energy/EnergyStorageItemStack.java +++ b/src/main/java/li/cil/oc2/common/energy/EnergyStorageItemStack.java @@ -41,7 +41,13 @@ public int receiveEnergy(final int maxReceive, final boolean simulate) { @Override public int extractEnergy(final int maxExtract, final boolean simulate) { - return 0; + final int stored = getEnergyStored(); + final int extract = Math.min(maxExtract, stored); + if (!simulate) { + NBTUtils.getOrCreateChildTag(stack.getOrCreateTag(), tagPath) + .putInt(FixedEnergyStorage.STORED_TAG_NAME, stored - extract); + } + return extract; } @Override @@ -56,7 +62,7 @@ public int getMaxEnergyStored() { @Override public boolean canExtract() { - return false; // We don't want our items to be usable as batteries. + return true; } @Override diff --git a/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java b/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java index b5b49f314..10863bd64 100644 --- a/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java +++ b/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java @@ -1,7 +1,6 @@ package li.cil.oc2.common.event; import li.cil.oc2.api.API; -import li.cil.oc2.common.config.AsyncConfig; import li.cil.oc2.common.util.AsyncUtils; import net.minecraft.server.MinecraftServer; import net.minecraftforge.event.server.ServerAboutToStartEvent; diff --git a/src/main/java/li/cil/oc2/common/event/LevelCapabilityEvents.java b/src/main/java/li/cil/oc2/common/event/LevelCapabilityEvents.java new file mode 100644 index 000000000..01ff2ac2f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/event/LevelCapabilityEvents.java @@ -0,0 +1,49 @@ +package li.cil.oc2.common.event; + +import java.util.Iterator; + +import li.cil.oc2.api.API; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.vm.tablet.TabletDataProvider; +import li.cil.oc2.common.vm.tablet.TabletState; +import li.cil.oc2.common.vm.tablet.TabletVirtualMachine; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = API.MOD_ID) +public class LevelCapabilityEvents { + public static final String TABLET_STATE_DATA = "tablet_state_data"; + + @SubscribeEvent + public static void attach(AttachCapabilitiesEvent event) { + if (event.getObject().dimension() == Level.OVERWORLD) { + event.addCapability(ResourceLocation.fromNamespaceAndPath(API.MOD_ID, TABLET_STATE_DATA), new TabletDataProvider()); + } + } + + + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + MinecraftServer server = event.getServer(); + + ServerLevel overworld = server.getLevel(Level.OVERWORLD); + if (overworld != null) { + overworld.getCapability(Capabilities.tabletData()).ifPresent(tabletData -> { + Iterator it = tabletData.getAll().iterator(); + + while (it.hasNext()) { + TabletState state = it.next(); + TabletVirtualMachine vm = state.getVirtualMachine(); + vm.suspend(); + vm.dispose(); + } + }); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/event/LevelTickEvent.java b/src/main/java/li/cil/oc2/common/event/LevelTickEvent.java new file mode 100644 index 000000000..bc997cfef --- /dev/null +++ b/src/main/java/li/cil/oc2/common/event/LevelTickEvent.java @@ -0,0 +1,30 @@ +package li.cil.oc2.common.event; + +import java.util.Iterator; + +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.world.level.Level; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber +public class LevelTickEvent { + @SubscribeEvent + public static void onLevelTick(TickEvent.LevelTickEvent event) { + if (!event.level.isClientSide() && event.phase == TickEvent.Phase.END) { + if (event.level.dimension() == Level.OVERWORLD) { + event.level.getCapability(Capabilities.tabletData()).ifPresent(tabletData -> { + Iterator it = tabletData.getAll().iterator(); + + while (it.hasNext()) { + TabletState state = it.next(); + + state.getVirtualMachine().tick(); + } + }); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/item/ItemGroup.java b/src/main/java/li/cil/oc2/common/item/ItemGroup.java index 5f0accf5b..4bb559c94 100644 --- a/src/main/java/li/cil/oc2/common/item/ItemGroup.java +++ b/src/main/java/li/cil/oc2/common/item/ItemGroup.java @@ -12,7 +12,8 @@ import net.minecraftforge.registries.RegistryObject; public final class ItemGroup { - public static final DeferredRegister TAB_REGISTER = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, API.MOD_ID); + public static final DeferredRegister TAB_REGISTER = DeferredRegister + .create(Registries.CREATIVE_MODE_TAB, API.MOD_ID); @SuppressWarnings("unused") public static final RegistryObject COMMON_TAB = TAB_REGISTER.register("common", () -> CreativeModeTab.builder() @@ -40,6 +41,7 @@ public final class ItemGroup { output.accept(Items.WRENCH.get()); output.accept(Items.MANUAL.get()); output.accept(RobotItem.getRobotWithFlash()); + output.accept(TabletItem.getTabletWithFlash()); output.accept(Items.NETWORK_CABLE.get()); output.accept(Items.MEMORY_SMALL.get()); output.accept(Items.MEMORY_MEDIUM.get()); diff --git a/src/main/java/li/cil/oc2/common/item/Items.java b/src/main/java/li/cil/oc2/common/item/Items.java index 82bb72242..648a2b8ee 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -106,6 +106,8 @@ public final class Items { public static final RegistryObject RAW_SILICON_WAFER = register("raw_silicon_wafer", ModItem::new); public static final RegistryObject CIRCUIT_BOARD = register("circuit_board", ModItem::new); + public static final RegistryObject TABLET = register("tablet", TabletItem::new); + /////////////////////////////////////////////////////////////////// public static void initialize(FMLJavaModLoadingContext context) { @@ -126,7 +128,8 @@ private static RegistryObject register(final RegistryObj return register(block, ModBlockItem::new); } - private static RegistryObject register(final RegistryObject block, final Function factory) { + private static RegistryObject register( + final RegistryObject block, final Function factory) { return register(block.getId().getPath(), () -> factory.apply(block.get())); } } diff --git a/src/main/java/li/cil/oc2/common/item/TabletItem.java b/src/main/java/li/cil/oc2/common/item/TabletItem.java new file mode 100644 index 000000000..312a0189e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/item/TabletItem.java @@ -0,0 +1,293 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.item; + +import static li.cil.oc2.common.util.NBTUtils.makeInventoryTag; +import static li.cil.oc2.common.util.RegistryUtils.key; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; + +import static li.cil.oc2.common.Constants.*; + +import li.cil.oc2.api.bus.device.DeviceTypes; +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.bus.CommonDeviceBusController.BusState; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.config.Config; +import li.cil.oc2.common.container.TabletInventoryContainer; +import li.cil.oc2.common.container.TabletTerminalContainer; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.TabletClientUpdateMessage; +import li.cil.oc2.common.network.message.TabletEnergyMessage; +import li.cil.oc2.common.network.message.TabletItemsMessage; +import li.cil.oc2.common.serialization.NBTSerialization; +import li.cil.oc2.common.util.NBTUtils; +import li.cil.oc2.common.util.TooltipUtils; +import li.cil.oc2.common.vm.VMRunState; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +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.InteractionResultHolder; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fml.util.thread.EffectiveSide; +import net.minecraftforge.server.ServerLifecycleHooks; + +public class TabletItem extends ModItem { + public static final String UUID_TAG_NAME = "uuid"; + public static final String TABLET_ON_TAG_NAME = "tablet_on"; + + private static final String TERMINAL_TAG_NAME = "terminal"; + + public static UUID getOrCreateUUID(final ItemStack stack) { + CompoundTag tag = stack.getOrCreateTag(); + + if (!tag.hasUUID(UUID_TAG_NAME)) { + UUID id = UUID.randomUUID(); + tag.putUUID(UUID_TAG_NAME, id); + return id; + } + + return tag.getUUID(UUID_TAG_NAME); + } + + public TabletItem() { + super(createProperties().stacksTo(1)); + } + + public static void openTerminalScreen(final ServerPlayer player, final UUID id) { + if (player.level() != null && !player.level().isClientSide()) { + player.getServer().getLevel(Level.OVERWORLD).getCapability(Capabilities.tabletData()) + .ifPresent(tabletData -> { + TabletTerminalContainer.createServer(id, player); + }); + } + } + + public static void openInventoryScreen(final ServerPlayer player, final UUID id) { + if (player.level() != null && !player.level().isClientSide()) { + player.getServer().getLevel(Level.OVERWORLD).getCapability(Capabilities.tabletData()) + .ifPresent(tabletData -> { + TabletInventoryContainer.createServer(id, player); + }); + } + } + + public static void start(final Level level, final UUID id) { + if (level != null && !level.isClientSide() && level.dimension() == Level.OVERWORLD) { + level.getCapability(Capabilities.tabletData()) + .ifPresent(tabletData -> tabletData.get(id).getVirtualMachine().start()); + } + } + + public static void stop(final Level level, final UUID id) { + if (level != null && !level.isClientSide() && level.dimension() == Level.OVERWORLD) { + level.getCapability(Capabilities.tabletData()) + .ifPresent(tabletData -> tabletData.get(id).getVirtualMachine().stop()); + } + } + + @Override + public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, boolean selected) { + if (level.isClientSide() && stack.getOrCreateTag().hasUUID(TabletItem.UUID_TAG_NAME)) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(getOrCreateUUID(stack)); + tablet.getTerminal().clientTick(); + } + } + + @Override + public void appendHoverText(final ItemStack stack, @Nullable final Level level, final List tooltip, + final TooltipFlag flag) { + super.appendHoverText(stack, level, tooltip, flag); + TooltipUtils.addEnergyConsumption(Config.tabletEnergyPerTick, tooltip); + TooltipUtils.addEntityEnergyInformation(stack, tooltip); + TooltipUtils.addEntityInventoryInformation(stack, tooltip); + } + + @Nullable + @Override + public ICapabilityProvider initCapabilities(final ItemStack stack, @Nullable final CompoundTag nbt) { + return new TabletItemStackCapabilityProvider(stack); + } + + private class TabletItemStackCapabilityProvider implements ICapabilitySerializable { + private ItemStack stack; + + public TabletItemStackCapabilityProvider(ItemStack stack) { + this.stack = stack; + } + + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability cap, + @Nullable Direction side) { + if (!stack.getOrCreateTag().hasUUID(TabletItem.UUID_TAG_NAME)) { + return LazyOptional.empty(); + } + + UUID id = stack.getTag().getUUID(TabletItem.UUID_TAG_NAME); + + TabletState state; + + if (EffectiveSide.get().isServer()) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + ServerLevel overworld = server.getLevel(Level.OVERWORLD); + state = overworld.getCapability(Capabilities.tabletData()).map(tabletData -> tabletData.get(id)) + .orElse(null); + + if (state == null) + return LazyOptional.empty(); + } else { + state = TabletStateCache.getOrCreateTabletState(id); + } + + if (cap == Capabilities.itemHandler()) { + return LazyOptional.of(() -> state.getDeviceItems()).cast(); + } + if (cap == Capabilities.energyStorage() && Config.tabletsUseEnergy()) { + return LazyOptional.of(() -> state.getEnergy()).cast(); + } + + return LazyOptional.empty(); + } + + @Override + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + + if (!stack.getOrCreateTag().hasUUID(TabletItem.UUID_TAG_NAME)) { + return tag; + } + + UUID id = stack.getTag().getUUID(TabletItem.UUID_TAG_NAME); + + // Sync NBT + if (EffectiveSide.get().isServer()) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + ServerLevel overworld = server.getLevel(Level.OVERWORLD); + TabletState state = null; + + try { + state = overworld.getCapability(Capabilities.tabletData()).map(tabletData -> tabletData.get(id)) + .orElse(null); + } catch (Exception e) { + // Only happens when destroyed in the creative menu with destroy all. + } + + if (state == null) + return tag; + + if (state.getBusController().getState() == BusState.SCAN_PENDING) { + state.getBusController().scheduleBusScan(); + } + + tag.put(ENERGY_TAG_NAME, state.getEnergy().serializeNBT()); + tag.put(ITEMS_TAG_NAME, state.getDeviceItems().saveItems()); + + boolean running = state.getVirtualMachine().getRunState() == VMRunState.RUNNING; + + tag.putBoolean(TABLET_ON_TAG_NAME, running); + stack.getTag().putBoolean(TABLET_ON_TAG_NAME, running); + + if (id != null) { + tag.putUUID(TabletItem.UUID_TAG_NAME, id); + } + + if (state.getTerminalUser() != null) { + Network.sendToClient(new TabletItemsMessage(state.getId(), tag), state.getTerminalUser()); + Network.sendToClient(new TabletEnergyMessage(id, tag), state.getTerminalUser()); + } + } + + return tag; + } + + @Override + public void deserializeNBT(CompoundTag tag) { + // Handled internally. + } + } + + @Override + public InteractionResultHolder use(final Level level, final Player player, final InteractionHand hand) { + if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer) { + final ItemStack item = serverPlayer.getItemInHand(hand); + + serverPlayer.getServer().getLevel(Level.OVERWORLD).getCapability(Capabilities.tabletData()) + .ifPresent(tabletData -> { + UUID id = getOrCreateUUID(item); + + // Create a new TabletState if it doesn't exist for this ItemStack yet. + if (tabletData.get(id) == null) { + tabletData.put(id, new TabletState(id, true)); + tabletData.get(id).getDeviceItems().loadItems( + NBTUtils.getOrCreateChildTag(item.getOrCreateTag(), MOD_TAG_NAME, ITEMS_TAG_NAME)); + } + + TabletState tablet = tabletData.get(id); + + // Run an initial client setup when a new user starts using the tablet. + ServerPlayer currentUser = tablet.getTerminalUser(); + if (currentUser == null || currentUser.getUUID() != serverPlayer.getUUID()) { + tablet.setTerminalUser(serverPlayer); + + if (tablet.getVirtualMachine().getRunState() != VMRunState.STOPPED) { + CompoundTag tag = new CompoundTag(); + tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(tablet.getTerminal())); + + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + NbtIo.writeCompressed(tag, out); + + byte[] data = out.toByteArray(); + + Network.sendToClient(new TabletClientUpdateMessage(id, data), serverPlayer); + } catch (Exception e) { + System.out.println("Failed to serialize terminal state."); + } + } + + tablet.getVirtualMachine().handleBusStateChanged(tablet.getBusController().getState()); + tablet.getVirtualMachine().handleRunStateChanged(tablet.getVirtualMachine().getRunState()); + } + + if (player.isShiftKeyDown()) { + tablet.getVirtualMachine().start(); + } else { + openTerminalScreen(serverPlayer, getOrCreateUUID(item)); + } + }); + } + + return InteractionResultHolder.sidedSuccess(player.getItemInHand(hand), level.isClientSide()); + } + + public static ItemStack getTabletWithFlash() { + final ItemStack tablet = new ItemStack(Items.TABLET.get()); + + final CompoundTag itemsTag = NBTUtils.getOrCreateChildTag(tablet.getOrCreateTag(), MOD_TAG_NAME, + ITEMS_TAG_NAME); + itemsTag.put(key(DeviceTypes.FLASH_MEMORY), makeInventoryTag( + new ItemStack(Items.FLASH_MEMORY_CUSTOM.get()))); + + return tablet; + } +} diff --git a/src/main/java/li/cil/oc2/common/mixin/CreativeClientMixin.java b/src/main/java/li/cil/oc2/common/mixin/CreativeClientMixin.java new file mode 100644 index 000000000..72c366787 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/mixin/CreativeClientMixin.java @@ -0,0 +1,99 @@ +package li.cil.oc2.common.mixin; + +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.item.TabletItem; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.TabletRemovalMessage; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.CreativeModeTab.Type; +import net.minecraft.world.item.ItemStack; + +@Mixin(CreativeModeInventoryScreen.class) +public class CreativeClientMixin { + @Shadow + private Slot destroyItemSlot; + + @Shadow + private boolean hasClickedOutside; + + @Shadow + private static CreativeModeTab selectedTab; + + private static void handleStack(ItemStack stack) { + if (!stack.isEmpty() && stack.is(Items.TABLET.get())) { + if (stack.getOrCreateTag().hasUUID(TabletItem.UUID_TAG_NAME)) { + UUID id = stack.getOrCreateTag().getUUID(TabletItem.UUID_TAG_NAME); + TabletStateCache.remove(id); + Network.sendToServer(new TabletRemovalMessage(id)); + } + } + } + + @Inject(method = "slotClicked", at = @At("HEAD")) + private void onSlotClicked(@Nullable Slot slot, int a, int b, ClickType ct, CallbackInfo ci) { + if ((Object) this instanceof CreativeModeInventoryScreen self) { + ItemStack stack = self.getMenu().getCarried(); + + boolean targetDiff = true; + + if (slot != null) { + targetDiff = !stack.getDescriptionId().equals(slot.getItem().getDescriptionId()); + } + + if (!hasClickedOutside) { + if (slot == this.destroyItemSlot && selectedTab.getType() == Type.INVENTORY) { + if (ct == ClickType.QUICK_MOVE) { + // Destroy all items in inventory using the trash slot. + for (int j = 0; j < Minecraft.getInstance().player.inventoryMenu.getItems().size(); ++j) { + Slot target = self.getMenu().getSlot(j); + if (target != null && target.getItem() != null) { + handleStack(target.getItem()); + } + } + } else { + // Destroy using the trash slot. + handleStack(stack); + } + } else if (selectedTab.getType() != Type.INVENTORY) { + if (ct == ClickType.QUICK_MOVE && (a >= 45 || !stack.isEmpty()) && !(b == 0 && !targetDiff)) { + if (!stack.isEmpty()) { + handleStack(stack); + } else { + Slot target = self.getMenu().getSlot(a); + if (target != null && target.getItem() != null) { + // Destroy by shift clicking into category. + handleStack(target.getItem()); + } + } + } else if (ct == ClickType.PICKUP && a >= 0 && a < 45 && !stack.isEmpty() && b < 2) { + if (targetDiff || b == 1) { + // Destroy by dropping into category. + handleStack(stack); + } + } else if (ct == ClickType.SWAP && a < 45) { + Slot target = self.getMenu().getSlot(b + 45); + if (target != null && target.getItem() != null) { + // Destroy by swapping stack onto item. + handleStack(target.getItem()); + } + } + } + + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/mixin/EntityMixin.java b/src/main/java/li/cil/oc2/common/mixin/EntityMixin.java new file mode 100644 index 000000000..42c8f7ca7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/mixin/EntityMixin.java @@ -0,0 +1,44 @@ +package li.cil.oc2.common.mixin; + +import java.util.UUID; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.item.TabletItem; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.TabletRemovalMessage; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; + +@Mixin(Entity.class) +public abstract class EntityMixin { + + @Inject(method = "remove", at = @At("HEAD")) + private void onRemove(Entity.RemovalReason reason, CallbackInfo ci) { + if ((Object) this instanceof ItemEntity self && !self.level().isClientSide) { + ItemStack stack = self.getItem(); + Component name = stack.getHoverName(); + + if (name.getContents() instanceof TranslatableContents translatable + && translatable.getKey().equals(Items.TABLET.get().getDescriptionId())) { + if (stack.is(Items.TABLET.get()) + && reason == Entity.RemovalReason.KILLED + || reason == Entity.RemovalReason.DISCARDED) { + if (stack.getOrCreateTag().hasUUID(TabletItem.UUID_TAG_NAME)) { + UUID id = stack.getOrCreateTag().getUUID(TabletItem.UUID_TAG_NAME); + TabletStateCache.remove(id); + Network.sendToServer(new TabletRemovalMessage(id)); + } + } + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/network/MessageUtils.java b/src/main/java/li/cil/oc2/common/network/MessageUtils.java index 14247c7bc..3d281dc20 100644 --- a/src/main/java/li/cil/oc2/common/network/MessageUtils.java +++ b/src/main/java/li/cil/oc2/common/network/MessageUtils.java @@ -6,15 +6,22 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraftforge.network.NetworkEvent; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Consumer; +import li.cil.oc2.common.item.TabletItem; + public final class MessageUtils { public static void withNearbyServerBlockEntityForInteraction(final NetworkEvent.Context context, final BlockPos pos, final Class type, final BiConsumer callback) { final ServerPlayer player = context.getSender(); @@ -92,4 +99,58 @@ public static void withClientEntity(final int id, final Class callback.accept((T) entity); } } + + @SuppressWarnings("unchecked") + public static void withInventoryItem(final NetworkEvent.Context context, final UUID id, + Class type, final BiConsumer callback) { + final ServerPlayer player = context.getSender(); + if (player == null) { + return; + } + + final ItemStack item = findItemByUUID(player, id); + + final Item tablet = item.getItem(); + + if (item != ItemStack.EMPTY && type.isInstance(item.getItem())) { + callback.accept((T) tablet, item); + } + } + + public static void withClientInventoryItem(final NetworkEvent.Context context, final UUID id, + final Consumer callback) { + final Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + final ItemStack item = findItemByUUID(player, id); + + if (item != ItemStack.EMPTY) { + callback.accept(item); + } + } + + public static ItemStack findItemByUUID(Player player, UUID uuid) { + + for (ItemStack stack : player.getInventory().items) { + if (matches(stack, uuid)) + return stack; + } + + for (ItemStack stack : player.getInventory().offhand) { + if (matches(stack, uuid)) + return stack; + } + + return ItemStack.EMPTY; + } + + private static boolean matches(ItemStack stack, UUID uuid) { + if (stack.isEmpty()) + return false; + + CompoundTag tag = stack.getTag(); + return tag != null && tag.hasUUID(TabletItem.UUID_TAG_NAME) && tag.getUUID(TabletItem.UUID_TAG_NAME).equals(uuid); + } } diff --git a/src/main/java/li/cil/oc2/common/network/Network.java b/src/main/java/li/cil/oc2/common/network/Network.java index a92911637..5edb3a906 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -62,6 +62,19 @@ public static void initialize() { registerMessage(OpenRobotInventoryMessage.class, OpenRobotInventoryMessage::new, NetworkDirection.PLAY_TO_SERVER); registerMessage(OpenRobotTerminalMessage.class, OpenRobotTerminalMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(TabletTerminalOutputMessage.class, TabletTerminalOutputMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletTerminalInputMessage.class, TabletTerminalInputMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(TabletRunStateMessage.class, TabletRunStateMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletBusStateMessage.class, TabletBusStateMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletBootErrorMessage.class, TabletBootErrorMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletPowerMessage.class, TabletPowerMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(TabletItemsMessage.class, TabletItemsMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletEnergyMessage.class, TabletEnergyMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletClientUpdateMessage.class, TabletClientUpdateMessage::new, NetworkDirection.PLAY_TO_CLIENT); + registerMessage(TabletRemovalMessage.class, TabletRemovalMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(OpenTabletInventoryMessage.class, OpenTabletInventoryMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(OpenTabletTerminalMessage.class, OpenTabletTerminalMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(DiskDriveFloppyMessage.class, DiskDriveFloppyMessage::new, NetworkDirection.PLAY_TO_CLIENT); registerMessage(FirmwareFlasherMessage.class, FirmwareFlasherMessage::new, NetworkDirection.PLAY_TO_CLIENT); diff --git a/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalItemMessage.java b/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalItemMessage.java new file mode 100644 index 000000000..f79b21bb8 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/AbstractTerminalItemMessage.java @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import net.minecraft.network.FriendlyByteBuf; + +public abstract class AbstractTerminalItemMessage extends AbstractMessage { + protected UUID id; + protected byte[] data; + + /////////////////////////////////////////////////////////////////// + + public AbstractTerminalItemMessage(final UUID id, final ByteBuffer data) { + this.id = id; + this.data = data.array(); + } + + public AbstractTerminalItemMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + data = buffer.readByteArray(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeByteArray(data); + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/OpenTabletInventoryMessage.java b/src/main/java/li/cil/oc2/common/network/message/OpenTabletInventoryMessage.java new file mode 100644 index 000000000..26667e7f7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/OpenTabletInventoryMessage.java @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.common.item.TabletItem; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +public final class OpenTabletInventoryMessage extends AbstractMessage { + private UUID id; + + /////////////////////////////////////////////////////////////////// + + public OpenTabletInventoryMessage(final UUID id) { + this.id = id; + } + + public OpenTabletInventoryMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player != null) { + TabletItem.openInventoryScreen(player, id); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/OpenTabletTerminalMessage.java b/src/main/java/li/cil/oc2/common/network/message/OpenTabletTerminalMessage.java new file mode 100644 index 000000000..3b3d0471c --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/OpenTabletTerminalMessage.java @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.common.item.TabletItem; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +public final class OpenTabletTerminalMessage extends AbstractMessage { + private UUID id; + + /////////////////////////////////////////////////////////////////// + + public OpenTabletTerminalMessage(final UUID id) { + this.id = id; + } + + public OpenTabletTerminalMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player != null) { + TabletItem.openTerminalScreen(player, id); + } + } +} + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletBootErrorMessage.java new file mode 100644 index 000000000..a6e1cccb1 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletBootErrorMessage.java @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import javax.annotation.Nullable; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletBootErrorMessage extends AbstractMessage { + private UUID id; + private Component value; + + /////////////////////////////////////////////////////////////////// + + public TabletBootErrorMessage(final UUID id, @Nullable final Component value) { + this.id = id; + this.value = value; + } + + public TabletBootErrorMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + value = buffer.readComponent(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeComponent(value); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + tablet.getVirtualMachine().setBootErrorClient(value); + } +} + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletBusStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletBusStateMessage.java new file mode 100644 index 000000000..0281048af --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletBusStateMessage.java @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.bus.CommonDeviceBusController; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletBusStateMessage extends AbstractMessage { + private UUID id; + private CommonDeviceBusController.BusState value; + + /////////////////////////////////////////////////////////////////// + + public TabletBusStateMessage(final UUID id, final CommonDeviceBusController.BusState value) { + this.id = id; + this.value = value; + } + + public TabletBusStateMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + value = buffer.readEnum(CommonDeviceBusController.BusState.class); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeEnum(value); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + tablet.getVirtualMachine().setBusStateClient(value); + } +} + + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletClientUpdateMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletClientUpdateMessage.java new file mode 100644 index 000000000..0e53e77e9 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletClientUpdateMessage.java @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.io.ByteArrayInputStream; +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.serialization.NBTSerialization; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletClientUpdateMessage extends AbstractMessage { + private static final String TERMINAL_TAG_NAME = "terminal"; + + private UUID id; + private byte[] termBuffer; + + /////////////////////////////////////////////////////////////////// + + public TabletClientUpdateMessage(final UUID id, final byte[] termBuffer) { + this.id = id; + this.termBuffer = termBuffer; + } + + public TabletClientUpdateMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + termBuffer = buffer.readByteArray(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeByteArray(termBuffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + + try (ByteArrayInputStream in = new ByteArrayInputStream(termBuffer)) { + CompoundTag tag = NbtIo.readCompressed(in); + + NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), tablet.getTerminal()); + } catch (Exception e) { + System.out.println("Failed to load terminal state from message."); + } + } +} + + + + + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletEnergyMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletEnergyMessage.java new file mode 100644 index 000000000..21edb5efe --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletEnergyMessage.java @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import static li.cil.oc2.common.Constants.*; + +public final class TabletEnergyMessage extends AbstractMessage { + private UUID id; + private CompoundTag tag; + + /////////////////////////////////////////////////////////////////// + + public TabletEnergyMessage(final UUID id, final CompoundTag tag) { + this.id = id; + this.tag = tag; + } + + public TabletEnergyMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + tag = buffer.readNbt(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeNbt(tag); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + + tablet.getEnergy().deserializeNBT(tag.getCompound(ENERGY_TAG_NAME)); + } +} + + + + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletItemsMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletItemsMessage.java new file mode 100644 index 000000000..8240619c5 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletItemsMessage.java @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.item.TabletItem; +import li.cil.oc2.common.network.MessageUtils; +import li.cil.oc2.common.util.NBTUtils; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import static li.cil.oc2.common.Constants.*; + +public final class TabletItemsMessage extends AbstractMessage { + private UUID id; + private CompoundTag tag; + + /////////////////////////////////////////////////////////////////// + + public TabletItemsMessage(final UUID id, final CompoundTag tag) { + this.id = id; + this.tag = tag; + } + + public TabletItemsMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + tag = buffer.readNbt(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeNbt(tag); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + + tablet.getDeviceItems().loadItems(tag.getCompound(ITEMS_TAG_NAME)); + + MessageUtils.withClientInventoryItem(context, id, stack -> { + stack.getTag().getCompound(MOD_TAG_NAME).remove(ITEMS_TAG_NAME); + final CompoundTag itemsTag = NBTUtils.getOrCreateChildTag(stack.getOrCreateTag(), MOD_TAG_NAME, ITEMS_TAG_NAME); + tablet.getDeviceItems().saveItems(itemsTag); // Puts one tag per device type, as expected by TooltipUtils. + stack.getTag().putBoolean(TabletItem.TABLET_ON_TAG_NAME, tag.getBoolean(TabletItem.TABLET_ON_TAG_NAME)); + }); + } +} + + + + + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletPowerMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletPowerMessage.java new file mode 100644 index 000000000..7b1788f8a --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletPowerMessage.java @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.common.item.TabletItem; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletPowerMessage extends AbstractMessage { + private UUID id; + private boolean power; + + /////////////////////////////////////////////////////////////////// + + public TabletPowerMessage(final UUID id, final boolean power) { + this.id = id; + this.power = power; + } + + public TabletPowerMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + power = buffer.readBoolean(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeBoolean(power); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player != null) { + if (power) { + TabletItem.start(player.level(), id); + } else { + TabletItem.stop(player.level(), id); + } + } + } +} + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletRemovalMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletRemovalMessage.java new file mode 100644 index 000000000..5a87b69d3 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletRemovalMessage.java @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.util.thread.EffectiveSide; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.server.ServerLifecycleHooks; + +public final class TabletRemovalMessage extends AbstractMessage { + private UUID id; + + /////////////////////////////////////////////////////////////////// + + public TabletRemovalMessage(final UUID id) { + this.id = id; + } + + public TabletRemovalMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + if (EffectiveSide.get().isServer()) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + ServerLevel overworld = server.getLevel(Level.OVERWORLD); + overworld.getCapability(Capabilities.tabletData()).ifPresent(tabletData -> { + TabletState state = tabletData.get(id); + if (state != null) { + state.getVirtualMachine().stop(); + state.getVirtualMachine().dispose(); + } + + tabletData.remove(id); + }); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletRunStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletRunStateMessage.java new file mode 100644 index 000000000..073d338c7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletRunStateMessage.java @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.vm.VMRunState; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletRunStateMessage extends AbstractMessage { + private UUID id; + private VMRunState value; + + /////////////////////////////////////////////////////////////////// + + public TabletRunStateMessage(final UUID id, final VMRunState value) { + this.id = id; + this.value = value; + } + + public TabletRunStateMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + id = buffer.readUUID(); + value = buffer.readEnum(VMRunState.class); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeUUID(id); + buffer.writeEnum(value); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + tablet.getVirtualMachine().setRunStateClient(value); + } +} + + + diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletTerminalInputMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletTerminalInputMessage.java new file mode 100644 index 000000000..168ba7895 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletTerminalInputMessage.java @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import static li.cil.oc2.common.container.AbstractTabletContainer.getTabletState; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletTerminalInputMessage extends AbstractTerminalItemMessage { + public TabletTerminalInputMessage(final UUID id, final ByteBuffer data) { + super(id, data); + } + + public TabletTerminalInputMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player != null) { + getTabletState(id, player).getTerminal().putInput(ByteBuffer.wrap(data)); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/TabletTerminalOutputMessage.java b/src/main/java/li/cil/oc2/common/network/message/TabletTerminalOutputMessage.java new file mode 100644 index 000000000..0e42cf327 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/TabletTerminalOutputMessage.java @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.network.message; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import li.cil.oc2.client.TabletStateCache; +import li.cil.oc2.common.vm.tablet.TabletState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +public final class TabletTerminalOutputMessage extends AbstractTerminalItemMessage { + public TabletTerminalOutputMessage(final UUID id, final ByteBuffer data) { + super(id, data); + } + + public TabletTerminalOutputMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + TabletState tablet = TabletStateCache.getOrCreateTabletState(id); + tablet.getTerminal().putOutput(ByteBuffer.wrap(data)); + } +} diff --git a/src/main/java/li/cil/oc2/common/util/BlockLocation.java b/src/main/java/li/cil/oc2/common/util/BlockLocation.java index 71f798014..33c3aaff1 100644 --- a/src/main/java/li/cil/oc2/common/util/BlockLocation.java +++ b/src/main/java/li/cil/oc2/common/util/BlockLocation.java @@ -3,6 +3,7 @@ package li.cil.oc2.common.util; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.entity.BlockEntity; @@ -11,6 +12,8 @@ import java.util.Objects; import java.util.Optional; +import li.cil.oc2.common.vm.tablet.TabletState; + public record BlockLocation(WeakReference level, BlockPos blockPos) { public static BlockLocation of(final Entity entity) { return new BlockLocation(new WeakReference<>(entity.level()), entity.blockPosition()); @@ -20,6 +23,13 @@ public static BlockLocation of(final BlockEntity blockEntity) { return new BlockLocation(new WeakReference<>(blockEntity.getLevel()), blockEntity.getBlockPos()); } + public static BlockLocation of(final ServerPlayer player) { + WeakReference level = new WeakReference<>(player.level()); + BlockPos position = player.blockPosition(); + + return new BlockLocation(level, position); + } + public static Optional ofOptional(final Entity entity) { if (entity.isAlive()) { return Optional.of(of(entity)); @@ -36,6 +46,13 @@ public static Optional ofOptional(final BlockEntity blockEntity) } } + public static Optional ofOptional(final TabletState state) { + if (state.getTerminalUser() != null) { + return Optional.of(of(state.getTerminalUser())); + } + return Optional.empty(); + } + public Optional tryGetLevel() { return Optional.ofNullable(level.get()); } diff --git a/src/main/java/li/cil/oc2/common/util/LocationSupplierUtils.java b/src/main/java/li/cil/oc2/common/util/LocationSupplierUtils.java index d83b008a3..33987d27e 100644 --- a/src/main/java/li/cil/oc2/common/util/LocationSupplierUtils.java +++ b/src/main/java/li/cil/oc2/common/util/LocationSupplierUtils.java @@ -4,6 +4,7 @@ import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery; import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery; +import li.cil.oc2.common.vm.tablet.TabletState; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.entity.BlockEntity; @@ -36,6 +37,11 @@ public static Supplier> of(final ItemDeviceQuery query) return () -> BlockLocation.ofOptional(entity.get()); } + final Optional state = query.getContainerTabletState(); + if (state.isPresent()) { + return () -> BlockLocation.ofOptional(state.get()); + } + return Optional::empty; } } diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/ITabletData.java b/src/main/java/li/cil/oc2/common/vm/tablet/ITabletData.java new file mode 100644 index 000000000..3b513d662 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/ITabletData.java @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import java.util.UUID; + +public interface ITabletData { + Iterable getAll(); + TabletState get(UUID id); + void put(UUID id, TabletState state); + void remove(UUID id); +} diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletBusElement.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletBusElement.java new file mode 100644 index 000000000..b8f2c266e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletBusElement.java @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import static java.util.Collections.singleton; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.UUID; + +import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.api.bus.device.Device; +import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery; +import li.cil.oc2.common.bus.AbstractItemDeviceBusElement; +import li.cil.oc2.common.bus.device.util.Devices; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.util.LazyOptional; + +public class TabletBusElement extends AbstractItemDeviceBusElement { + private static final String DEVICE_ID_TAG_NAME = "device_id"; + + private final HashSet devices = new HashSet<>(); + private final TabletState state; + private UUID deviceId = UUID.randomUUID(); + + public TabletBusElement(TabletState state) { + super(0); + + this.state = state; + } + + @Override + public Optional>> getNeighbors() { + return Optional.of(singleton(LazyOptional.of(() -> state.getDeviceItems().busElement))); + } + + @Override + public Optional getDeviceIdentifier(final Device device) { + if (devices.contains(device)) { + return Optional.of(deviceId); + } + return super.getDeviceIdentifier(device); + } + + @Override + public CompoundTag save() { + final CompoundTag tag = super.save(); + tag.putUUID(DEVICE_ID_TAG_NAME, deviceId); + return tag; + } + + public void load(final CompoundTag tag) { + super.load(tag); + if (tag.hasUUID(DEVICE_ID_TAG_NAME)) { + deviceId = tag.getUUID(DEVICE_ID_TAG_NAME); + } + } + + @Override + protected ItemDeviceQuery makeQuery(ItemStack stack) { + return Devices.makeQuery(state, stack); + } +} + diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletData.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletData.java new file mode 100644 index 000000000..da6055b7a --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletData.java @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.fml.util.thread.EffectiveSide; + +public class TabletData implements ITabletData, INBTSerializable { + public static final Map map = new HashMap<>(); + + @Override + public Iterable getAll() { + return map.values(); + } + + @Override + public TabletState get(UUID id) { + return map.get(id); + } + + @Override + public void put(UUID id, TabletState state) { + map.put(id, state); + } + + @Override + public void remove(UUID id) { + TabletState state = map.get(id); + if (state != null) { + state.getVirtualMachine().stop(); + map.remove(id); + } + } + + @Override + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + + Iterator> it = map.entrySet().iterator(); + + while (it.hasNext()) { + Map.Entry entry = it.next(); + tag.put(entry.getKey().toString(), entry.getValue().serializeNBT()); + } + + return tag; + } + + @Override + public void deserializeNBT(CompoundTag tag) { + map.clear(); + + for (String key : tag.getAllKeys()) { + UUID id = UUID.fromString(key); + + TabletState tablet = new TabletState(tag.getCompound(key), !EffectiveSide.get().isServer()); + + map.put(id, tablet); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletDataProvider.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletDataProvider.java new file mode 100644 index 000000000..e16519578 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletDataProvider.java @@ -0,0 +1,32 @@ +package li.cil.oc2.common.vm.tablet; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.LazyOptional; +import li.cil.oc2.common.capabilities.Capabilities; + +public class TabletDataProvider implements ICapabilitySerializable { + + private final TabletData data = new TabletData(); + + @Override + public LazyOptional getCapability(Capability cap, Direction side) { + if (cap == Capabilities.tabletData()) { + return LazyOptional.of(() -> data).cast(); + } + + return LazyOptional.empty(); + } + + @Override + public CompoundTag serializeNBT() { + return data.serializeNBT(); + } + + @Override + public void deserializeNBT(CompoundTag tag) { + data.deserializeNBT(tag); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletItemStackHandlers.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletItemStackHandlers.java new file mode 100644 index 000000000..d3edbee33 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletItemStackHandlers.java @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import li.cil.oc2.api.bus.device.DeviceTypes; +import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery; +import li.cil.oc2.common.bus.device.util.Devices; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.TabletItemsMessage; +import li.cil.oc2.common.vm.AbstractVMItemStackHandlers; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; + +import static li.cil.oc2.common.Constants.ITEMS_TAG_NAME; + +public class TabletItemStackHandlers extends AbstractVMItemStackHandlers { + private static final int MEMORY_SLOTS = 4; + private static final int HARD_DRIVE_SLOTS = 2; + private static final int FLASH_MEMORY_SLOTS = 1; + private static final int CPU_SLOTS = 1; + private static final int CARD_SLOTS = 2; + + private final TabletState state; + private final boolean isClientSide; + + + public TabletItemStackHandlers(TabletState state, boolean isClientSide) { + super( + new GroupDefinition(DeviceTypes.MEMORY, MEMORY_SLOTS), + new GroupDefinition(DeviceTypes.HARD_DRIVE, HARD_DRIVE_SLOTS), + new GroupDefinition(DeviceTypes.FLASH_MEMORY, FLASH_MEMORY_SLOTS), + new GroupDefinition(DeviceTypes.CARD, CARD_SLOTS), + new GroupDefinition(DeviceTypes.CPU, CPU_SLOTS)); + + this.state = state; + this.isClientSide = isClientSide; + } + + @Override + protected ItemDeviceQuery makeQuery(final ItemStack stack) { + return Devices.makeQuery(state, stack); + } + + @Override + protected void onChanged() { + super.onChanged(); + if (!isClientSide) { + state.getBusElement().scheduleScan(); + + if (state.getTerminalUser() != null) { + CompoundTag tag = new CompoundTag(); + tag.put(ITEMS_TAG_NAME, state.getDeviceItems().saveItems()); + + Network.sendToClient(new TabletItemsMessage(state.getId(), tag), state.getTerminalUser()); + } + } + } +} + diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletState.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletState.java new file mode 100644 index 000000000..d1631ca1a --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletState.java @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import static li.cil.oc2.common.Constants.ENERGY_TAG_NAME; +import static li.cil.oc2.common.Constants.ITEMS_TAG_NAME; + +import java.util.Collections; +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; + +import li.cil.oc2.common.bus.CommonDeviceBusController; +import li.cil.oc2.common.config.Config; +import li.cil.oc2.common.energy.FixedEnergyStorage; +import li.cil.oc2.common.serialization.NBTSerialization; +import li.cil.oc2.common.vm.VMRunState; +import li.cil.oc2.common.vm.terminal.Terminal; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.common.util.INBTSerializable; +import li.cil.oc2.common.ext.ICaptureInputStateStorage; +import li.cil.oc2.api.capabilities.TerminalUserProvider; + +public class TabletState implements TerminalUserProvider, ICaptureInputStateStorage, INBTSerializable { + private static final String ID_TAG_NAME = "id"; + private static final String TERMINAL_TAG_NAME = "terminal"; + private static final String STATE_TAG_NAME = "state"; + private static final String BUS_ELEMENT_TAG_NAME = "bus_element"; + private static final String DEVICES_TAG_NAME = "devices"; + + private UUID id; + private final FixedEnergyStorage energy; + private final TabletItemStackHandlers deviceItems; + private final TabletBusElement busElement; + private final CommonDeviceBusController busController; + private final Terminal terminal; + private final TabletVirtualMachine virtualMachine; + @Nullable private ServerPlayer terminalUser; + + private boolean captureInputState = false; + + public TabletState(UUID id, + FixedEnergyStorage energy, + TabletItemStackHandlers deviceItems, + TabletBusElement busElement, + CommonDeviceBusController busController, + Terminal terminal, + TabletVirtualMachine virtualMachine) { + this.id = id; + this.energy = energy; + this.deviceItems = deviceItems; + this.busElement = busElement; + this.busController = busController; + this.terminal = terminal; + this.virtualMachine = virtualMachine; + } + + public TabletState(CompoundTag tag, boolean isClientSide) { + this.energy = new FixedEnergyStorage(Config.tabletEnergyStorage); + this.deviceItems = new TabletItemStackHandlers(this, isClientSide); + + this.busElement = new TabletBusElement(this); + this.busController = new CommonDeviceBusController(busElement, + Config.tabletEnergyPerTick); + + this.terminal = new Terminal(); + this.virtualMachine = new TabletVirtualMachine(this); + + deserializeNBT(tag); + } + + public TabletState(UUID id, boolean isClientSide) { + this.id = id; + this.energy = new FixedEnergyStorage(Config.tabletEnergyStorage); + this.deviceItems = new TabletItemStackHandlers(this, isClientSide); + + this.busElement = new TabletBusElement(this); + this.busController = new CommonDeviceBusController(busElement, + Config.tabletEnergyPerTick); + + this.terminal = new Terminal(); + this.terminal.setDisplayOnly(isClientSide); + + this.virtualMachine = new TabletVirtualMachine(this); + } + + @Override + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + + if (virtualMachine.getRunState() != VMRunState.STOPPED) { + tag.put(STATE_TAG_NAME, virtualMachine.serialize()); + tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); + } + + tag.putUUID(ID_TAG_NAME, id); + tag.put(ENERGY_TAG_NAME, energy.serializeNBT()); + tag.put(BUS_ELEMENT_TAG_NAME, busElement.save()); + tag.put(ITEMS_TAG_NAME, deviceItems.saveItems()); + tag.put(DEVICES_TAG_NAME, deviceItems.saveDevices()); + + return tag; + } + + @Override + public void deserializeNBT(CompoundTag tag) { + id = tag.getUUID(ID_TAG_NAME); + energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME)); + busElement.load(tag.getCompound(BUS_ELEMENT_TAG_NAME)); + deviceItems.loadItems(tag.getCompound(ITEMS_TAG_NAME)); + deviceItems.loadDevices(tag.getCompound(DEVICES_TAG_NAME)); + virtualMachine.deserialize(tag.getCompound(STATE_TAG_NAME)); + NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); + } + + public void deserializeClientUpdate(CompoundTag tag) { + energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME)); + deviceItems.loadItems(tag.getCompound(ITEMS_TAG_NAME)); + } + + public UUID getId() { + return id; + } + + public FixedEnergyStorage getEnergy() { + return energy; + } + + public TabletItemStackHandlers getDeviceItems() { + return deviceItems; + } + + public TabletBusElement getBusElement() { + return busElement; + } + + public CommonDeviceBusController getBusController() { + return busController; + } + + public Terminal getTerminal() { + return terminal; + } + + public TabletVirtualMachine getVirtualMachine() { + return virtualMachine; + } + + public ServerPlayer getTerminalUser() { + return terminalUser; + } + + @Override + public Iterable getTerminalUsers() { + return (terminalUser == null) ? Collections.emptyList() : Collections.singleton(terminalUser); + } + + public void setTerminalUser(ServerPlayer terminalUser) { + if (terminalUser == null) { + return; + } + + this.terminalUser = terminalUser; + } + + @Override + public boolean getCaptureInputState() { + return captureInputState; + } + + @Override + public void setCaptureInputState(boolean state) { + captureInputState = state; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/tablet/TabletVirtualMachine.java b/src/main/java/li/cil/oc2/common/vm/tablet/TabletVirtualMachine.java new file mode 100644 index 000000000..486bf6777 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/tablet/TabletVirtualMachine.java @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.tablet; + +import java.nio.ByteBuffer; + +import javax.annotation.Nullable; + +import li.cil.oc2.common.bus.CommonDeviceBusController; +import li.cil.oc2.common.config.Config; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.TabletBootErrorMessage; +import li.cil.oc2.common.network.message.TabletBusStateMessage; +import li.cil.oc2.common.network.message.TabletRunStateMessage; +import li.cil.oc2.common.network.message.TabletTerminalOutputMessage; +import li.cil.oc2.common.util.TerminalUtils; +import li.cil.oc2.common.vm.AbstractTerminalVMRunner; +import li.cil.oc2.common.vm.AbstractVirtualMachine; +import li.cil.oc2.common.vm.VMRunState; +import net.minecraft.network.chat.Component; + +public class TabletVirtualMachine extends AbstractVirtualMachine { + private TabletState tabletState; + + public TabletVirtualMachine(final TabletState tabletState) { + super(tabletState.getBusController()); + + this.tabletState = tabletState; + + state.vmAdapter.setBaseAddressProvider(tabletState.getDeviceItems()::getDeviceAddressBase); + } + + @Override + protected boolean consumeEnergy(final int amount, final boolean simulate) { + if (!Config.tabletsUseEnergy()) { + return true; + } + + if (amount > tabletState.getEnergy().getEnergyStored()) { + return false; + } + + tabletState.getEnergy().extractEnergy(amount, simulate); + return true; + } + + @Override + protected void stopRunnerAndReset() { + super.stopRunnerAndReset(); + + TerminalUtils.resetTerminal(tabletState.getTerminal(), output -> { + if (tabletState.getTerminalUser() != null) + Network.sendToClient(new TabletTerminalOutputMessage(tabletState.getId(), output), + tabletState.getTerminalUser()); + }); + } + + @Override + protected AbstractTerminalVMRunner createRunner() { + return new TabletVMRunner(tabletState); + } + + @Override + public void handleBusStateChanged(final CommonDeviceBusController.BusState value) { + if (tabletState.getTerminalUser() != null) + Network.sendToClient(new TabletBusStateMessage(tabletState.getId(), value), tabletState.getTerminalUser()); + } + + @Override + public void handleRunStateChanged(final VMRunState value) { + if (tabletState.getTerminalUser() != null) + Network.sendToClient(new TabletRunStateMessage(tabletState.getId(), value), tabletState.getTerminalUser()); + } + + @Override + protected void handleBootErrorChanged(@Nullable Component value) { + if (value == null) { + value = Component.literal(""); + } + if (tabletState.getTerminalUser() != null) + Network.sendToClient(new TabletBootErrorMessage(tabletState.getId(), value), tabletState.getTerminalUser()); + } + + private final class TabletVMRunner extends AbstractTerminalVMRunner { + private final TabletState state; + + public TabletVMRunner(final TabletState state) { + super(state.getVirtualMachine(), state.getTerminal()); + + this.state = state; + } + + protected void sendTerminalUpdateToClient(final ByteBuffer output) { + if (tabletState.getTerminalUser() != null) + Network.sendToClient(new TabletTerminalOutputMessage(state.getId(), output), state.getTerminalUser()); + } + } +} diff --git a/src/main/resources/assets/oc2r/doc/en_us/item/index.md b/src/main/resources/assets/oc2r/doc/en_us/item/index.md index af8fe1545..0b9c9eb36 100644 --- a/src/main/resources/assets/oc2r/doc/en_us/item/index.md +++ b/src/main/resources/assets/oc2r/doc/en_us/item/index.md @@ -17,3 +17,4 @@ This index lists all documented items. If you're looking for a block, see the [b - [Robot](robot.md) - [Scrench](wrench.md) - [Sound Card](sound_card.md) +- [Tablet](tablet.md) diff --git a/src/main/resources/assets/oc2r/doc/en_us/item/robot.md b/src/main/resources/assets/oc2r/doc/en_us/item/robot.md index 07e69ab96..8d85cd322 100644 --- a/src/main/resources/assets/oc2r/doc/en_us/item/robot.md +++ b/src/main/resources/assets/oc2r/doc/en_us/item/robot.md @@ -7,7 +7,7 @@ Robots have a fixed-size inventory and sport a state-of-the-art energy storage. In their default configuration, robots cannot interact with their own inventory. Use an [inventory operations module](inventory_operations_module.md) to enable robots to move items in their own inventory, as well as to insert and extract items to and from other inventories. -To recharge a robot, it is recommended to make use the [charger](../block/charger.md). It is possible for robots to recharge themselves by simply moving on top of a charger. Alternatively, they may be placed into an inventory on top of the charger. +To recharge a robot, it is recommended to make use of the [charger](../block/charger.md). It is possible for robots to recharge themselves by simply moving on top of a charger. Alternatively, they may be placed into an inventory on top of the charger. The default Linux distribution provides a utility Lua library, *robot*, that eases controlling robots. The underlying API offers asynchronous methods for movement. The library implements synchronous alternatives, making sequential programming more convenient. diff --git a/src/main/resources/assets/oc2r/doc/en_us/item/tablet.md b/src/main/resources/assets/oc2r/doc/en_us/item/tablet.md new file mode 100644 index 000000000..83aacd3af --- /dev/null +++ b/src/main/resources/assets/oc2r/doc/en_us/item/tablet.md @@ -0,0 +1,15 @@ +# Tablet +![Not a stele](item:oc2r:tablet) + +Tablets are portable [computers](../block/computer.md). You can take them with you inside of your inventory. + +Once started, tablets will keep running until they run out of power, or you turn them back off. This also means you can put them inside of a different inventory or toss them, and they'll keep running! + +### Configuration +Configure the components in a tablet in its inventory screen. To access this screen switch to the inventory screen using the toggle button to the left of the terminal screen. + +### Energy +Tablets feature a sizable battery. To recharge a tablet, it is recommended to make use of the [charger](../block/charger.md). Just place a container (like a chest or barrel) on top of a powered charger, and place the tablet inside. + +### Connectivity +No devices can be externally connected to the tablet. In order for the tablet to communicate with other computers, make use of [network tunnel cards](network_tunnel_card.md). diff --git a/src/main/resources/assets/oc2r/lang/en_us.json b/src/main/resources/assets/oc2r/lang/en_us.json index fb4b32f3a..30f4add8b 100644 --- a/src/main/resources/assets/oc2r/lang/en_us.json +++ b/src/main/resources/assets/oc2r/lang/en_us.json @@ -66,6 +66,8 @@ "item.oc2r.file_import_export_card.desc": "Imports and exports files from and into your real file system.", "item.oc2r.robot": "Robot", "item.oc2r.robot.desc": "A mobile Computer. Use a Scrench to pick it back up.", + "item.oc2r.tablet": "Tablet", + "item.oc2r.tablet.desc": "A Computer you can carry on your person.", "item.oc2r.inventory_operations_module": "Inventory Operations Module", "item.oc2r.inventory_operations_module.desc": "Moves items in, into and out of the Robot's inventory.", "item.oc2r.block_operations_module": "Block Operations Module", @@ -91,7 +93,6 @@ "item.oc2r.raw_silicon_wafer": "Raw Silicon Wafer", "item.oc2r.raw_silicon_wafer.desc": "A wafer with small imperfections that must be cooked out.", - "entity.oc2r.robot": "Robot", "gui.oc2r.computer.error.unknown": "Unknown Error", diff --git a/src/main/resources/assets/oc2r/models/item/tablet.json b/src/main/resources/assets/oc2r/models/item/tablet.json new file mode 100644 index 000000000..d16cc640f --- /dev/null +++ b/src/main/resources/assets/oc2r/models/item/tablet.json @@ -0,0 +1,14 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "oc2r:item/terminal_off" + }, + "overrides": [ + { + "predicate": { + "oc2r:tablet_on": 1.0 + }, + "model": "oc2r:item/terminal_on" + } + ] +} diff --git a/src/main/resources/assets/oc2r/models/item/terminal_on.json b/src/main/resources/assets/oc2r/models/item/terminal_on.json new file mode 100644 index 000000000..177872156 --- /dev/null +++ b/src/main/resources/assets/oc2r/models/item/terminal_on.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "oc2r:item/terminal_on" + } +} diff --git a/src/main/resources/assets/oc2r/textures/item/terminal_off.png b/src/main/resources/assets/oc2r/textures/item/terminal_off.png new file mode 100644 index 000000000..6fae2d07d Binary files /dev/null and b/src/main/resources/assets/oc2r/textures/item/terminal_off.png differ diff --git a/src/main/resources/assets/oc2r/textures/item/terminal_on.png b/src/main/resources/assets/oc2r/textures/item/terminal_on.png new file mode 100644 index 000000000..46c8e2ac3 Binary files /dev/null and b/src/main/resources/assets/oc2r/textures/item/terminal_on.png differ diff --git a/src/main/resources/assets/oc2r/textures/item/terminal_on.png.mcmeta b/src/main/resources/assets/oc2r/textures/item/terminal_on.png.mcmeta new file mode 100644 index 000000000..6274fbfe5 --- /dev/null +++ b/src/main/resources/assets/oc2r/textures/item/terminal_on.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 10, + "frames": [ 0, 1 ] + } +} \ No newline at end of file diff --git a/src/main/resources/data/oc2r/advancements/recipes/oc2r.common/tablet.json b/src/main/resources/data/oc2r/advancements/recipes/oc2r.common/tablet.json new file mode 100644 index 000000000..9621bcea7 --- /dev/null +++ b/src/main/resources/data/oc2r/advancements/recipes/oc2r.common/tablet.json @@ -0,0 +1,48 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "oc2r:tablet" + ] + }, + "criteria": { + "has_transistor": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "oc2r:transistor" + ] + } + ] + } + }, + "has_circuit_board": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "oc2r:circuit_board" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "oc2r:tablet" + } + } + }, + "requirements": [ + [ + "has_transistor", + "has_circuit_board", + "has_the_recipe" + ] + ] +} + diff --git a/src/main/resources/data/oc2r/recipes/tablet.json b/src/main/resources/data/oc2r/recipes/tablet.json new file mode 100644 index 000000000..3647b1f52 --- /dev/null +++ b/src/main/resources/data/oc2r/recipes/tablet.json @@ -0,0 +1,45 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "IBI", + "DTD", + "ICI" + ], + "key": { + "I": { + "tag": "forge:ingots/iron" + }, + "B": { + "tag": "minecraft:stone_buttons" + }, + "D": { + "item": "minecraft:diamond" + }, + "T": { + "item": "oc2r:transistor" + }, + "C": { + "item": "oc2r:circuit_board" + } + }, + "result": { + "item": "oc2r:tablet", + "nbt": { + "oc2r": { + "items": { + "translation{key='gui.oc2r.device_type.flash_memory', args=[]}": { + "Items": [ + { + "Slot": 0, + "Count": 1, + "id": "oc2r:flash_memory_custom" + } + ], + "Size": 1 + } + } + } + } + } +} + diff --git a/src/main/resources/mixins.oc2r.json b/src/main/resources/mixins.oc2r.json index 448193494..a7e6fb523 100644 --- a/src/main/resources/mixins.oc2r.json +++ b/src/main/resources/mixins.oc2r.json @@ -4,13 +4,12 @@ "required": true, "package": "li.cil.oc2.common.mixin", "refmap": "mixins.oc2r.refmap.json", - "mixins": [ - "ServerChunkCacheMixin" - ], + "mixins": ["ServerChunkCacheMixin", "EntityMixin"], "client": [ "FrustumMixin", "LevelRendererMixin", - "MinecraftMixin" + "MinecraftMixin", + "CreativeClientMixin" ], "injectors": { "defaultRequire": 1