diff --git a/CHANGELOG.md b/CHANGELOG.md index f22bced2..f927bd31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +------------------------------------------------------ +Create: Mobile Packages - v0.7.0 - 1.21.1 - 17.03.2026 +------------------------------------------------------ + +### Additions + +- Added Bee Port Return Mode toggle in the Bee Port GUI (#136) + - New button that toggles between normal delivery and return mode + - In return mode, Robo Bees spawned from that port fly back to their origin port after successful player delivery + +- Added Admin/OP Commands for Network Management (#305) + - `/cmp network list` - Display all logistics networks with names, player counts, lock status, and owner information + - `/cmp network add ` - Add a player to a network (auto-filters to named networks only) + - `/cmp network remove ` - Remove a player from a network (auto-filters to networks the player is + part of) + - Full command auto-completion with intelligent filtering + +- Added TrashSlots to the Portable Stock Ticker allowing players to send items (#299) + - Set an address + - Put items in the TrashSlots + - A Robo Bee will come and pick up the items and send them to the address + +### Bug Fixes + +- Fixed Port with full packages inventory can't accept RoboBee without package (#301) +- Fixed Portable Stock Ticker not updating to an empty item list if the last item is removed (#302) +- Fixed Portable Stock Ticker losing address after restart (#287) + ------------------------------------------------------ Create: Mobile Packages - v0.6.1 - 1.21.1 - 20.02.2026 ------------------------------------------------------ diff --git a/README.md b/README.md index c5dbf8ff..bd08c495 100644 --- a/README.md +++ b/README.md @@ -10,77 +10,103 @@ A Minecraft mod that adds support for delivering Create Mod Packages directly to ## Requirements -### 1.20.1 Forge -- Forge 47.3.33 or newer -- Create 6.0.8 or newer - ### 1.21.1 NeoForge + - NeoForge 21.1.206 or newer - Create 6.0.9 or newer +### 1.20.1 Forge (no longer supported) +- Forge 47.3.33 or newer +- Create 6.0.8 or newer -## Where to Download? +## Download? - Modrinth: https://modrinth.com/mod/create-mobile-packages - Curseforge: https://www.curseforge.com/minecraft/mc-mods/create-mobile-packages -## Translations +## Overview + +### Logistics Network -[![Crowdin](https://badges.crowdin.net/create-mobile-packages/localized.svg)](https://crowdin.com/project/create-mobile-packages) This project is translated using Crowdin.[https://crowdin.com/project/create-mobile-packages](https://crowdin.com/project/create-mobile-packages) +The core of the mod is the **Logistics Network**. -You're welcome to contribute to the translations or add your language. +- **Linking**: Bee Ports must be linked to a network. Place a new one to create a network, or link to an existing one. +- **Security**: Robo Bees only fly to ports and players within the same network. +- **Membership**: Players can join a network via the Bee Port GUI. View your networks with the `H` hotkey. -## Items ### Bee Port -The **Bee Port** is a specialized block that automates the delivery of Create mod Packages to players or other Bee Ports. +The hub for your logistics network. -Key Features: -- **Package delivery system** that reads address labels and send a bee with the packages to players or other Bee Ports -- **Insert packages manually or automatically** using Funnels, Chutes, or Hoppers -- **Pull packages** from adjacent inventories -- **Push packages** to adjacent inventories if: - - The address matches the Bee Port's configured addresses - - The Bee Port receives a redstone signal +- **Send & Receive**: Automates package delivery to addresses (players or other ports). +- **Requirement**: Requires a **Robo Bee** item in its internal inventory to send packages. +- **Return Mode**: Toggle to have Robo Bees return to this port after delivering to a player. +- **Automation**: By default, it pulls packages from adjacent inventories. When powered by **Redstone**, it pushes items + to adjacent inventories. -![bee Port](https://github.com/user-attachments/assets/3b15287e-44fc-4ebc-9e59-a38fc2a5da49) +![Bee Port](https://github.com/user-attachments/assets/3b15287e-44fc-4ebc-9e59-a38fc2a5da49) -#### Robo Bee -The **Robo Bee** is an entity that delivers Create mod Packages to their destination. -It follows the address on the package, delivering directly to players or between Bee Ports as needed. +### Robo Bee -Key Features: -- **Spawned using the item** -- **Required by Bee Ports** to send packages -- **Carries packages** directly to players or between Bee Ports +The courier entity. -![robo_bee](https://github.com/user-attachments/assets/9b78670f-a2f8-4343-bd58-5936103a9596) +- **Delivery**: Carries packages to their destination address. +- **Spawning**: Right-click with a **Robo Bee** item to spawn. +- **Network**: The bee binds to the network the item is linked to. If the item is unlinked, the bee will target any + available port. -### Portable Stock Ticker +![Robo Bee](https://github.com/user-attachments/assets/9b78670f-a2f8-4343-bd58-5936103a9596) -The **Portable Stock Ticker** is a handheld device that integrates with a Create mod network, allowing players to request packages remotely. +### Portable Stock Ticker -Once linked to a **Stock Ticker** or a **Stock Link**, it provides a **Stockkeeper interface**, enabling players to request items on the go without needing direct access to a Stockkeeper. +A handheld device for remote item management. -Key Features: -- **Remote Item Requesting** via Stockkeeper interface -- **JEI-synchronized search** +- **Request Items**: Access your stock remotely by linking to Create Logistics Network. +- **Send Items**: Use the **Trash Slots** to send items from your inventory to a specific address via Robo Bee. (The Bee Port needs to be in the same network as the Portable Stock Ticker) - **Crafting support** within the request interface -- **Category synchronization**: If linked to a Stock Ticker with categories, those categories will be copied to the Portable Stock Ticker +- **JEI Support**: Synchronized item search. -![Controller](https://github.com/user-attachments/assets/d8a85e58-3ffa-4c2a-8b74-48f6c2b76642) +![Stock Ticker](https://github.com/user-attachments/assets/d8a85e58-3ffa-4c2a-8b74-48f6c2b76642) ### Mobile Packager -The **Mobile Packager** is a handheld device allowing players to create or modify Create mod Packages on the go. +A utility for managing packages on the go. -Key Features: -- **Creating Packages** using the device opens a GUI where up to nine Stacks can be placed along with an address. On submission, a new Package is placed in the player's inventory containing the added items. -- **Edit Packages** using the device with the Shift key opens a GUI where an existing package can be placed. On submission, the contents of the package are shown and can be modified. +- **Create**: Pack up to 9 item stacks into an addressed package. +- **Edit**: View and modify existing package contents (Sneak + Use). ![Mobile Packager](https://github.com/user-attachments/assets/9d21daf4-f64e-4df8-9ad3-a689e6f83ab5) +## Commands + +Admin commands for managing logistics networks. + +- `/cmp network list` - Display all networks, player counts, and owners. +- `/cmp network add ` - Force add a player to a network. +- `/cmp network remove ` - Force remove a player from a network. +- `/cmp robos clear` - Remove all Robo Bees from the world. + +## Translations + +[![Crowdin](https://badges.crowdin.net/create-mobile-packages/localized.svg)](https://crowdin.com/project/create-mobile-packages) + +Help translate the mod on [Crowdin](https://crowdin.com/project/create-mobile-packages). ## Gallery -![image](https://github.com/user-attachments/assets/80b6f028-61f9-415a-aa4d-bd911d1d1997) -![image](https://github.com/user-attachments/assets/9c9afb41-4671-4092-9a4f-0e23dbf155bb) +### Simple Setup +Link the Port and the Stock Link, by clicking with one item on the other block. You can link your Portable Stock Ticker by Shift-Clicking with it on the Stock Link, Port or other Linked Blocks +2026-03-17_21 28 01 + +### Robo Bee on its way delivering to you +2026-03-17_21 28 22 + +### Robo Bee on its way picking up your Trash Slot items to deliver them to "Port1" +2026-03-17_21 34 10 + +### Portable Stock Ticker selecting items to be send to you +2026-03-17_21 29 00 + +### Admin Console Command to manage Networks (can also be done via UI) +2026-03-17_21 36 13 + + diff --git a/gradle.properties b/gradle.properties index 59e0740b..07581aba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ mod_name=Create: Mobile Packages mod_license=MIT License # The mod version. See https://semver.org/ -mod_version=0.6.1 +mod_version=0.7.0 mod_logo=assets/create_mobile_packages/textures/create_mobile_packages.png mod_display_url=https://github.com/timplay33/Create-Mobile-Packages mod_group_id=de.theidler.create_mobile_packages diff --git a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortBlockEntity.java b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortBlockEntity.java index 53d7ff91..fd9fae85 100644 --- a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortBlockEntity.java +++ b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortBlockEntity.java @@ -14,12 +14,13 @@ import de.theidler.create_mobile_packages.index.config.CMPConfigs; import de.theidler.create_mobile_packages.items.robo_bee.RoboBeeItem; import de.theidler.create_mobile_packages.network_settings.NetworkHelper; +import de.theidler.create_mobile_packages.robo.BeePortBlockEntityTarget; import de.theidler.create_mobile_packages.robo.RoboManager; import de.theidler.create_mobile_packages.robo.VirtualRobo; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.HolderLookup; import net.minecraft.core.GlobalPos; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; @@ -36,12 +37,12 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.items.IItemHandler; import net.neoforged.neoforge.items.ItemHandlerHelper; import net.neoforged.neoforge.items.ItemStackHandler; import org.jetbrains.annotations.NotNull; -import net.neoforged.neoforge.capabilities.Capabilities; -import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import org.jetbrains.annotations.Nullable; import java.util.Comparator; @@ -60,8 +61,9 @@ public class BeePortBlockEntity extends PackagePortBlockEntity { private static final int ROBOBEE_INVENTORY_STACK_SIZE = 64; private UUID placerUUID; - private final ContainerData data = new SimpleContainerData(2); + private final ContainerData data = new SimpleContainerData(3); private final ItemStackHandler roboBeeInventory = new ItemStackHandler(1); + private boolean beeReturnModeEnabled = false; private final IItemHandler handler = new IItemHandler() { @Override public int getSlots() { @@ -200,7 +202,7 @@ public static boolean sendPackageToPlayer(Player player, ItemStack itemStack) { private synchronized void requestRoboEntity() { if (level instanceof ServerLevel serverLevel) { - RoboManager.get(serverLevel).requestRobo(this.getBlockPos(), this.getLogisticsNetworkId()); + RoboManager.get(serverLevel).requestRobo(new BeePortBlockEntityTarget(this), this.getLogisticsNetworkId(), RoboRequest.Mission.RESTOCK); } } @@ -208,6 +210,7 @@ private synchronized void requestRoboEntity() { protected void write(CompoundTag tag, HolderLookup.Provider registries, boolean clientPacket) { super.write(tag, registries, clientPacket); tag.put("RoboBeeInventory", roboBeeInventory.serializeNBT(registries)); + tag.putBoolean("BeeReturnModeEnabled", beeReturnModeEnabled); if (placerUUID != null) { tag.putUUID("PlacerUUID", placerUUID); } @@ -219,6 +222,7 @@ protected void read(CompoundTag tag, HolderLookup.Provider registries, boolean c if (tag.contains("RoboBeeInventory")) { roboBeeInventory.deserializeNBT(registries, tag.getCompound("RoboBeeInventory")); } + beeReturnModeEnabled = tag.getBoolean("BeeReturnModeEnabled"); if (tag.contains("PlacerUUID")) { placerUUID = tag.getUUID("PlacerUUID"); } @@ -241,6 +245,7 @@ public void tick() { int minEta = eta.stream().min(Comparator.naturalOrder()).orElse(-1); this.data.set(0, minEta); this.data.set(1, eta.isEmpty() ? 0 : 1); + this.data.set(2, beeReturnModeEnabled ? 1 : 0); } } @@ -362,7 +367,7 @@ private void sendItem(ItemStack itemStack, int slot) { // Check if the item can be sent to another drone port. if (CMPConfigs.server().portToPort.get() && !PackageItem.matchAddress(address, addressFilter)) { BeePortBlockEntity beePortBlockEntity = CMPHelper.getClosestBeePort(level, address, this.getBlockPos(), null, getLogisticsNetworkId()); - if (beePortBlockEntity != null && !beePortBlockEntity.isFull()) { + if (beePortBlockEntity != null && beePortBlockEntity.hasSpaceForPackageAndRobo()) { sendDrone(itemStack, slot); } } @@ -411,7 +416,7 @@ private void sendDrone(ItemStack itemStack, int slot) { } roboSendCooldown = 2; if (level instanceof ServerLevel serverLevel) { - RoboManager.get(serverLevel).newRobo(serverLevel, itemStack, this.getBlockPos(), this.getLogisticsNetworkId(), 0, this.getBlockPos()); + RoboManager.get(serverLevel).newRobo(serverLevel, itemStack, this.getBlockPos(), this.getLogisticsNetworkId(), 0, this.getBlockPos(), beeReturnModeEnabled); } inventory.setStackInSlot(slot, ItemStack.EMPTY); } @@ -523,36 +528,35 @@ public void destroy() { } /** - * Checks if the drone port is full, considering a specified number of slots to leave empty. + * Checks if the at least one of the PackageSlots is empty. * - * @param slotsToLeaveEmpty The number of slots that should remain empty. - * @return True if the number of empty slots is less than or equal to the specified slots to leave empty, false otherwise. + * @return True if at least one slot is empty, false if all slots are full. */ - public boolean hasFullInventory(int slotsToLeaveEmpty) { - int emptySlots = 0; + public boolean hasSpaceForPackage() { for (int i = 0; i < inventory.getSlots(); i++) { if (inventory.getStackInSlot(i).isEmpty()) { - emptySlots++; + return true; } } - return emptySlots <= slotsToLeaveEmpty; - } - - public synchronized boolean hasFullRoboSlot(int leaveEmpty) { - return roboBeeInventory.getStackInSlot(0).getCount() >= ROBOBEE_INVENTORY_STACK_SIZE - leaveEmpty; + return false; } /** - * Checks if the drone port is full. + * Checks if there is space for at least one Robo-Bee in the RoboBeeInventory. * - * @return True if the drone port is full, false otherwise. + * @return True if there is space for at least one Robo-Bee, false otherwise. */ - public boolean isFull() { - return isFull(0); + public synchronized boolean hasSpaceForRobo() { + return roboBeeInventory.getStackInSlot(0).getCount() < ROBOBEE_INVENTORY_STACK_SIZE; } - public boolean isFull(int slotsToLeaveEmpty) { - return hasFullInventory(slotsToLeaveEmpty) || hasFullRoboSlot(0); + /** + * Checks if there is space for at least one Package and one Robo-Bee in the respective inventories. + * + * @return True if there is space for at least one Package and one Robo-Bee, false otherwise. + */ + public boolean hasSpaceForPackageAndRobo() { + return hasSpaceForPackage() && hasSpaceForRobo(); } /** @@ -564,9 +568,9 @@ public boolean isFull(int slotsToLeaveEmpty) { */ public synchronized boolean canAcceptEntity(VirtualRobo entity, Boolean hasPackage) { if (this.isRemoved()) return false; - if (entity == null) return hasPackage ? !isFull() : !hasFullRoboSlot(0); + if (entity == null) return hasPackage ? hasSpaceForPackageAndRobo() : hasSpaceForRobo(); if (hasRoboRequest()) return false; - return hasPackage ? !isFull() : !hasFullRoboSlot(0); + return hasPackage ? hasSpaceForPackageAndRobo() : hasSpaceForRobo(); } public ItemStackHandler getRoboBeeInventory() { @@ -616,4 +620,13 @@ public void handleRequest(RoboRequest request) { RoboManager.get(serverLevel).newRequestRobo(serverLevel, this.getBlockPos(), request); } } + + public void setBeeReturnModeEnabled(boolean beeReturnModeEnabled) { + if (this.beeReturnModeEnabled == beeReturnModeEnabled) return; + this.beeReturnModeEnabled = beeReturnModeEnabled; + if (level != null && !level.isClientSide) { + level.blockEntityChanged(worldPosition); + } + setChanged(); + } } \ No newline at end of file diff --git a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortMenu.java b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortMenu.java index 4b331717..29d3909c 100644 --- a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortMenu.java +++ b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortMenu.java @@ -127,4 +127,11 @@ public boolean isBeeOnTravel() { } return false; } + + public boolean isBeeReturnModeEnabled() { + if (data != null) { + return data.get(2) == 1; + } + return false; + } } diff --git a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortScreen.java b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortScreen.java index 108e4ec0..452df400 100644 --- a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortScreen.java +++ b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/BeePortScreen.java @@ -3,7 +3,10 @@ import com.simibubi.create.content.logistics.packagePort.PackagePortMenu; import com.simibubi.create.content.logistics.packagePort.PackagePortScreen; import com.simibubi.create.content.logistics.packagerLink.LogisticallyLinkedBehaviour; +import com.simibubi.create.foundation.gui.widget.IconButton; import de.theidler.create_mobile_packages.CreateMobilePackages; +import de.theidler.create_mobile_packages.index.CMPIcons; +import net.createmod.catnip.platform.CatnipServices; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; @@ -12,6 +15,9 @@ public class BeePortScreen extends PackagePortScreen { + private IconButton enableReturnModeButton; + private IconButton disableReturnModeButton; + public BeePortScreen(PackagePortMenu container, Inventory inv, Component title) { super(container, inv, title); } @@ -19,9 +25,31 @@ public BeePortScreen(PackagePortMenu container, Inventory inv, Component title) @Override protected void init() { super.init(); + int buttonX = getGuiLeft() - 22; + int buttonY = getGuiTop() - 10; + LogisticallyLinkedBehaviour lo = (LogisticallyLinkedBehaviour) menu.contentHolder.getAllBehaviours().stream().filter(b -> b instanceof LogisticallyLinkedBehaviour).findFirst().orElse(null); if (lo == null) return; - addRenderableWidget(createNetworkSettingsButton(getGuiLeft() - 22, getGuiTop() - 10, lo.freqId)); + addRenderableWidget(createNetworkSettingsButton(buttonX, buttonY, lo.freqId)); + + if (!(menu instanceof BeePortMenu beePortMenu) || !(menu.contentHolder instanceof BeePortBlockEntity beePort)) + return; + + enableReturnModeButton = new IconButton(buttonX, buttonY + 18, CMPIcons.I_RETURN); + enableReturnModeButton.setToolTip(Component.translatable("tooltip.create_mobile_packages.bee_port.enable_return_mode")); + enableReturnModeButton.withCallback(() -> CatnipServices.NETWORK.sendToServer( + new ToggleBeeReturnModePacket(beePort.getBlockPos(), true) + )); + addRenderableWidget(enableReturnModeButton); + + disableReturnModeButton = new IconButton(buttonX, buttonY + 18, CMPIcons.I_DIRECT); + disableReturnModeButton.setToolTip(Component.translatable("tooltip.create_mobile_packages.bee_port.disable_return_mode")); + disableReturnModeButton.withCallback(() -> CatnipServices.NETWORK.sendToServer( + new ToggleBeeReturnModePacket(beePort.getBlockPos(), false) + )); + addRenderableWidget(disableReturnModeButton); + + updateReturnModeButtons(beePortMenu.isBeeReturnModeEnabled()); } @Override @@ -30,6 +58,7 @@ protected void renderBg(GuiGraphics graphics, float pPartialTick, int pMouseX, i graphics.blit(CreateMobilePackages.asResource("textures/gui/bee_port.png"), getGuiLeft(), getGuiTop(), 0, 47, 220, 82); if (menu instanceof BeePortMenu beePortMenu) { + updateReturnModeButtons(beePortMenu.isBeeReturnModeEnabled()); int eta = beePortMenu.getETA(); Component text = beePortMenu.isBeeOnTravel() ? Component.translatable("create_mobile_packages.bee_port.screen.arrival_time", eta) @@ -37,4 +66,12 @@ protected void renderBg(GuiGraphics graphics, float pPartialTick, int pMouseX, i graphics.drawString(font, text, getGuiLeft() + 34, getGuiTop() + 64, 0x3D3C48, false); } } + + private void updateReturnModeButtons(boolean returnModeEnabled) { + if (enableReturnModeButton == null || disableReturnModeButton == null) return; + enableReturnModeButton.visible = !returnModeEnabled; + enableReturnModeButton.active = !returnModeEnabled; + disableReturnModeButton.visible = returnModeEnabled; + disableReturnModeButton.active = returnModeEnabled; + } } diff --git a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/RoboRequest.java b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/RoboRequest.java index 77119003..d325ea0e 100644 --- a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/RoboRequest.java +++ b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/RoboRequest.java @@ -1,21 +1,28 @@ package de.theidler.create_mobile_packages.blocks.bee_port; -import net.minecraft.core.BlockPos; +import de.theidler.create_mobile_packages.robo.RoboTarget; +import net.minecraft.world.phys.Vec3; import java.util.UUID; -public class RoboRequest { //TODO: integrate with RoboTarget logic to allow requests from players / ports and blocks +public class RoboRequest { private final UUID logisticsNetworkId; - BlockPos targetPos; + private final Mission mission; long createdAt; private Status status; private int eta = -1; + RoboTarget target; - public RoboRequest(BlockPos pos, UUID logisticsNetworkId) { - this.targetPos = pos; + public RoboRequest(RoboTarget target, UUID logisticsNetworkId, Mission mission) { + this.target = target; status = Status.PENDING; createdAt = System.currentTimeMillis(); this.logisticsNetworkId = logisticsNetworkId; + this.mission = mission; + } + + public Mission getMission() { + return mission; } public int getEta() { @@ -39,8 +46,12 @@ public void setStatus(Status status) { this.status = status; } - public BlockPos getTargetPos() { - return targetPos; + public Vec3 getTargetPos() { + return target.getTargetPos(); + } + + public RoboTarget getTarget() { + return target; } public long getCreatedAt() { @@ -50,4 +61,10 @@ public long getCreatedAt() { public enum Status { PENDING, IN_PROGRESS, DONE, CANCELLED } + + public enum Mission { + RESTOCK, // TODO: remove and replace with PICKUP for "fly by" PICKUP + DELIVER, + PICKUP + } } diff --git a/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/ToggleBeeReturnModePacket.java b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/ToggleBeeReturnModePacket.java new file mode 100644 index 00000000..34d2cbb4 --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/blocks/bee_port/ToggleBeeReturnModePacket.java @@ -0,0 +1,55 @@ +package de.theidler.create_mobile_packages.blocks.bee_port; + +import de.theidler.create_mobile_packages.IExtendedLogisticsNetwork; +import de.theidler.create_mobile_packages.index.CMPPackets; +import de.theidler.create_mobile_packages.network_settings.NetworkHelper; +import net.createmod.catnip.net.base.ServerboundPacketPayload; +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; + +public class ToggleBeeReturnModePacket implements ServerboundPacketPayload { + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, packet -> packet.portPos, + ByteBufCodecs.BOOL, packet -> packet.returnModeEnabled, + ToggleBeeReturnModePacket::new + ); + + private final BlockPos portPos; + private final boolean returnModeEnabled; + + public ToggleBeeReturnModePacket(BlockPos portPos, boolean returnModeEnabled) { + this.portPos = portPos; + this.returnModeEnabled = returnModeEnabled; + } + + @Override + public void handle(ServerPlayer player) { + if (player == null) return; + ServerLevel serverLevel = player.serverLevel(); + if (!(serverLevel.getBlockEntity(portPos) instanceof BeePortBlockEntity beePort)) return; + + if (player.distanceToSqr(portPos.getCenter()) > 64.0) return; + + if (beePort.behaviour != null) { + IExtendedLogisticsNetwork network = NetworkHelper.getExtendedLogisticsNetwork(beePort.behaviour.freqId); + if (network != null + && !network.create_mobile_packages$getPlayers().contains(player.getUUID()) + && !beePort.behaviour.mayInteractMessage(player) + ) { + return; + } + } + + beePort.setBeeReturnModeEnabled(returnModeEnabled); + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.TOGGLE_BEE_RETURN_MODE; + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeBehaviorController.java b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeBehaviorController.java index 3f75eb90..d6adc93a 100644 --- a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeBehaviorController.java +++ b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeBehaviorController.java @@ -3,14 +3,23 @@ import com.simibubi.create.content.logistics.box.PackageItem; import de.theidler.create_mobile_packages.blocks.bee_port.BeePortBlockEntity; import de.theidler.create_mobile_packages.blocks.bee_port.RoboRequest; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.SyncTrashItemsToClientPacket; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.TrashMenu; import de.theidler.create_mobile_packages.robo.PlayerTarget; +import de.theidler.create_mobile_packages.robo.RoboManager; +import de.theidler.create_mobile_packages.robo.RoboTrashStore; import de.theidler.create_mobile_packages.robo.VirtualRobo; +import net.createmod.catnip.platform.CatnipServices; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; +import java.util.List; + import static de.theidler.create_mobile_packages.CMPHelper.calcETA; public class RoboBeeBehaviorController { @@ -37,15 +46,66 @@ public void tick(VirtualRobo robo) { case DELIVER_PACKAGE: handleDeliverPackage(robo); break; + case PICKUP_PACKAGE: + handlePickupPackage(robo); + break; case SHUTDOWN: handleShutdown(robo); break; } } + private void handlePickupPackage(VirtualRobo robo) { + boolean pickedUp = false; + // Try to pickup from player + if (robo.getTarget() != null && robo.getTarget().asPlayer() != null) { + pickedUp = pickupPackageFromPlayer(robo.getTarget().asPlayer(), robo); + } + // TODO: PICKUP from bee port + + if (pickedUp) { + robo.clearRequest(); + if (robo.getItemStack() != null) { + robo.setTargetAddress(PackageItem.getAddress(robo.getItemStack()), true); + } + setState(RoboBeeState.IDLE); + } + } + + private boolean pickupPackageFromPlayer(Player player, VirtualRobo robo) { + RoboManager manager = RoboManager.get(robo.getServerLevel()); + RoboTrashStore trashStore = manager.getTrashStore(robo.getLogisticsNetworkId(), player.getUUID()); + if (trashStore == null || !trashStore.hasItems()) { + return true; + } + String targetAddress = trashStore.getTargetAddress(); + + List takenItems = manager.takeTrashItems(robo.getLogisticsNetworkId(), player.getUUID()); + if (takenItems == null) { + return true; + } + + ServerPlayer serverPlayer = robo.getServerLevel().getServer().getPlayerList().getPlayer(player.getUUID()); + if (serverPlayer != null) { + if (serverPlayer.containerMenu instanceof TrashMenu trashMenu) { + trashMenu.markAsPickedUpByRobo(); + } + CatnipServices.NETWORK.sendToClient(serverPlayer, new SyncTrashItemsToClientPacket(List.of())); + } + + ItemStack packageItem = PackageItem.containing(takenItems); + if (packageItem.isEmpty()) { + return false; + } + + PackageItem.addAddress(packageItem, targetAddress); + robo.setItemStack(packageItem); + return true; + } + private void handleIdle(VirtualRobo robo) { robo.setTargetVelocity(Vec3.ZERO); - if (robo.getTarget() != null && robo.getTarget().isValid()) { + if (robo.getTarget() != null && robo.getTarget().isValid(robo)) { setState(RoboBeeState.TAKEOFF); } } @@ -112,7 +172,11 @@ private void handleAlignForDelivery(VirtualRobo robo) { private void handleLand(VirtualRobo robo) { @Nullable BeePortBlockEntity port = robo.getTarget() != null ? robo.getTarget().asBeePortBlockEntity() : null; if (port == null) { - setState(RoboBeeState.DELIVER_PACKAGE); + if (robo.getRequest() != null && robo.getRequest().getMission() == RoboRequest.Mission.PICKUP) { + setState(RoboBeeState.PICKUP_PACKAGE); + } else { + setState(RoboBeeState.DELIVER_PACKAGE); + } return; } Vec3 end = getBelow(port, 0.5); @@ -134,7 +198,11 @@ private void handleLand(VirtualRobo robo) { robo.setPos(end); robo.setTargetVelocity(Vec3.ZERO); openPort(robo.getTarget().asBeePortBlockEntity(), false); - setState(RoboBeeState.DELIVER_PACKAGE); + if (robo.getRequest() != null && robo.getRequest().getMission() == RoboRequest.Mission.PICKUP) { + setState(RoboBeeState.PICKUP_PACKAGE); + } else { + setState(RoboBeeState.DELIVER_PACKAGE); + } } } diff --git a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeState.java b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeState.java index 34780bf1..6cf7d5d5 100644 --- a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeState.java +++ b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboBeeState.java @@ -6,6 +6,7 @@ public enum RoboBeeState { NAVIGATE_TO_TARGET, ALIGN_FOR_DELIVERY, DELIVER_PACKAGE, + PICKUP_PACKAGE, LAND, SHUTDOWN } diff --git a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboEntity.java b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboEntity.java index bf525a82..58dcf0fa 100644 --- a/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboEntity.java +++ b/src/main/java/de/theidler/create_mobile_packages/entities/robo_entity/RoboEntity.java @@ -1,6 +1,5 @@ package de.theidler.create_mobile_packages.entities.robo_entity; -import com.simibubi.create.Create; import de.theidler.create_mobile_packages.IExtendedLogisticsNetwork; import de.theidler.create_mobile_packages.index.CMPItems; import de.theidler.create_mobile_packages.index.config.CMPConfigs; @@ -98,6 +97,9 @@ private void updateNametag(VirtualRobo virtualRobo) { BlockPos pos = virtualRobo.getTarget().asBeePortBlockEntity().getBlockPos(); setCustomName(Component.literal("-> [" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + "]")); setCustomNameVisible(true); + } else if (virtualRobo.getTarget() != null && virtualRobo.getTarget().asPlayer() != null) { + setCustomName(Component.literal("-> " + virtualRobo.getTarget().asPlayer().getName().getString())); + setCustomNameVisible(true); } else { setCustomName(Component.translatable("entity.create_mobile_packages.robo_bee.no_valid_target")); setCustomNameVisible(true); diff --git a/src/main/java/de/theidler/create_mobile_packages/index/CMPCommands.java b/src/main/java/de/theidler/create_mobile_packages/index/CMPCommands.java index 4904658a..55be5904 100644 --- a/src/main/java/de/theidler/create_mobile_packages/index/CMPCommands.java +++ b/src/main/java/de/theidler/create_mobile_packages/index/CMPCommands.java @@ -3,16 +3,21 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; +import com.simibubi.create.Create; +import de.theidler.create_mobile_packages.IExtendedLogisticsNetwork; +import de.theidler.create_mobile_packages.network_settings.NetworkHelper; import de.theidler.create_mobile_packages.robo.RoboManager; -import de.theidler.create_mobile_packages.toast.types.SimpleToast; import de.theidler.create_mobile_packages.toast.RemoveAllToastsOnClientPacket; import de.theidler.create_mobile_packages.toast.ShowToastOnClientPacket; +import de.theidler.create_mobile_packages.toast.types.SimpleToast; import net.createmod.catnip.platform.CatnipServices; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import java.util.UUID; @@ -47,9 +52,100 @@ public static void register(CommandDispatcher dispatcher) { .executes(CMPCommands::clearRobos) ) ) + .then( + Commands.literal("network") + .requires(cs -> cs.hasPermission(2)) // admin only + .then( + Commands.literal("list") + .executes(CMPCommands::showAllNetworks) + ) + .then( + Commands.literal("add") + .then( + Commands.argument("player", EntityArgument.player()) + .then( + Commands.argument("networkId", StringArgumentType.word()) + .suggests((ctx, builder) -> { + Create.LOGISTICS.logisticsNetworks.forEach((uuid, value) -> { + IExtendedLogisticsNetwork extendedNetwork = NetworkHelper.getExtendedLogisticsNetwork(value); + String name = extendedNetwork != null ? extendedNetwork.create_mobile_packages$getName() : "Unnamed Network"; + if (!name.equals("Unnamed Network")) { + builder.suggest(uuid.toString()); + } + }); + return builder.buildFuture(); + }) + .executes(ctx -> addPlayerToNetwork(ctx, + EntityArgument.getPlayer(ctx, "player"), + UUID.fromString(StringArgumentType.getString(ctx, "networkId")))) + ) + ) + ) + .then( + Commands.literal("remove") + .then( + Commands.argument("player", EntityArgument.player()) + .then( + Commands.argument("networkId", StringArgumentType.word()) + .suggests((ctx, builder) -> { + ServerPlayer player = EntityArgument.getPlayer(ctx, "player"); + Create.LOGISTICS.logisticsNetworks.forEach((key, value) -> { + IExtendedLogisticsNetwork extendedNetwork = NetworkHelper.getExtendedLogisticsNetwork(value); + if (extendedNetwork != null && extendedNetwork.create_mobile_packages$getPlayers().contains(player.getUUID())) { + builder.suggest(key.toString()); + } + }); + return builder.buildFuture(); + }) + .executes(ctx -> removePlayerFromNetwork(ctx, + EntityArgument.getPlayer(ctx, "player"), + UUID.fromString(StringArgumentType.getString(ctx, "networkId")))) + ) + ) + ) + ) ); } + private static int showAllNetworks(CommandContext context) { + CommandSourceStack source = context.getSource(); + ServerLevel level = source.getLevel(); + + if (Create.LOGISTICS.logisticsNetworks.isEmpty()) { + source.sendSuccess(() -> Component.literal("No logistics networks found."), false); + return 0; + } + + StringBuilder output = new StringBuilder("═══════════════════════════════════\n"); + output.append("Logistics Networks:\n"); + + for (var entry : Create.LOGISTICS.logisticsNetworks.entrySet()) { + UUID networkId = entry.getKey(); + var logisticsNetwork = entry.getValue(); + IExtendedLogisticsNetwork extendedNetwork = NetworkHelper.getExtendedLogisticsNetwork(logisticsNetwork); + + String name = extendedNetwork != null ? extendedNetwork.create_mobile_packages$getName() : "Unnamed Network"; + if (name.equals("Unnamed Network")) continue; // Skip unnamed networks + + int playerCount = extendedNetwork.create_mobile_packages$getPlayers().size(); + boolean isLocked = logisticsNetwork.locked; + Player owner = level.getPlayerByUUID(logisticsNetwork.owner); + String ownerName = owner != null ? owner.getName().getString() : "Unknown"; + + String shortId = networkId.toString().substring(0, 8); + output.append("\n▸ ").append(name) + .append(" | ID: ").append(shortId).append("...") + .append("\n └─ Players: ").append(playerCount) + .append(" | Status: ").append(isLocked ? "🔒 Locked" : "🔓 Unlocked") + .append(" | Owner: ").append(ownerName); + } + + output.append("\n═══════════════════════════════════"); + String finalOutput = output.toString(); + source.sendSuccess(() -> Component.literal(finalOutput), false); + return 1; + } + private static int clearRobos(CommandContext context) { CommandSourceStack source = context.getSource(); ServerLevel level = source.getLevel(); @@ -78,4 +174,26 @@ private static int clearToasts(CommandContext context) { CatnipServices.NETWORK.sendToClient(player, RemoveAllToastsOnClientPacket.INSTANCE); return 1; } + + private static int addPlayerToNetwork(CommandContext context, ServerPlayer targetPlayer, UUID networkId) { + CommandSourceStack source = context.getSource(); + + IExtendedLogisticsNetwork network = NetworkHelper.getExtendedLogisticsNetwork(networkId); + if (network == null) return 0; + network.create_mobile_packages$addPlayer(targetPlayer.getUUID()); + + source.sendSuccess(() -> Component.literal("Added player " + targetPlayer.getName().getString() + " to network " + networkId), true); + return 1; + } + + private static int removePlayerFromNetwork(CommandContext context, ServerPlayer targetPlayer, UUID networkId) { + CommandSourceStack source = context.getSource(); + + IExtendedLogisticsNetwork network = NetworkHelper.getExtendedLogisticsNetwork(networkId); + if (network == null) return 0; + network.create_mobile_packages$removePlayer(targetPlayer.getUUID()); + + source.sendSuccess(() -> Component.literal("Removed player " + targetPlayer.getName().getString() + " from network " + networkId), true); + return 1; + } } diff --git a/src/main/java/de/theidler/create_mobile_packages/index/CMPGuiTextures.java b/src/main/java/de/theidler/create_mobile_packages/index/CMPGuiTextures.java index a0f192b9..f5c49463 100644 --- a/src/main/java/de/theidler/create_mobile_packages/index/CMPGuiTextures.java +++ b/src/main/java/de/theidler/create_mobile_packages/index/CMPGuiTextures.java @@ -17,7 +17,9 @@ public enum CMPGuiTextures implements ScreenElement, TextureSheetSegment { PLAYER_NETWORKS_EDIT_NAME("player_networks", 230, 3, 13, 13), PLAYER_NETWORKS_BG("player_networks", 2, 36, 210, 22), PLAYER_NETWORKS_FOOTER("player_networks", 2, 73, 218, 31), - PLAYER_NETWORKS_SLOT("player_networks", 2, 113, 210, 18) + PLAYER_NETWORKS_SLOT("player_networks", 2, 113, 210, 18), + + TRASH_MENU("trash_menu", 0, 0, 256, 95) ; diff --git a/src/main/java/de/theidler/create_mobile_packages/index/CMPIcons.java b/src/main/java/de/theidler/create_mobile_packages/index/CMPIcons.java new file mode 100644 index 00000000..2896e874 --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/index/CMPIcons.java @@ -0,0 +1,42 @@ +package de.theidler.create_mobile_packages.index; + +import de.theidler.create_mobile_packages.CreateMobilePackages; +import net.createmod.catnip.gui.element.ScreenElement; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import org.jetbrains.annotations.NotNull; + +public class CMPIcons implements ScreenElement { + // from com.simibubi.create.foundation.gui.AllIcons + public static final ResourceLocation ICON_ATLAS = ResourceLocation.fromNamespaceAndPath(CreateMobilePackages.MODID, "textures/gui/icons.png"); + public static final int ICON_ATLAS_SIZE = 256; + + private static int x = 0, y = -1; + public static final CMPIcons + I_RETURN = newRow(), + I_DIRECT = next(); + private final int iconX; + private final int iconY; + + + public CMPIcons(int x, int y) { + iconX = x * 16; + iconY = y * 16; + } + + private static CMPIcons next() { + return new CMPIcons(++x, y); + } + + private static CMPIcons newRow() { + return new CMPIcons(x = 0, ++y); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void render(@NotNull GuiGraphics graphics, int x, int y) { + graphics.blit(ICON_ATLAS, x, y, 0, iconX, iconY, 16, 16, ICON_ATLAS_SIZE, ICON_ATLAS_SIZE); + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/index/CMPMenuTypes.java b/src/main/java/de/theidler/create_mobile_packages/index/CMPMenuTypes.java index 78e53c64..16215151 100644 --- a/src/main/java/de/theidler/create_mobile_packages/index/CMPMenuTypes.java +++ b/src/main/java/de/theidler/create_mobile_packages/index/CMPMenuTypes.java @@ -6,8 +6,12 @@ import de.theidler.create_mobile_packages.blocks.bee_port.BeePortMenu; import de.theidler.create_mobile_packages.blocks.bee_port.BeePortScreen; import de.theidler.create_mobile_packages.items.mobile_packager.*; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTicker; import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTickerMenu; import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTickerScreen; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.TrashMenu; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.TrashScreen; +import net.minecraft.world.item.ItemStack; public class CMPMenuTypes { @@ -39,6 +43,19 @@ public class CMPMenuTypes { () -> MobilePackagerEditScreen::new ).register(); + public static final MenuEntry TRASH_MENU = + CreateMobilePackages.REGISTRATE.menu( + "trash_menu", + (trashMenuType, containerId, playerInventory) -> { + ItemStack pstStack = PortableStockTicker.find(playerInventory); + PortableStockTicker pst = (pstStack != null && pstStack.getItem() instanceof PortableStockTicker) + ? (PortableStockTicker) pstStack.getItem() + : null; + return new TrashMenu(containerId, playerInventory, pst); + }, + () -> TrashScreen::new + ).register(); + public static void register() { } } diff --git a/src/main/java/de/theidler/create_mobile_packages/index/CMPPackets.java b/src/main/java/de/theidler/create_mobile_packages/index/CMPPackets.java index da4d224c..2a861753 100644 --- a/src/main/java/de/theidler/create_mobile_packages/index/CMPPackets.java +++ b/src/main/java/de/theidler/create_mobile_packages/index/CMPPackets.java @@ -1,9 +1,14 @@ package de.theidler.create_mobile_packages.index; import de.theidler.create_mobile_packages.CreateMobilePackages; +import de.theidler.create_mobile_packages.blocks.bee_port.ToggleBeeReturnModePacket; import de.theidler.create_mobile_packages.items.mobile_packager.ConfirmEditMenuPacket; import de.theidler.create_mobile_packages.items.mobile_packager.OpenEditMenuPacket; import de.theidler.create_mobile_packages.items.portable_stock_ticker.*; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.OpenTrashMenuPacket; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.SyncTrashAddressPacket; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.SyncTrashAddressToClientPacket; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.SyncTrashItemsToClientPacket; import de.theidler.create_mobile_packages.network_settings.*; import de.theidler.create_mobile_packages.toast.RemoveAllToastsOnClientPacket; import de.theidler.create_mobile_packages.toast.RemoveToastOnClientPacket; @@ -30,10 +35,15 @@ public enum CMPPackets implements BasePacketPayload.PacketTypeProvider { MODIFY_NETWORK_LOCK_STATE(ModifyNetworkLockStatePackage.class, ModifyNetworkLockStatePackage.STREAM_CODEC), REQUEST_NETWORK_DATA(RequestNetworkDataPacket.class, RequestNetworkDataPacket.STREAM_CODEC), REQUEST_PLAYER_NETWORKS(RequestPlayerNetworksPacket.class, RequestPlayerNetworksPacket.STREAM_CODEC), - + OPEN_TRASH_MENU(OpenTrashMenuPacket.class, OpenTrashMenuPacket.STREAM_CODEC), + SYNC_TRASH_ADDRESS(SyncTrashAddressPacket.class, SyncTrashAddressPacket.STREAM_CODEC), + SAVE_PORTABLE_STOCK_TICKER_ADDRESS(SavePortableStockTickerAddressPacket.class, SavePortableStockTickerAddressPacket.STREAM_CODEC), + TOGGLE_BEE_RETURN_MODE(ToggleBeeReturnModePacket.class, ToggleBeeReturnModePacket.STREAM_CODEC), // Server to Client BIG_ITEM_STACK_LIST(GenericStackListPacket.class, GenericStackListPacket.STREAM_CODEC), + SYNC_TRASH_ADDRESS_TO_CLIENT(SyncTrashAddressToClientPacket.class, SyncTrashAddressToClientPacket.STREAM_CODEC), + SYNC_TRASH_ITEMS_TO_CLIENT(SyncTrashItemsToClientPacket.class, SyncTrashItemsToClientPacket.STREAM_CODEC), SHOW_TOAST_ON_CLIENT(ShowToastOnClientPacket.class, ShowToastOnClientPacket.STREAM_CODEC), REMOVE_TOAST_ON_CLIENT(RemoveToastOnClientPacket.class, RemoveToastOnClientPacket.STREAM_CODEC), REMOVE_ALL_TOAST_ON_CLIENT(RemoveAllToastsOnClientPacket.class, RemoveAllToastsOnClientPacket.STREAM_CODEC), diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/PortableStockTickerScreen.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/PortableStockTickerScreen.java index 4c5201f6..a8b36b4b 100644 --- a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/PortableStockTickerScreen.java +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/PortableStockTickerScreen.java @@ -12,13 +12,16 @@ import com.simibubi.create.content.logistics.stockTicker.PackageOrderWithCrafts; import com.simibubi.create.content.trains.station.NoShadowFontWrapper; import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.menu.AbstractSimiContainerScreen; +import com.simibubi.create.foundation.gui.widget.IconButton; import com.simibubi.create.foundation.gui.widget.ScrollInput; import com.simibubi.create.foundation.utility.CreateLang; import com.simibubi.create.infrastructure.config.AllConfigs; import de.theidler.create_mobile_packages.CreateMobilePackages; import de.theidler.create_mobile_packages.compat.Mods; import de.theidler.create_mobile_packages.compat.jei.CMPJEI; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.OpenTrashMenuPacket; import net.createmod.catnip.animation.LerpedFloat; import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Pair; @@ -57,7 +60,6 @@ import javax.annotation.Nullable; import java.util.*; -import java.util.Optional; public class PortableStockTickerScreen extends AbstractSimiContainerScreen implements OrderProvider, CategoriesProvider { @@ -67,6 +69,8 @@ public class PortableStockTickerScreen extends AbstractSimiContainerScreen stacks) { // Sort first (O(N log N)) stacks.sort(Comparator.comparingInt((GenericStack bigStack) -> -bigStack.amount())); @@ -256,11 +279,27 @@ protected void init() { addWidget(searchBox); boolean initial = addressBox == null; - String previouslyUsedAddress = initial ? menu.portableStockTicker.previouslyUsedAddress : addressBox.getValue(); + // Load address directly from the ItemStack to ensure it persists across world reloads + ItemStack pstStack = PortableStockTicker.find(playerInventory); + String previouslyUsedAddress = ""; + if (pstStack != null && pstStack.getItem() instanceof PortableStockTicker pst) { + String loadedAddress = pst.loadAddressFromStack(pstStack); + previouslyUsedAddress = loadedAddress != null ? loadedAddress : ""; + } + // Fall back to the in-memory address if nothing was loaded from stack + if (previouslyUsedAddress.isEmpty() && !initial) { + previouslyUsedAddress = addressBox.getValue(); + } + // If still empty, use the item's previouslyUsedAddress field as last resort + if (previouslyUsedAddress.isEmpty()) { + previouslyUsedAddress = menu.portableStockTicker.previouslyUsedAddress != null ? + menu.portableStockTicker.previouslyUsedAddress : ""; + } addressBox = new AddressEditBox(this, new NoShadowFontWrapper(font), x + 27, y + windowHeight - 36, 92, 10, true, "@" + this.playerInventory.player.getName().getString()); addressBox.setTextColor(0x714A40); addressBox.setValue(previouslyUsedAddress); + lastSyncedAddress = previouslyUsedAddress; addRenderableWidget(addressBox); ClientScreenStorage.manualUpdate(); @@ -271,6 +310,10 @@ protected void init() { playUiSound(SoundEvents.BOOK_PAGE_TURN, 1, 1); syncRecipeViewers(); } + + trashMenuButton = new IconButton(x - 10, y + 25, AllIcons.I_TRASH); + trashMenuButton.withCallback(() -> CatnipServices.NETWORK.sendToServer(OpenTrashMenuPacket.INSTANCE)); + addRenderableWidget(trashMenuButton); } private Couple getHoveredSlot(int x, int y) { diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/RequestStockUpdate.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/RequestStockUpdate.java index 4a01756c..522e285b 100644 --- a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/RequestStockUpdate.java +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/RequestStockUpdate.java @@ -30,6 +30,11 @@ public void handle(ServerPlayer player) { return; List allStacks = getAccurateSummary(stack).get(); + if (allStacks.isEmpty()) { + CatnipServices.NETWORK.sendToClient(player, new GenericStackListPacket(List.of(), true)); + return; + } + for (int i = 0; i < allStacks.size(); i += MAX_ITEMS_PER_PACKET) { int end = Math.min(i + MAX_ITEMS_PER_PACKET, allStacks.size()); boolean last = end == allStacks.size(); diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/SavePortableStockTickerAddressPacket.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/SavePortableStockTickerAddressPacket.java new file mode 100644 index 00000000..7597babe --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/SavePortableStockTickerAddressPacket.java @@ -0,0 +1,40 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker; + +import de.theidler.create_mobile_packages.index.CMPPackets; +import net.createmod.catnip.net.base.ServerboundPacketPayload; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +public class SavePortableStockTickerAddressPacket implements ServerboundPacketPayload { + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, packet -> packet.address, + SavePortableStockTickerAddressPacket::new + ); + + private final String address; + + public SavePortableStockTickerAddressPacket(String address) { + this.address = address; + } + + @Override + public void handle(ServerPlayer player) { + if (player == null) return; + + ItemStack pstStack = PortableStockTicker.find(player.getInventory()); + if (pstStack == null) return; + + PortableStockTicker pst = (PortableStockTicker) pstStack.getItem(); + pst.saveAddressToStack(pstStack, address); + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.SAVE_PORTABLE_STOCK_TICKER_ADDRESS; + } +} + diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/OpenTrashMenuPacket.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/OpenTrashMenuPacket.java new file mode 100644 index 00000000..7c35f8ea --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/OpenTrashMenuPacket.java @@ -0,0 +1,52 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import de.theidler.create_mobile_packages.index.CMPPackets; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTicker; +import de.theidler.create_mobile_packages.robo.RoboManager; +import de.theidler.create_mobile_packages.robo.RoboTrashStore; +import net.createmod.catnip.net.base.ServerboundPacketPayload; +import net.createmod.catnip.platform.CatnipServices; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.item.ItemStack; + +import java.util.UUID; + +import static de.theidler.create_mobile_packages.items.portable_stock_ticker.LogisticallyLinkedItem.networkFromStack; + +public class OpenTrashMenuPacket implements ServerboundPacketPayload { + public static final OpenTrashMenuPacket INSTANCE = new OpenTrashMenuPacket(); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(INSTANCE); + + @Override + public void handle(ServerPlayer player) { + if (player == null || !player.isAlive()) return; + + ItemStack pstItem = PortableStockTicker.find(player.getInventory()); + if (pstItem == null) return; + PortableStockTicker pst = (PortableStockTicker) pstItem.getItem(); + UUID networkId = networkFromStack(pstItem); + if (networkId == null) return; // This should never happen, but just in case + + RoboTrashStore trashStore = RoboManager.get(player.serverLevel()).getTrashStore(networkId, player.getUUID()); + String targetAddress = trashStore != null ? trashStore.getTargetAddress() : ""; + + // Open the menu on server with targetAddress + // Menu constructor will load inventory from RoboManager automatically + player.closeContainer(); + player.openMenu(new SimpleMenuProvider( + (id, inv, p) -> new TrashMenu(id, inv, pst, targetAddress), + net.minecraft.network.chat.Component.translatable("item.create_mobile_packages.portable_stock_ticker.trash_menu") + )); + + // Sync address to client (redundant but ensures client is aware) + CatnipServices.NETWORK.sendToClient(player, new SyncTrashAddressToClientPacket(targetAddress)); + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.OPEN_TRASH_MENU; + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressPacket.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressPacket.java new file mode 100644 index 00000000..c88325ef --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressPacket.java @@ -0,0 +1,48 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import de.theidler.create_mobile_packages.index.CMPPackets; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.LogisticallyLinkedItem; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTicker; +import de.theidler.create_mobile_packages.robo.RoboManager; +import net.createmod.catnip.net.base.ServerboundPacketPayload; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +import java.util.UUID; + +public class SyncTrashAddressPacket implements ServerboundPacketPayload { + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, packet -> packet.address, + SyncTrashAddressPacket::new + ); + + private final String address; + + public SyncTrashAddressPacket(String address) { + this.address = address; + } + + @Override + public void handle(ServerPlayer player) { + if (player == null || !(player.level() instanceof ServerLevel serverLevel)) return; + + ItemStack pstItem = PortableStockTicker.find(player.getInventory()); + if (pstItem == null) return; + + UUID networkId = LogisticallyLinkedItem.networkFromStack(pstItem); + if (networkId == null) return; + + RoboManager roboManager = RoboManager.get(serverLevel); + roboManager.setTrashTargetAddress(networkId, player.getUUID(), address); + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.SYNC_TRASH_ADDRESS; + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressToClientPacket.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressToClientPacket.java new file mode 100644 index 00000000..7f9fa296 --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashAddressToClientPacket.java @@ -0,0 +1,43 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import de.theidler.create_mobile_packages.index.CMPPackets; +import net.createmod.catnip.net.base.ClientboundPacketPayload; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; + +public class SyncTrashAddressToClientPacket implements ClientboundPacketPayload { + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, packet -> packet.address, + SyncTrashAddressToClientPacket::new + ); + + private final String address; + + public SyncTrashAddressToClientPacket(String address) { + this.address = address; + } + + @Override + public void handle(LocalPlayer player) { + if (player == null) return; + + if (player.containerMenu instanceof TrashMenu trashMenu) { + trashMenu.setTargetAddress(address); + } + + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.screen instanceof TrashScreen trashScreen) { + trashScreen.applyTargetAddress(address); + } + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.SYNC_TRASH_ADDRESS_TO_CLIENT; + } +} + diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashItemsToClientPacket.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashItemsToClientPacket.java new file mode 100644 index 00000000..8b47da67 --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/SyncTrashItemsToClientPacket.java @@ -0,0 +1,42 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import de.theidler.create_mobile_packages.index.CMPPackets; +import net.createmod.catnip.net.base.ClientboundPacketPayload; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class SyncTrashItemsToClientPacket implements ClientboundPacketPayload { + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.collection(ArrayList::new, ItemStack.OPTIONAL_STREAM_CODEC), packet -> packet.items, + SyncTrashItemsToClientPacket::new + ); + + private final List items; + + public SyncTrashItemsToClientPacket(List items) { + this.items = items; + } + + @Override + public void handle(LocalPlayer player) { + if (player == null) return; + + if (player.containerMenu instanceof TrashMenu trashMenu) { + // Update the trash inventory with the new items + trashMenu.updateTrashInventory(items); + } + } + + @Override + public PacketTypeProvider getTypeProvider() { + return CMPPackets.SYNC_TRASH_ITEMS_TO_CLIENT; + } +} + diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashMenu.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashMenu.java new file mode 100644 index 00000000..42877bff --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashMenu.java @@ -0,0 +1,241 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import com.simibubi.create.foundation.gui.menu.MenuBase; +import de.theidler.create_mobile_packages.index.CMPMenuTypes; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.LogisticallyLinkedItem; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.PortableStockTicker; +import de.theidler.create_mobile_packages.robo.RoboManager; +import de.theidler.create_mobile_packages.robo.RoboTrashStore; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.items.SlotItemHandler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class TrashMenu extends MenuBase { + + private ItemStackHandler trashInventory; + private String targetAddress; + /** + * True after a robo picks up the items; cleared on the next user interaction. + */ + boolean pickedUpByRobo = false; + /** + * True while markAsPickedUpByRobo() clears slots, so setChanged() won't reset the flag. + */ + private boolean clearingForPickup = false; + + public TrashMenu(int id, Inventory playerInventory, PortableStockTicker contentHolder) { + super(CMPMenuTypes.TRASH_MENU.get(), id, playerInventory, contentHolder); + this.targetAddress = ""; + } + + public TrashMenu(int id, Inventory playerInventory, PortableStockTicker contentHolder, String targetAddress) { + super(CMPMenuTypes.TRASH_MENU.get(), id, playerInventory, contentHolder); + this.targetAddress = targetAddress != null ? targetAddress : ""; + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player player, int i) { + ItemStack itemStack = ItemStack.EMPTY; + Slot slot = this.slots.get(i); + + if (slot.hasItem()) { + ItemStack itemStackInSlot = slot.getItem(); + itemStack = itemStackInSlot.copy(); + + if (i < trashInventory.getSlots()) { + // Trash Inventory -> Player Inventory + if (!this.moveItemStackTo(itemStackInSlot, trashInventory.getSlots(), this.slots.size(), true)) { + return ItemStack.EMPTY; + } + } else { + // Player Inventory -> Trash Inventory + if (!this.moveItemStackTo(itemStackInSlot, 0, trashInventory.getSlots(), false)) { + return ItemStack.EMPTY; + } + } + + if (itemStackInSlot.isEmpty()) { + slot.set(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + } + return itemStack; + } + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + + @Override + protected PortableStockTicker createOnClient(RegistryFriendlyByteBuf extraData) { + return null; + } + + @Override + protected void initAndReadInventory(PortableStockTicker contentHolder) { + trashInventory = new ItemStackHandler(9); + + // Ensure targetAddress is initialized (may be called before constructor completes) + if (targetAddress == null) { + targetAddress = ""; + } + + // Server-side only: Load from RoboManager + Level level = player.level(); + if (!level.isClientSide && level instanceof ServerLevel serverLevel) { + UUID networkId = getNetworkId(); + if (networkId != null) { + RoboTrashStore trashStore = RoboManager.get(serverLevel).getTrashStore(networkId, player.getUUID()); + if (trashStore != null) { + List trashSlots = trashStore.getItemStacks(); + for (int i = 0; i < trashSlots.size() && i < trashInventory.getSlots(); i++) { + // Use copies to avoid modifying the original store's items + trashInventory.setStackInSlot(i, trashSlots.get(i).copy()); + } + // Only update targetAddress if it wasn't already set (e.g., from constructor parameter) + if (targetAddress.isEmpty()) { + targetAddress = trashStore.getTargetAddress(); + } + } + } + } + } + + private @Nullable UUID getNetworkId() { + ItemStack stack = PortableStockTicker.find(player.getInventory()); + if (stack != null && stack.getItem() instanceof PortableStockTicker) { + return LogisticallyLinkedItem.networkFromStack(stack); + } + return null; + } + + public String getTargetAddress() { + return targetAddress; + } + + public void setTargetAddress(String address) { + this.targetAddress = address; + } + + /** + * Called by the robo after atomically taking items. Clears the menu inventory and + * blocks saveDataImmediately until the next real user interaction. + */ + public void markAsPickedUpByRobo() { + pickedUpByRobo = true; + clearingForPickup = true; + try { + for (int i = 0; i < trashInventory.getSlots(); i++) { + trashInventory.setStackInSlot(i, ItemStack.EMPTY); + } + } finally { + clearingForPickup = false; + } + } + + public void updateTrashInventory(List items) { + // Update the trash inventory with items from server + for (int i = 0; i < trashInventory.getSlots(); i++) { + if (i < items.size() && !items.get(i).isEmpty()) { + trashInventory.setStackInSlot(i, items.get(i).copy()); + } else { + trashInventory.setStackInSlot(i, ItemStack.EMPTY); + } + } + } + + private List toTrashStacks() { + List trashStacks = new ArrayList<>(); + for (int i = 0; i < trashInventory.getSlots(); i++) { + // Include all slots (even empty) so slot indices are preserved on the client + trashStacks.add(trashInventory.getStackInSlot(i).copy()); + } + return trashStacks; + } + + @Override + protected void addSlots() { + for (int i = 0; i < trashInventory.getSlots(); i++) { + TrashStackHandler slot = new TrashStackHandler(trashInventory, i, 40 + i * 20, 4); + slot.setMenu(this); + addSlot(slot); + } + addPlayerSlots(48, 84); + } + + @Override + public void removed(Player playerIn) { + if (!playerIn.level().isClientSide && playerIn.level() instanceof ServerLevel serverLevel && !pickedUpByRobo) { + ItemStack carried = getCarried(); + if (!carried.isEmpty()) { + for (int i = 0; i < trashInventory.getSlots(); i++) { + if (trashInventory.getStackInSlot(i).isEmpty()) { + trashInventory.setStackInSlot(i, carried.copy()); + setCarried(ItemStack.EMPTY); + break; + } + } + } + saveDataImmediately(serverLevel); + } + super.removed(playerIn); + } + + @Override + protected void saveData(PortableStockTicker contentHolder) { + } + + private void saveDataImmediately(ServerLevel serverLevel) { + if (pickedUpByRobo) return; + UUID networkId = getNetworkId(); + if (networkId != null) { + RoboManager.get(serverLevel).setTrashSlots(serverLevel, networkId, player.getUUID(), toTrashStacks()); + } + } + + static class TrashStackHandler extends SlotItemHandler { + private TrashMenu menu; + + public TrashStackHandler(IItemHandler itemHandler, int index, int xPosition, int yPosition) { + super(itemHandler, index, xPosition, yPosition); + } + + public void setMenu(TrashMenu menu) { + this.menu = menu; + } + + @Override + public boolean mayPlace(ItemStack stack) { + return !(stack.getItem() instanceof PortableStockTicker); + } + + @Override + public void setChanged() { + super.setChanged(); + if (menu != null) { + var level = menu.player.level(); + if (!level.isClientSide && level instanceof ServerLevel serverLevel) { + if (!menu.clearingForPickup) { + menu.pickedUpByRobo = false; + } + menu.saveDataImmediately(serverLevel); + } + } + } + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashScreen.java b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashScreen.java new file mode 100644 index 00000000..d5de7a96 --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/items/portable_stock_ticker/trash_menu/TrashScreen.java @@ -0,0 +1,139 @@ +package de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.logistics.AddressEditBox; +import com.simibubi.create.content.trains.station.NoShadowFontWrapper; +import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.gui.menu.AbstractSimiContainerScreen; +import de.theidler.create_mobile_packages.index.CMPGuiTextures; +import net.createmod.catnip.platform.CatnipServices; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.entity.player.Inventory; +import org.jetbrains.annotations.NotNull; + +public class TrashScreen extends AbstractSimiContainerScreen { + + private AddressEditBox addressBox; + private String lastSyncedAddress = ""; + + public TrashScreen(TrashMenu container, Inventory inv, Component title) { + super(container, inv, title); + } + + @Override + protected void init() { + int bgWidth = CMPGuiTextures.TRASH_MENU.getWidth(); + int bgHeight = CMPGuiTextures.TRASH_MENU.getHeight(); + setWindowSize(bgWidth, bgHeight + AllGuiTextures.PLAYER_INVENTORY.getHeight()); + super.init(); + clearWidgets(); + int x = getGuiLeft(); + int y = getGuiTop(); + + String previousAddress = addressBox == null ? menu.getTargetAddress() : addressBox.getValue(); + lastSyncedAddress = previousAddress; + addressBox = new AddressEditBox(this, new NoShadowFontWrapper(font), x + 38, y + 39, 160, 10, + true); + addressBox.setValue(previousAddress); + addressBox.setTextColor(0x555555); + addRenderableWidget(addressBox); + } + + protected void containerTick() { + super.containerTick(); + addressBox.tick(); + // Check if address has changed and sync to server (client initiates, server handles) + String currentAddress = addressBox.getValue(); + if (!currentAddress.equals(lastSyncedAddress)) { + lastSyncedAddress = currentAddress; + // Send to server - server-side will handle RoboManager update + CatnipServices.NETWORK.sendToServer(new SyncTrashAddressPacket(currentAddress)); + } + } + + @Override + public void onClose() { + // Save the address one final time when closing (send to server) + String currentAddress = addressBox.getValue(); + if (!currentAddress.equals(lastSyncedAddress)) { + CatnipServices.NETWORK.sendToServer(new SyncTrashAddressPacket(currentAddress)); + } + super.onClose(); + } + + @Override + public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { + // Handle addressBox focus and clicks + if (addressBox.isFocused()) { + // When focused, let addressBox handle clicks in its area (including suggestions dropdown) + if (addressBox.mouseClicked(pMouseX, pMouseY, pButton)) + return true; + // Clicked outside - unfocus + addressBox.setFocused(false); + } + return super.mouseClicked(pMouseX, pMouseY, pButton); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (addressBox.mouseScrolled(mouseX, mouseY, scrollX, scrollY)) + return true; + return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); + } + + @Override + public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { + if (addressBox.isFocused() && addressBox.keyPressed(pKeyCode, pScanCode, pModifiers)) + return true; + return addressBox.isFocused() && pKeyCode != 256 || super.keyPressed(pKeyCode, pScanCode, pModifiers); + } + + @Override + public boolean charTyped(char pCodePoint, int pModifiers) { + if (addressBox.isFocused() && addressBox.charTyped(pCodePoint, pModifiers)) + return true; + return super.charTyped(pCodePoint, pModifiers); + } + + @Override + protected boolean isHovering(int x, int y, int width, int height, double mouseX, double mouseY) { + // Prevent slot hover highlighting when addressBox suggestions dropdown is showing + if (addressBox.isFocused()) + return false; + return super.isHovering(x, y, width, height, mouseX, mouseY); + } + + @Override + protected void renderBg(@NotNull GuiGraphics guiGraphics, float v, int i, int i1) { + if (minecraft != null && this != minecraft.screen) + return; // stencil buffer does not cooperate with ponders gui fade out + + PoseStack ms = guiGraphics.pose(); + + ms.pushPose(); + + int x = getGuiLeft(); + int y = getGuiTop(); + + CMPGuiTextures.TRASH_MENU.render(guiGraphics, x, y - 20); + renderPlayerInventory(guiGraphics, x + 40, y + 66); + + MutableComponent headerTitle = Component.translatable( + "item.create_mobile_packages.portable_stock_ticker.trash_menu"); + guiGraphics.drawString(font, headerTitle, x + 256 / 2 - font.width(headerTitle) / 2, y - 16, 0x714A40, + false); + + ms.popPose(); + } + + public void applyTargetAddress(String address) { + String resolved = address != null ? address : ""; + lastSyncedAddress = resolved; + if (addressBox != null) { + addressBox.setValue(resolved); + } + menu.setTargetAddress(resolved); + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/items/robo_bee/RoboBeeItem.java b/src/main/java/de/theidler/create_mobile_packages/items/robo_bee/RoboBeeItem.java index 1c66d360..d4c04a28 100644 --- a/src/main/java/de/theidler/create_mobile_packages/items/robo_bee/RoboBeeItem.java +++ b/src/main/java/de/theidler/create_mobile_packages/items/robo_bee/RoboBeeItem.java @@ -48,7 +48,7 @@ public RoboBeeItem(Properties pProperties) { if (level instanceof ServerLevel serverLevel) { UUID networkId = networkFromStack(context.getItemInHand()); UUID finalNetworkId = networkId != null ? networkId : UUID.randomUUID(); - RoboManager.get(serverLevel).newRobo(serverLevel, packageItem, pos, finalNetworkId, 1, null); + RoboManager.get(serverLevel).newRobo(serverLevel, packageItem, pos, finalNetworkId, 1, null, false); } context.getItemInHand().shrink(1); return InteractionResult.SUCCESS; diff --git a/src/main/java/de/theidler/create_mobile_packages/network_settings/AddPlayerToNetworkPackage.java b/src/main/java/de/theidler/create_mobile_packages/network_settings/AddPlayerToNetworkPackage.java index 2809fdb4..0309277b 100644 --- a/src/main/java/de/theidler/create_mobile_packages/network_settings/AddPlayerToNetworkPackage.java +++ b/src/main/java/de/theidler/create_mobile_packages/network_settings/AddPlayerToNetworkPackage.java @@ -49,7 +49,6 @@ public void handle(ServerPlayer player) { } extendedNetwork.create_mobile_packages$addPlayer(playerId); - CreateMobilePackages.LOGGER.debug("Added player {} to network", playerId); // Mark as dirty to persist Create.LOGISTICS.markDirty(); @@ -70,5 +69,3 @@ public PacketTypeProvider getTypeProvider() { return CMPPackets.ADD_PLAYER_TO_NETWORK; } } - - diff --git a/src/main/java/de/theidler/create_mobile_packages/network_settings/NetworkHelper.java b/src/main/java/de/theidler/create_mobile_packages/network_settings/NetworkHelper.java index e005e8ad..bc9edd7c 100644 --- a/src/main/java/de/theidler/create_mobile_packages/network_settings/NetworkHelper.java +++ b/src/main/java/de/theidler/create_mobile_packages/network_settings/NetworkHelper.java @@ -27,9 +27,6 @@ public class NetworkHelper { public static @Nullable IExtendedLogisticsNetwork getExtendedLogisticsNetwork(@Nullable LogisticsNetwork logisticsNetwork) { if (logisticsNetwork == null) return null; - // Debugging to console - CreateMobilePackages.LOGGER.debug("Checking network: " + logisticsNetwork.getClass().getName()); - if (logisticsNetwork instanceof IExtendedLogisticsNetwork extendedLogisticsNetwork) { return extendedLogisticsNetwork; } diff --git a/src/main/java/de/theidler/create_mobile_packages/network_settings/RequestNetworkDataPacket.java b/src/main/java/de/theidler/create_mobile_packages/network_settings/RequestNetworkDataPacket.java index 15730430..4c0b6f6a 100644 --- a/src/main/java/de/theidler/create_mobile_packages/network_settings/RequestNetworkDataPacket.java +++ b/src/main/java/de/theidler/create_mobile_packages/network_settings/RequestNetworkDataPacket.java @@ -48,8 +48,6 @@ public void handle(ServerPlayer player) { return; } - CreateMobilePackages.LOGGER.debug("RequestNetworkDataPacket: Sending network data for {} to player {}", networkId, player.getName().getString()); - // Extract data directly from network NBT to avoid mixin issues String name = "Logistics Network " + networkId.toString().substring(0, 4); List players = new ArrayList<>(); @@ -60,7 +58,6 @@ public void handle(ServerPlayer player) { if (extendedNetwork != null) { name = extendedNetwork.create_mobile_packages$getName(); players = new ArrayList<>(extendedNetwork.create_mobile_packages$getPlayers()); - CreateMobilePackages.LOGGER.debug("RequestNetworkDataPacket: Got data from extended network - name: '{}', {} players", name, players.size()); } else { // Fallback: try to read from NBT CreateMobilePackages.LOGGER.debug("RequestNetworkDataPacket: extendedNetwork is null, trying NBT fallback"); diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/BeePortBlockEntityTarget.java b/src/main/java/de/theidler/create_mobile_packages/robo/BeePortBlockEntityTarget.java index ff57c6c8..b2f14fc7 100644 --- a/src/main/java/de/theidler/create_mobile_packages/robo/BeePortBlockEntityTarget.java +++ b/src/main/java/de/theidler/create_mobile_packages/robo/BeePortBlockEntityTarget.java @@ -29,9 +29,11 @@ public BeePortBlockEntity asBeePortBlockEntity() { } @Override - public boolean isValid() { + public boolean isValid(VirtualRobo robo) { BeePortBlockEntity be = asBeePortBlockEntity(); - return be != null && !be.isRemoved() && !be.isFull(); + boolean doesBeePortExists = be != null && !be.isRemoved(); + boolean hasItemStack = !robo.getItemStack().isEmpty(); + return doesBeePortExists && (hasItemStack ? be.hasSpaceForPackageAndRobo() : be.hasSpaceForRobo()); } @Override diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/PlayerTarget.java b/src/main/java/de/theidler/create_mobile_packages/robo/PlayerTarget.java index 11bb1574..73f75a16 100644 --- a/src/main/java/de/theidler/create_mobile_packages/robo/PlayerTarget.java +++ b/src/main/java/de/theidler/create_mobile_packages/robo/PlayerTarget.java @@ -2,10 +2,13 @@ import com.simibubi.create.content.logistics.box.PackageItem; import de.theidler.create_mobile_packages.IExtendedLogisticsNetwork; +import de.theidler.create_mobile_packages.blocks.bee_port.RoboRequest; import de.theidler.create_mobile_packages.index.CMPItems; import de.theidler.create_mobile_packages.network_settings.NetworkHelper; import de.theidler.create_mobile_packages.toast.ShowToastOnClientPacket; +import de.theidler.create_mobile_packages.toast.Toast; import de.theidler.create_mobile_packages.toast.types.PackageToast; +import de.theidler.create_mobile_packages.toast.types.SimpleToast; import net.createmod.catnip.platform.CatnipServices; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; @@ -53,7 +56,7 @@ public Player asPlayer() { } @Override - public boolean isValid() { + public boolean isValid(VirtualRobo robo) { return player != null && player.isAlive() && network != null && network.create_mobile_packages$getPlayers().contains(player.getUUID()); } @@ -67,14 +70,26 @@ public void updateEtaToast(VirtualRobo robo) { items.add(itemHandler.getStackInSlot(i)); } - PackageToast toast = new PackageToast( - robo.getId(), - Component.translatable("create_mobile_packages.toast.robo_bee_on_the_way"), - Component.translatable("create_mobile_packages.toast.eta", getETA()), - CMPItems.ROBO_BEE.asStack(), - items - ); - if (player instanceof ServerPlayer serverPlayer) + Toast toast = null; + + RoboRequest.Mission missionType = robo.getRequest() != null ? robo.getRequest().getMission() : RoboRequest.Mission.DELIVER; + switch (missionType) { + case DELIVER -> toast = new PackageToast( + robo.getId(), + Component.translatable("create_mobile_packages.toast.robo_bee_on_the_way"), + Component.translatable("create_mobile_packages.toast.eta", getETA()), + CMPItems.ROBO_BEE.asStack(), + items + ); + case PICKUP -> toast = new SimpleToast( + robo.getId(), + Component.translatable("create_mobile_packages.toast.robo_bee_on_the_way"), + Component.translatable("create_mobile_packages.toast.eta", getETA()), + CMPItems.ROBO_BEE.asStack() + ); + } + + if (player instanceof ServerPlayer serverPlayer && toast != null) CatnipServices.NETWORK.sendToClient(serverPlayer, new ShowToastOnClientPacket(toast)); lastToastUpdate = System.currentTimeMillis(); } diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/RoboManager.java b/src/main/java/de/theidler/create_mobile_packages/robo/RoboManager.java index 21ca5e9f..122b3b3d 100644 --- a/src/main/java/de/theidler/create_mobile_packages/robo/RoboManager.java +++ b/src/main/java/de/theidler/create_mobile_packages/robo/RoboManager.java @@ -3,12 +3,16 @@ import de.theidler.create_mobile_packages.blocks.bee_port.BeePortBlockEntity; import de.theidler.create_mobile_packages.blocks.bee_port.DronePortTracker; import de.theidler.create_mobile_packages.blocks.bee_port.RoboRequest; +import de.theidler.create_mobile_packages.items.portable_stock_ticker.trash_menu.SyncTrashItemsToClientPacket; +import net.createmod.catnip.platform.CatnipServices; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.saveddata.SavedData; @@ -23,21 +27,12 @@ public class RoboManager extends SavedData { public Map robos; public List beePortRoboRequests; + public List roboTrashStores; public RoboManager() { init(); } - @Override - public @NotNull CompoundTag save(@NotNull CompoundTag tag, HolderLookup.@NotNull Provider provider) { - ListTag robosList = new ListTag(); - for (VirtualRobo robo : robos.values()) { - robosList.add(robo.serializeNBT()); - } - tag.put("robos", robosList); - return tag; - } - public static RoboManager load(CompoundTag tag, ServerLevel level) { RoboManager manager = new RoboManager(); @@ -48,9 +43,34 @@ public static RoboManager load(CompoundTag tag, ServerLevel level) { VirtualRobo robo = VirtualRobo.deserializeNBT(level, roboTag); manager.robos.put(robo.getId(), robo); } + + // Load Trash Stores + ListTag trashSlotsTag = tag.getList("trashSlots", Tag.TAG_COMPOUND); + for (int i = 0; i < trashSlotsTag.size(); i++) { + CompoundTag trashStoreTag = trashSlotsTag.getCompound(i); + RoboTrashStore roboTrashStore = RoboTrashStore.load(trashStoreTag); + manager.roboTrashStores.add(roboTrashStore); + } return manager; } + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag tag, HolderLookup.@NotNull Provider provider) { + ListTag robosList = new ListTag(); + for (VirtualRobo robo : robos.values()) { + robosList.add(robo.serializeNBT()); + } + tag.put("robos", robosList); + + // Save Trash Stores + ListTag trashSlotsTag = new ListTag(); + for (RoboTrashStore roboTrashStore : roboTrashStores) { + trashSlotsTag.add(roboTrashStore.save()); + } + tag.put("trashSlots", trashSlotsTag); + return tag; + } + public static RoboManager get(ServerLevel level) { return level.getDataStorage().computeIfAbsent( new SavedData.Factory<>(RoboManager::new, (tag, provider) -> RoboManager.load(tag, level)), @@ -72,12 +92,108 @@ public void add(VirtualRobo robo) { this.setDirty(); } + public @Nullable RoboTrashStore getTrashStore(@NotNull UUID networkId, @NotNull UUID playerId) { + return roboTrashStores.stream() + .filter((store) -> store.getNetworkUUID().equals(networkId)) + .filter((store) -> store.getPlayerUUID().equals(playerId)) + .findFirst().orElse(null); + } + + /** + * Atomically reads and clears the trash items for the given player+network combination. + * Returns a snapshot of the items that were in the store (copies), and empties the store. + * Returns null if no store exists or the store has no items. + * This prevents duplication when items are modified concurrently while a robo picks them up. + */ + public synchronized @Nullable List takeTrashItems(@NotNull UUID networkId, @NotNull UUID playerId) { + RoboTrashStore store = getTrashStore(networkId, playerId); + if (store == null || !store.hasItems()) return null; + List snapshot = new ArrayList<>(); + for (ItemStack stack : store.getItemStacks()) { + snapshot.add(stack.copy()); + } + store.getItemStacks().clear(); + this.setDirty(); + return snapshot; + } + + public synchronized void setTrashSlots(UUID networkId, UUID playerId, List trashSlots) { + RoboTrashStore existingStore = getTrashStore(networkId, playerId); + if (existingStore != null) { + List storeItems = existingStore.getItemStacks(); + storeItems.clear(); + // Create copies of the ItemStacks to ensure proper data transfer + for (ItemStack stack : trashSlots) { + storeItems.add(stack.copy()); + } + } else { + // Create a new mutable list with copies of the stacks + List copiedStacks = new ArrayList<>(); + for (ItemStack stack : trashSlots) { + copiedStacks.add(stack.copy()); + } + roboTrashStores.add(new RoboTrashStore(playerId, networkId, copiedStacks)); + } + this.setDirty(); + } + + public synchronized void setTrashSlots(ServerLevel level, UUID networkId, UUID playerId, List trashSlots) { + setTrashSlots(networkId, playerId, trashSlots); + + // Send sync packet to the player + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerId); + if (player != null) { + CatnipServices.NETWORK.sendToClient(player, new SyncTrashItemsToClientPacket(new ArrayList<>(trashSlots))); + } + } + + public synchronized void setTrashTargetAddress(UUID networkId, UUID playerId, String targetAddress) { + RoboTrashStore existingStore = getTrashStore(networkId, playerId); + if (existingStore != null) { + existingStore.setTargetAddress(targetAddress != null ? targetAddress : ""); + } else { + List emptyStacks = new ArrayList<>(); + RoboTrashStore store = new RoboTrashStore(playerId, networkId, emptyStacks); + store.setTargetAddress(targetAddress != null ? targetAddress : ""); + roboTrashStores.add(store); + } + this.setDirty(); + } + public void tick(ServerLevel level) { robos.values().forEach(robo -> robo.tick(level)); getPendingRoboRequests().forEach(roboRequest -> tryHandlingRequest(roboRequest, level)); // prune finished requests older than a minute to avoid unbounded growth long now = System.currentTimeMillis(); beePortRoboRequests.removeIf(r -> (r.getStatus() == RoboRequest.Status.DONE || r.getStatus() == RoboRequest.Status.CANCELLED) && (now - r.getCreatedAt()) > 60_000); + + // handle trash stores + synchronized (this) { + roboTrashStores.forEach(store -> { + if (!store.hasItems()) return; + Player player = level.getPlayerByUUID(store.getPlayerUUID()); + if (player == null) return; + + UUID networkId = store.getNetworkUUID(); + UUID playerId = store.getPlayerUUID(); + + // Check if there is already an active request for this player and network + // This will also clean up dead robos by marking their requests as CANCELLED + if (hasActiveTrashRequest(playerId, networkId)) { + return; + } + + // At this point, there's no active request, so we can create a new one + // (The previous request either completed, was canceled, or had a dead robo) + RoboRequest request = new RoboRequest(new PlayerTarget( + player, + networkId + ), networkId, RoboRequest.Mission.PICKUP); + + requestRobo(request); + }); + } + this.setDirty(); } @@ -85,16 +201,52 @@ private void tryHandlingRequest(RoboRequest request, ServerLevel level) { DronePortTracker tracker = DronePortTracker.get(level); List allBEs = new ArrayList<>(tracker.getAllByNetwork(request.getLogisticsNetworkId())); allBEs.removeIf(BlockEntity::isRemoved); - allBEs.removeIf(be -> be.getBlockPos().equals(request.getTargetPos())); + allBEs.removeIf(be -> be.getBlockPos().equals(BlockPos.containing(request.getTargetPos()))); allBEs.removeIf(be -> be.getRoboBeeInventory().getStackInSlot(0).getCount() <= 0); - allBEs.stream().min(Comparator.comparingDouble(a -> a.getBlockPos().distSqr(request.getTargetPos()))).ifPresent(target -> target.handleRequest(request)); + allBEs.stream().min(Comparator.comparingDouble(a -> a.getBlockPos().getCenter().distanceToSqr(request.getTargetPos()))).ifPresent(target -> target.handleRequest(request)); + } + + /** + * Checks if there is already an active request (PENDING or IN_PROGRESS) for the given player and network. + * This ensures only one bee is sent per player+network combination at a time. + * If a request is IN_PROGRESS but the robo is dead, it will be canceled and return false. + */ + private boolean hasActiveTrashRequest(UUID playerId, UUID networkId) { + for (RoboRequest request : beePortRoboRequests) { + if (!request.getLogisticsNetworkId().equals(networkId)) continue; + if (!(request.getTarget() instanceof PlayerTarget target)) continue; + + if (target.asPlayer() == null || !target.asPlayer().getUUID().equals(playerId)) continue; + + // Check if this is an active request + if (request.getStatus() == RoboRequest.Status.PENDING) { + if (request.getMission() == RoboRequest.Mission.PICKUP) { + return true; + } + } + + if (request.getStatus() == RoboRequest.Status.IN_PROGRESS) { + // Check if the robo actually exists for this request + boolean roboExists = robos.values().stream() + .anyMatch(robo -> robo.getRequest() == request); + + if (roboExists) { + return true; + } else { + // Robo is dead but request is still IN_PROGRESS - mark as canceled + request.setStatus(RoboRequest.Status.CANCELLED); + } + } + } + return false; } - public UUID newRobo(ServerLevel level, ItemStack itemStack, BlockPos spawnPos, UUID logisticsNetworkId, float packageHeightScale, @Nullable BlockPos homePort) { + public UUID newRobo(ServerLevel level, ItemStack itemStack, BlockPos spawnPos, UUID logisticsNetworkId, float packageHeightScale, @Nullable BlockPos homePort, boolean returnToHomeAfterDelivery) { UUID id = UUID.randomUUID(); VirtualRobo robo = new VirtualRobo(level, id, itemStack, spawnPos, logisticsNetworkId); robo.setPackageHeightScale(packageHeightScale); robo.setHomePortPos(homePort); + robo.setReturnToHomeAfterDelivery(returnToHomeAfterDelivery); this.add(robo); setDirty(); return id; @@ -108,8 +260,13 @@ public void newRequestRobo(ServerLevel level, BlockPos spawnPos, RoboRequest req setDirty(); } - public synchronized void requestRobo(BlockPos pos, UUID logisticsNetworkId) { - beePortRoboRequests.add(new RoboRequest(pos, logisticsNetworkId)); + public void requestRobo(RoboTarget roboTarget, UUID logisticsNetworkId, RoboRequest.Mission mission) { + requestRobo(new RoboRequest(roboTarget, logisticsNetworkId, mission)); + } + + public synchronized void requestRobo(RoboRequest request) { + beePortRoboRequests.add(request); + setDirty(); } public List getRoboRequestsWithStatus(RoboRequest.Status status) { @@ -121,7 +278,7 @@ public List getPendingRoboRequests() { } public List getRoboRequests(BlockPos pos) { - return beePortRoboRequests.stream().filter(request -> request.getTargetPos().equals(pos)).toList(); + return beePortRoboRequests.stream().filter(request -> BlockPos.containing(request.getTargetPos()).equals(pos)).toList(); } public List getInboundRobo(BlockPos pos) { @@ -150,5 +307,7 @@ public List getETAs(BlockPos pos) { private void init() { this.robos = new ConcurrentHashMap<>(); this.beePortRoboRequests = new CopyOnWriteArrayList<>(); + this.roboTrashStores = new CopyOnWriteArrayList<>(); } -} \ No newline at end of file +} + diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/RoboTarget.java b/src/main/java/de/theidler/create_mobile_packages/robo/RoboTarget.java index 8cf1471b..0e289088 100644 --- a/src/main/java/de/theidler/create_mobile_packages/robo/RoboTarget.java +++ b/src/main/java/de/theidler/create_mobile_packages/robo/RoboTarget.java @@ -20,7 +20,7 @@ default BlockPos asBlockPos() { return null; } - default boolean isValid() { + default boolean isValid(VirtualRobo robo) { return true; } diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/RoboTrashStore.java b/src/main/java/de/theidler/create_mobile_packages/robo/RoboTrashStore.java new file mode 100644 index 00000000..39ce7a8f --- /dev/null +++ b/src/main/java/de/theidler/create_mobile_packages/robo/RoboTrashStore.java @@ -0,0 +1,76 @@ +package de.theidler.create_mobile_packages.robo; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class RoboTrashStore { + private final UUID playerUUID; + private final UUID networkUUID; + private final List itemStacks; + private String targetAddress = ""; + + public RoboTrashStore(UUID playerUUID, UUID networkUUID, List itemStacks) { + this.playerUUID = playerUUID; + this.networkUUID = networkUUID; + this.itemStacks = itemStacks; + } + + public static RoboTrashStore load(CompoundTag tag) { + UUID playerUUID = tag.getUUID("PlayerUUID"); + UUID networkUUID = tag.getUUID("NetworkUUID"); + ListTag itemsTag = tag.getList("ItemStacks", Tag.TAG_COMPOUND); + List itemStacks = new ArrayList<>(itemsTag.stream() + .map(itemTag -> ItemStack.CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, itemTag).result().orElse(ItemStack.EMPTY)) + .toList()); + + RoboTrashStore store = new RoboTrashStore(playerUUID, networkUUID, itemStacks); + if (tag.contains("TargetAddress")) { + store.setTargetAddress(tag.getString("TargetAddress")); + } + return store; + } + + public String getTargetAddress() { + return targetAddress; + } + + public void setTargetAddress(String address) { + this.targetAddress = address; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + public UUID getNetworkUUID() { + return networkUUID; + } + + public List getItemStacks() { + return itemStacks; + } + + /** + * Returns true if at least one slot contains a non-empty ItemStack. + */ + public boolean hasItems() { + return itemStacks.stream().anyMatch(stack -> !stack.isEmpty()); + } + + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + tag.putUUID("PlayerUUID", playerUUID); + tag.putUUID("NetworkUUID", networkUUID); + ListTag itemsTag = new ListTag(); + itemStacks.forEach(itemStack -> itemsTag.add(ItemStack.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, itemStack).result().orElse(new CompoundTag()))); + tag.put("ItemStacks", itemsTag); + tag.putString("TargetAddress", targetAddress); + return tag; + } +} diff --git a/src/main/java/de/theidler/create_mobile_packages/robo/VirtualRobo.java b/src/main/java/de/theidler/create_mobile_packages/robo/VirtualRobo.java index 75677cb8..b4295762 100644 --- a/src/main/java/de/theidler/create_mobile_packages/robo/VirtualRobo.java +++ b/src/main/java/de/theidler/create_mobile_packages/robo/VirtualRobo.java @@ -39,6 +39,7 @@ public class VirtualRobo { private float packageHeightScale; private RoboRequest request = null; private @Nullable BlockPos homePortPos; + private boolean returnToHomeAfterDelivery; public VirtualRobo(ServerLevel level, UUID id, ItemStack itemStack, BlockPos spawnPos, UUID logisticsNetworkId) { this.id = id; @@ -64,6 +65,10 @@ public static VirtualRobo deserializeNBT(ServerLevel level, CompoundTag roboTag) VirtualRobo virtualRobo = new VirtualRobo(level, id, itemStack, BlockPos.containing(pos), logisticsNetworkId); virtualRobo.setSpeed(speed); + if (roboTag.contains("homePortPos")) { + virtualRobo.setHomePortPos(BlockPos.of(roboTag.getLong("homePortPos"))); + } + virtualRobo.setReturnToHomeAfterDelivery(roboTag.getBoolean("returnToHomeAfterDelivery")); if (!virtualRobo.getItemStack().isEmpty()) { virtualRobo.setPackageHeightScale(1.0f); } @@ -103,11 +108,22 @@ private void setTargetFromItemStack(ItemStack itemStack) { private void updateTarget() { // if the target is still valid and in the correct network, do nothing - if (target != null && target.isValid()) return; + if (target != null && target.isValid(this)) return; + + // Return-mode should prefer the original home port after successful delivery. + if (shouldReturnToHomePort()) { + BeePortBlockEntity homePort = CMPHelper.getPortAtPos(serverLevel, homePortPos); + if (homePort != null) { + target = new BeePortBlockEntityTarget(homePort); + if (target.isValid(this)) { + return; + } + } + } // try finding a Player first target = PlayerTarget.fromAddress(serverLevel, targetAddress, logisticsNetworkId); - if (target != null && target.isValid()) { + if (target != null && target.isValid(this)) { return; } @@ -116,7 +132,7 @@ private void updateTarget() { if (targetBlockEntity != null) { target = new BeePortBlockEntityTarget(targetBlockEntity); } - if (target != null && target.isValid()) { + if (target != null && target.isValid(this)) { return; } @@ -125,7 +141,7 @@ private void updateTarget() { if (homePort != null) { target = new BeePortBlockEntityTarget(homePort); } - if (target != null && target.isValid()) { + if (target != null && target.isValid(this)) { return; } @@ -222,6 +238,10 @@ public CompoundTag serializeNBT() { if (!getItemStack().isEmpty()) { tag.put("itemStack", getItemStack().save(serverLevel.registryAccess(), new CompoundTag())); } + if (homePortPos != null) { + tag.putLong("homePortPos", homePortPos.asLong()); + } + tag.putBoolean("returnToHomeAfterDelivery", returnToHomeAfterDelivery); return tag; } @@ -320,10 +340,6 @@ public void setPackageHeightScale(float scale) { this.packageHeightScale = scale; } - public void setTarget(@Nullable RoboTarget target) { - this.target = target; - } - public void setYaw(float yaw) { this.yaw = yaw; } @@ -344,7 +360,15 @@ public RoboRequest getRequest() { public void setRequest(RoboRequest request) { this.request = request; this.request.setStatus(RoboRequest.Status.IN_PROGRESS); - this.target = new BeePortBlockEntityTarget((BeePortBlockEntity) serverLevel.getBlockEntity(request.getTargetPos())); + this.target = request.getTarget(); + } + + public void clearRequest() { + if (this.request != null) { + this.request.setStatus(RoboRequest.Status.DONE); + this.request = null; + } + invalidateTarget(); } public UUID getLogisticsNetworkId() { @@ -355,4 +379,12 @@ public void setHomePortPos(@Nullable BlockPos homePort) { if (homePort == null) return; this.homePortPos = homePort; } + + public boolean shouldReturnToHomePort() { + return returnToHomeAfterDelivery && request == null && (itemStack == null || itemStack.isEmpty()); + } + + public void setReturnToHomeAfterDelivery(boolean returnToHomeAfterDelivery) { + this.returnToHomeAfterDelivery = returnToHomeAfterDelivery; + } } diff --git a/src/main/resources/assets/create_mobile_packages/lang/de_de.json b/src/main/resources/assets/create_mobile_packages/lang/de_de.json index 7af1a7aa..4f90a6c2 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/de_de.json +++ b/src/main/resources/assets/create_mobile_packages/lang/de_de.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Robo-Biene auf dem Weg!", "create_mobile_packages.toast.eta": "%1$s Sekunden verbleibend", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Öffne den Mobilen Lagerticker", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Robo-Bienen Port", "create_mobile_packages.ponder.robo_bee_port.text_1": "Der Robo-Bienen Port kann Pakete an Spieler senden.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Platziere ein Paket mit einer Adresse, die dem Namen des Spielers entspricht in den Robo-Bienen Port.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Platziere, das Item, um eine Robo-Biene zu erzeugen. Wird gebraucht in Robo-Bienen Ports um Pakete zu versenden.", "tooltip.create_mobile_packages.bee_port.jade.network": "Netzwerk: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "Du bist Teil dieses Netzwerkes", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Bienen-Rückkehrmodus aktivieren", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Bienen-Rückkehrmodus deaktivieren", "tooltip.create_mobile_packages.network.remove_player": "Spieler entfernen", "tooltip.create_mobile_packages.network.add_yourself": "Füge dich hinzu", "tooltip.create_mobile_packages.network.leave": "Verlasse das Netzwerk", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Öffnet ein Menü zum Verändern von Päckchen", "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", "create_mobile_packages.network.players": "Players in Network:", - "create_mobile_packages.network.owner": "Owner: %1$s" + "create_mobile_packages.network.owner": "Owner: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/en_us.json b/src/main/resources/assets/create_mobile_packages/lang/en_us.json index 862a721f..66a87786 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/en_us.json +++ b/src/main/resources/assets/create_mobile_packages/lang/en_us.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Robo Bee on the way!", "create_mobile_packages.toast.eta": "%1$s seconds remaining", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Open Portable Stock Ticker", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Robo Bee Port", "create_mobile_packages.ponder.robo_bee_port.text_1": "The Robo Bee Port can send a Package to a Player.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Place a package with an address matching the Player's name in the Robo Bee Port.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Item to spawn a Robo Bee. Needed in Robo Bee Ports to send packages.", "tooltip.create_mobile_packages.bee_port.jade.network": "Network: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "You are part of this network", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "Remove Player", "tooltip.create_mobile_packages.network.add_yourself": "Add Yourself", "tooltip.create_mobile_packages.network.leave": "Leave Network", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Opens a menu to modify Packages", "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", "create_mobile_packages.network.players": "Players in Network:", - "create_mobile_packages.network.owner": "Owner: %1$s" + "create_mobile_packages.network.owner": "Owner: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/fr_fr.json b/src/main/resources/assets/create_mobile_packages/lang/fr_fr.json index 7bd33ed1..1aa8f26e 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/fr_fr.json +++ b/src/main/resources/assets/create_mobile_packages/lang/fr_fr.json @@ -1,7 +1,7 @@ { "itemGroup.create_mobile_packages": "Create : Mobile Packages", - "block.create_mobile_packages.bee_port": "Port de Robeille", - "item.create_mobile_packages.bee_port": "Port de Robeille", + "block.create_mobile_packages.bee_port": "Port Robeille", + "item.create_mobile_packages.bee_port": "Port Robeille", "item.create_mobile_packages.portable_stock_ticker": "Téléscripteur de Stock Portable", "item.create_mobile_packages.portable_stock_ticker.screen_title": "Téléscripteur de Stock Portable", "item.create_mobile_packages.portable_stock_ticker.not_linked": "Non lié au réseau", @@ -22,51 +22,55 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Robeille est en chemin !", "create_mobile_packages.toast.eta": "%1$s secondes restantes", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Ouvrir le Téléscripteur de Stock Portable", - "create_mobile_packages.ponder.robo_bee_port.header": "Port de Robeille", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Ouvrir les paramètres réseau", + "create_mobile_packages.ponder.robo_bee_port.header": "Port Robeille", "create_mobile_packages.ponder.robo_bee_port.text_1": "Le Port de Robeille peut envoyer des colis à un joueur.", - "create_mobile_packages.ponder.robo_bee_port.text_2": "Placez un colis avec une adresse correspondant au nom du joueur dans le Port de Robeille.", - "create_mobile_packages.ponder.robo_bee_port.text_3": "Placez une Robeille dans l'emplacement à Robeille du Port de Robeille.", - "create_mobile_packages.ponder.robo_bee_port.text_4": "Le Port de Robeille enverra désormais la Robeille au joueur dont le nom correspond à l'adresse indiquée dans le colis.", + "create_mobile_packages.ponder.robo_bee_port.text_2": "Placez un colis avec une adresse correspondant au nom du joueur dans le Port Robeille.", + "create_mobile_packages.ponder.robo_bee_port.text_3": "Placez une Robeille dans l'emplacement à Robeille du Port Robeille.", + "create_mobile_packages.ponder.robo_bee_port.text_4": "Le Port Robeille enverra désormais la Robeille au joueur dont le nom correspond à l'adresse indiquée dans le colis.", "create_mobile_packages.ponder.pull_from_chest.header": "Prendre depuis un coffre", "create_mobile_packages.ponder.pull_from_chest.text_1": "Met un colis dans le coffre.", - "create_mobile_packages.ponder.pull_from_chest.text_2": "Le Port de Robeille extraira le colis de tout inventaire adjacent et le mettra dedans.", + "create_mobile_packages.ponder.pull_from_chest.text_2": "Le Port Robeille extraira le colis de tout inventaire adjacent et le mettra dedans.", "create_mobile_packages.ponder.simple_setup.header": "Installation simple", "create_mobile_packages.ponder.simple_setup.text_1": "Il s'agit d'une configuration de base pour la gestion automatisée des colis.", "create_mobile_packages.ponder.simple_setup.text_2": "Placez un coffre ou tout autre espace de rangement.", "create_mobile_packages.ponder.simple_setup.text_3": "Placez un Emballeur face au stockage pour créer des colis.", "create_mobile_packages.ponder.simple_setup.text_4": "Connectez une Liaison de Stock à l'Embaleur pour le connecter à un réseau logistique.", - "create_mobile_packages.ponder.simple_setup.text_5": "Enfin, placez un Port de Robeille pour envoyer des colis.", + "create_mobile_packages.ponder.simple_setup.text_5": "Enfin, placez un Port Robeille pour envoyer des colis.", "create_mobile_packages.ponder.push_to_chest.header": "Mettre dans un coffre", - "create_mobile_packages.ponder.push_to_chest.text_1": "Le Port de Robeille peut le pousser vers les inventaires adjacents lorsqu'il est alimenté par de la redstone.", + "create_mobile_packages.ponder.push_to_chest.text_1": "Le Port Robeille peut le pousser vers les inventaires adjacents lorsqu'il est alimenté par de la redstone.", "create_mobile_packages.ponder.push_to_chest.text_2": "Assurez-vous que le Port est alimenté par redstone.", "create_mobile_packages.ponder.push_to_chest.text_3": "Le colis sera poussé dans n'importe quel inventaire adjacent, comme un coffre.", "create_mobile_packages.ponder.crafter_setup.header": "Installation d'établis mécaniques", - "create_mobile_packages.ponder.crafter_setup.text_1": "Il s'agit d'une configuration pour la fabrication automatisée et avec le Port de Robeille.", + "create_mobile_packages.ponder.crafter_setup.text_1": "Il s'agit d'une configuration pour la fabrication automatisée et avec le Port Robeille.", "create_mobile_packages.ponder.crafter_setup.text_2": "Le Réemballeur divise le colis en plusieurs colis plus petits.", "create_mobile_packages.ponder.crafter_setup.text_3": "Les colis sont envoyés via une chute jusqu'à l'Emballeur.", "create_mobile_packages.ponder.crafter_setup.text_4": "L'Emballeur est connecté à un établi mécanique 3x3, qui fabrique les objets.", "create_mobile_packages.ponder.crafter_setup.text_5": "L'objet fabriquée est placée dans un coffre.", "create_mobile_packages.ponder.port_to_port.header": "Port à Port", - "create_mobile_packages.ponder.port_to_port.text_1": "Le Port de Robeille peut également envoyer des colis vers un autre Port.", + "create_mobile_packages.ponder.port_to_port.text_1": "Le Port Robeille peut également envoyer des colis vers un autre Port.", "create_mobile_packages.ponder.port_to_port.text_2": "Définissez une adresse dans le Port de destination.", "create_mobile_packages.ponder.port_to_port.text_3": "Définissez la même adresse que l'adresse du colis et placez le colis dans le premier Port.", "create_mobile_packages.ponder.port_to_port.text_4": "Le colis sera envoyé d'un Port à l'autre.", "create_mobile_packages.ponder.port_to_port.text_5": "Cette fonctionnalité peut être désactivée dans les fichiers de configuration.", "tooltip.create_mobile_packages.robo_bee.package_transport": "Lorsque vous tenez un colis dans la main secondaire, il sera envoyé avec la Robeille.", - "tooltip.create_mobile_packages.robo_bee.robo_bee": "Objet permettant de faire apparaître une Robeille. Nécessaire dans les Ports de Robeille pour envoyer des colis.", - "tooltip.create_mobile_packages.bee_port.jade.network": "Network: %1$s", - "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "You are part of this network", - "tooltip.create_mobile_packages.network.remove_player": "Remove Player", - "tooltip.create_mobile_packages.network.add_yourself": "Add Yourself", - "tooltip.create_mobile_packages.network.leave": "Leave Network", - "tooltip.create_mobile_packages.network.settings": "Network Settings", + "tooltip.create_mobile_packages.robo_bee.robo_bee": "Objet permettant de faire apparaître une Robeille. Nécessaire dans les Ports Robeille pour envoyer des colis.", + "tooltip.create_mobile_packages.bee_port.jade.network": "Réseau : %1$s", + "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "Vous faites partie de ce réseau", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", + "tooltip.create_mobile_packages.network.remove_player": "Retirer le Joueur", + "tooltip.create_mobile_packages.network.add_yourself": "S'ajouter", + "tooltip.create_mobile_packages.network.leave": "Quitter le réseau", + "tooltip.create_mobile_packages.network.settings": "Paramètre réseau", "item.create_mobile_packages.mobile_packager": "Emballeur portable", "item.create_mobile_packages.mobile_packager.tooltip.summary": "Emballer des objets à la volée", "item.create_mobile_packages.mobile_packager.tooltip.condition1": "Lorsqu'il est utilisé", "item.create_mobile_packages.mobile_packager.tooltip.behaviour1": "Ouvre un menu pour créer un colis", "item.create_mobile_packages.mobile_packager.tooltip.condition2": "Lorsqu'il est utilisé en étant accroupis", "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Ouvre un menu pour modifier un colis", - "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", - "create_mobile_packages.network.players": "Players in Network:", - "create_mobile_packages.network.owner": "Owner: %1$s" + "config.jade.plugin_create_mobile_packages.bee_port": "Information du Port Robeille", + "create_mobile_packages.network.players": "Joueur dans le réseau :", + "create_mobile_packages.network.owner": "Propriétaire : %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Menu Poubelle" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/ja_jp.json b/src/main/resources/assets/create_mobile_packages/lang/ja_jp.json index 7b1b54ed..6431e617 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/ja_jp.json +++ b/src/main/resources/assets/create_mobile_packages/lang/ja_jp.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "ロボビーが間もなく到着します!", "create_mobile_packages.toast.eta": "到着まで残り%1$s秒", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "所持しているポータブルストックティッカーの画面を開く", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "ビーポート", "create_mobile_packages.ponder.robo_bee_port.text_1": "ロボビーはプレイヤーに小包を送ることができます", "create_mobile_packages.ponder.robo_bee_port.text_2": "プレイヤーと同じ名前のアドレスが設定された小包を配置してください", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "使用するとロボビーを配置できるアイテム。ビーポートからの搬送に必要です。", "tooltip.create_mobile_packages.bee_port.jade.network": "ネットワーク: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "あなたはこのネットワークに属しています", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "プレイヤーを削除", "tooltip.create_mobile_packages.network.add_yourself": "自分を追加", "tooltip.create_mobile_packages.network.leave": "ネットワークから退出する", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "小包を編集するメニューを開きます", "config.jade.plugin_create_mobile_packages.bee_port": "ビーポートの情報", "create_mobile_packages.network.players": "ネットワーク内のプレイヤー:", - "create_mobile_packages.network.owner": "所有者: %1$s" + "create_mobile_packages.network.owner": "所有者: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/pl_pl.json b/src/main/resources/assets/create_mobile_packages/lang/pl_pl.json index 3ff867ef..fcad9b94 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/pl_pl.json +++ b/src/main/resources/assets/create_mobile_packages/lang/pl_pl.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Robo-Pszczół w drodze!", "create_mobile_packages.toast.eta": "Pozostało %1$s sekund", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Otwórz Przenośny Licznik Zapasów", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Port Robo-Pszczół", "create_mobile_packages.ponder.robo_bee_port.text_1": "Port Robo-Pszczół może wysłać Paczkę do gracza.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Umieść przesyłkę z adresem odpowiadającym imieniu Gracza w Porcie Robo-Pszcól.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Item to spawn a Robo Bee. Needed in Robo Bee Ports to send packages.", "tooltip.create_mobile_packages.bee_port.jade.network": "Network: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "You are part of this network", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "Remove Player", "tooltip.create_mobile_packages.network.add_yourself": "Add Yourself", "tooltip.create_mobile_packages.network.leave": "Leave Network", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Opens a menu to modify Packages", "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", "create_mobile_packages.network.players": "Players in Network:", - "create_mobile_packages.network.owner": "Owner: %1$s" + "create_mobile_packages.network.owner": "Owner: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/pt_br.json b/src/main/resources/assets/create_mobile_packages/lang/pt_br.json index 7c781625..44f67dd3 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/pt_br.json +++ b/src/main/resources/assets/create_mobile_packages/lang/pt_br.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Abelha Robô a caminho!", "create_mobile_packages.toast.eta": "%1$s segundos restantes", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Abrir o Telégrafo de Estoque Portátil", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Porto de Abelhas Robô", "create_mobile_packages.ponder.robo_bee_port.text_1": "Porto de Abelhas Robô pode enviar Pacotes ao Jogador.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Coloque um pacote com o endereço correspondente ao nome do jogador no Porto de Abelhas Robô.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Item para gerar uma Abelha Robô. Necessário nos Portos da Abelha Robô para enviar pacotes.", "tooltip.create_mobile_packages.bee_port.jade.network": "Network: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "You are part of this network", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "Remove Player", "tooltip.create_mobile_packages.network.add_yourself": "Add Yourself", "tooltip.create_mobile_packages.network.leave": "Leave Network", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Abre um menu para modificar Pacotes", "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", "create_mobile_packages.network.players": "Players in Network:", - "create_mobile_packages.network.owner": "Owner: %1$s" + "create_mobile_packages.network.owner": "Owner: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/ru_ru.json b/src/main/resources/assets/create_mobile_packages/lang/ru_ru.json index bf166fda..f4403e44 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/ru_ru.json +++ b/src/main/resources/assets/create_mobile_packages/lang/ru_ru.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Пчелодрон в пути", "create_mobile_packages.toast.eta": "Ожидайте %1$s сек", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Открыть складскую сеть", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Размышляем над пчелопортами", "create_mobile_packages.ponder.robo_bee_port.text_1": "Пчелопорт может быть использован чтобы отправить посылку прямо к игроку.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Поместите посылку, у которой адрес совпадает с ником игрока в печлопорт.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Предмет для создания Пчёлодрона. Необходим в Дронопорте для отправки посылок.", "tooltip.create_mobile_packages.bee_port.jade.network": "Сеть: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "Вы часть этой сети", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "Выгнать игрока", "tooltip.create_mobile_packages.network.add_yourself": "Добавить себя", "tooltip.create_mobile_packages.network.leave": "Выйти из сети", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Открывает меню изменения посылки", "config.jade.plugin_create_mobile_packages.bee_port": "Bee Port Info", "create_mobile_packages.network.players": "Игроки в сети:", - "create_mobile_packages.network.owner": "Владелец: %1$s" + "create_mobile_packages.network.owner": "Владелец: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/sv_se.json b/src/main/resources/assets/create_mobile_packages/lang/sv_se.json index 797affff..433f0ea6 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/sv_se.json +++ b/src/main/resources/assets/create_mobile_packages/lang/sv_se.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "Robot Bi på väg!", "create_mobile_packages.toast.eta": "%1$s sekunder kvar", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "Öppna Portabel Lagerindikator", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "Robot Bi Port", "create_mobile_packages.ponder.robo_bee_port.text_1": "Robot Bi Porten kan skicka Paket till Spelare.", "create_mobile_packages.ponder.robo_bee_port.text_2": "Placera ett paket med en adress som machar Spelarens namn i Robot Bi Porten.", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "Föremål att skapa ett Robot Bi. Används i Robot Bi Portar att skicka paket.", "tooltip.create_mobile_packages.bee_port.jade.network": "Nätverk: %1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "Du är en del av detta nätverk", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "Ta bort Spelare", "tooltip.create_mobile_packages.network.add_yourself": "Lägg till dig själv", "tooltip.create_mobile_packages.network.leave": "Lämna Nätverk", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "Öppnar en meny att ändra Paket", "config.jade.plugin_create_mobile_packages.bee_port": "Robot Bi Ports Information", "create_mobile_packages.network.players": "Spelare i Nätverk: ", - "create_mobile_packages.network.owner": "Ägare: %1$s" + "create_mobile_packages.network.owner": "Ägare: %1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/lang/zh_cn.json b/src/main/resources/assets/create_mobile_packages/lang/zh_cn.json index 3a676b28..2dc6ac6c 100644 --- a/src/main/resources/assets/create_mobile_packages/lang/zh_cn.json +++ b/src/main/resources/assets/create_mobile_packages/lang/zh_cn.json @@ -22,6 +22,7 @@ "create_mobile_packages.toast.robo_bee_on_the_way": "运输蜂正在运送货物!", "create_mobile_packages.toast.eta": "剩余时间:%1$s 秒", "create_mobile_packages.keyinfo.open_portable_stock_ticker": "打开便携式仓库管理器", + "create_mobile_packages.keyinfo.open_player_networks_screen": "Open Network Settings", "create_mobile_packages.ponder.robo_bee_port.header": "运输蜂停泊港", "create_mobile_packages.ponder.robo_bee_port.text_1": "运输蜂停泊港可将包裹送至玩家。", "create_mobile_packages.ponder.robo_bee_port.text_2": "将地址为玩家名的包裹放入运输蜂停泊港内。", @@ -56,6 +57,8 @@ "tooltip.create_mobile_packages.robo_bee.robo_bee": "运输蜂的物品形式。运输蜂停泊港发送包裹时需要使用。", "tooltip.create_mobile_packages.bee_port.jade.network": "网络:%1$s", "tooltip.create_mobile_packages.bee_port.jade.part_of_network": "你是此网络的成员之一", + "tooltip.create_mobile_packages.bee_port.enable_return_mode": "Enable Bee Return Mode", + "tooltip.create_mobile_packages.bee_port.disable_return_mode": "Disable Bee Return Mode", "tooltip.create_mobile_packages.network.remove_player": "移除玩家", "tooltip.create_mobile_packages.network.add_yourself": "添加你自己", "tooltip.create_mobile_packages.network.leave": "离开网络", @@ -68,5 +71,6 @@ "item.create_mobile_packages.mobile_packager.tooltip.behaviour2": "打开编辑包裹界面", "config.jade.plugin_create_mobile_packages.bee_port": "运输蜂停泊港信息", "create_mobile_packages.network.players": "网络中的玩家:", - "create_mobile_packages.network.owner": "所有者:%1$s" + "create_mobile_packages.network.owner": "所有者:%1$s", + "item.create_mobile_packages.portable_stock_ticker.trash_menu": "Trash Menu" } diff --git a/src/main/resources/assets/create_mobile_packages/textures/gui/icons.png b/src/main/resources/assets/create_mobile_packages/textures/gui/icons.png new file mode 100644 index 00000000..66e9d605 Binary files /dev/null and b/src/main/resources/assets/create_mobile_packages/textures/gui/icons.png differ diff --git a/src/main/resources/assets/create_mobile_packages/textures/gui/trash_menu.png b/src/main/resources/assets/create_mobile_packages/textures/gui/trash_menu.png new file mode 100644 index 00000000..5518dcea Binary files /dev/null and b/src/main/resources/assets/create_mobile_packages/textures/gui/trash_menu.png differ