diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChest.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChest.java new file mode 100644 index 000000000..64b48a293 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChest.java @@ -0,0 +1,44 @@ +package org.cyclops.integratedterminals.core.terminalstorage; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.cyclops.integrateddynamics.api.network.INetwork; +import org.cyclops.integratedterminals.Reference; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTab; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabClient; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabServer; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; + +import javax.annotation.Nullable; + +/** + * Terminal storage tab for Ender Chest. + * @author rubensworks + */ +public class TerminalStorageTabEnderChest implements ITerminalStorageTab { + + public static ResourceLocation NAME = new ResourceLocation(Reference.MOD_ID, "ender_chest"); + + @Override + public ResourceLocation getName() { + return NAME; + } + + @Override + public ITerminalStorageTabClient createClientTab(ContainerTerminalStorageBase container, Player player) { + return new TerminalStorageTabEnderChestClient(container, getName()); + } + + @Override + public ITerminalStorageTabServer createServerTab(ContainerTerminalStorageBase container, Player player, INetwork network) { + return new TerminalStorageTabEnderChestServer(getName(), (ServerPlayer) player); + } + + @Nullable + @Override + public ITerminalStorageTabCommon createCommonTab(ContainerTerminalStorageBase container, Player player) { + return new TerminalStorageTabEnderChestCommon(container, getName()); + } +} diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestClient.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestClient.java new file mode 100644 index 000000000..51ab2b29c --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestClient.java @@ -0,0 +1,246 @@ +package org.cyclops.integratedterminals.core.terminalstorage; + +import com.google.common.collect.Lists; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalButton; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalRowColumnProvider; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageSlot; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabClient; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * A client-side storage terminal tab for Ender Chest. + * @author rubensworks + */ +public class TerminalStorageTabEnderChestClient implements ITerminalStorageTabClient { + + private final ResourceLocation name; + private final ItemStack icon; + protected final ContainerTerminalStorageBase container; + + public TerminalStorageTabEnderChestClient(ContainerTerminalStorageBase container, ResourceLocation name) { + this.name = name; + this.icon = new ItemStack(Blocks.ENDER_CHEST); + this.container = container; + } + + @Override + public void onSelect(int channel) { + // No action needed on select + } + + @Override + public void onDeselect(int channel) { + // No action needed on deselect + } + + @Override + public ResourceLocation getName() { + return this.name; + } + + @Override + public ItemStack getIcon() { + return this.icon; + } + + @Override + public List getTooltip() { + return Lists.newArrayList(Component.translatable("gui.integratedterminals.terminal_storage.ender_chest")); + } + + @Override + public String getInstanceFilter(int channel) { + return ""; + } + + @Override + public void setInstanceFilter(int channel, String filter) { + // Ender Chest doesn't support filtering + } + + @Override + public List getSlots(int channel, int offset, int limit) { + // Ender Chest inventory is handled by regular container slots + return Collections.emptyList(); + } + + @Override + public ITerminalRowColumnProvider getRowColumnProvider() { + return () -> new ITerminalRowColumnProvider.RowsAndColumns(3, 9); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public int getSlotCount(int channel) { + return 27; // Ender Chest has 27 slots + } + + @Override + public String getStatus(int channel) { + return ""; + } + + @Override + public int[] getChannels() { + return new int[]{0}; // Single channel + } + + @Override + public void resetActiveSlot() { + // No active slot for Ender Chest + } + + @Override + public boolean handleClick(AbstractContainerMenu container, int channel, int hoveringStorageSlot, int mouseButton, + boolean hasClickedOutside, boolean hasClickedInStorage, int hoveredContainerSlot, + boolean isQuickMove) { + // Handle shift-clicking for Ender Chest slots + if (isQuickMove && hoveredContainerSlot >= 0 && container instanceof ContainerTerminalStorageBase) { + ContainerTerminalStorageBase terminalContainer = (ContainerTerminalStorageBase) container; + + // Get the Ender Chest slots for this tab + List> enderSlots = + terminalContainer.getTabSlots(getName().toString()); + + if (!enderSlots.isEmpty()) { + // Find the start and end indices of Ender Chest slots + int startIndex = Integer.MAX_VALUE; + int endIndex = Integer.MIN_VALUE; + for (Pair slotPair : enderSlots) { + int index = slotPair.getLeft().index; + startIndex = Math.min(startIndex, index); + endIndex = Math.max(endIndex, index); + } + + boolean isEnderSlot = hoveredContainerSlot >= startIndex && hoveredContainerSlot <= endIndex; + Slot slot = container.getSlot(hoveredContainerSlot); + ItemStack stackInSlot = slot.getItem().copy(); + + if (!stackInSlot.isEmpty()) { + // Try to move items + if (isEnderSlot) { + // Moving from Ender Chest to player inventory (slots 0-35) + if (moveItems(container, stackInSlot, 0, 36)) { + slot.set(stackInSlot.isEmpty() ? ItemStack.EMPTY : stackInSlot); + slot.setChanged(); + return true; + } + } else { + // Moving from player inventory to Ender Chest + if (moveItems(container, stackInSlot, startIndex, endIndex + 1)) { + slot.set(stackInSlot.isEmpty() ? ItemStack.EMPTY : stackInSlot); + slot.setChanged(); + return true; + } + } + } + } + } + + // Let default container handling take care of other clicks + return false; + } + + private boolean moveItems(AbstractContainerMenu container, ItemStack stack, int startIndex, int endIndex) { + boolean moved = false; + int originalCount = stack.getCount(); + + // Try to merge with existing stacks first + for (int i = startIndex; i < endIndex && !stack.isEmpty(); i++) { + Slot targetSlot = container.getSlot(i); + ItemStack targetStack = targetSlot.getItem(); + + if (!targetStack.isEmpty() && ItemStack.isSameItemSameTags(stack, targetStack)) { + int maxSize = Math.min(targetSlot.getMaxStackSize(), targetStack.getMaxStackSize()); + int toTransfer = Math.min(stack.getCount(), maxSize - targetStack.getCount()); + + if (toTransfer > 0) { + targetStack.grow(toTransfer); + stack.shrink(toTransfer); + targetSlot.setChanged(); + moved = true; + } + } + } + + // Then try to put in empty slots + for (int i = startIndex; i < endIndex && !stack.isEmpty(); i++) { + Slot targetSlot = container.getSlot(i); + + if (!targetSlot.mayPlace(stack)) { + continue; + } + + ItemStack targetStack = targetSlot.getItem(); + if (targetStack.isEmpty()) { + int toTransfer = Math.min(stack.getCount(), targetSlot.getMaxStackSize()); + targetSlot.set(stack.split(toTransfer)); + targetSlot.setChanged(); + moved = true; + } + } + + return moved && stack.getCount() < originalCount; + } + + @Override + public boolean handleScroll(AbstractContainerMenu container, int channel, int hoveringStorageSlot, double delta, + boolean hasClickedOutside, boolean hasClickedInStorage, int hoveredContainerSlot) { + // No scroll handling needed + return false; + } + + @Override + public int getActiveSlotId() { + return -1; + } + + @Override + public int getActiveSlotQuantity() { + return 0; + } + + @Override + public void setActiveSlotQuantity(int quantity) { + // No active slot for Ender Chest + } + + @Override + public List> getButtons() { + return Collections.emptyList(); + } + + @Override + public boolean isSlotValidForDraggingInto(int channel, Slot slot) { + // Allow dragging into Ender Chest slots + return true; + } + + @Override + public int computeDraggingQuantity(Set dragSlots, int dragMode, ItemStack stack, int quantity) { + // Use default dragging behavior + return quantity / Math.max(1, dragSlots.size()); + } + + @Override + public int dragIntoSlot(AbstractContainerMenu container, int channel, Slot slot, int quantity, boolean simulate) { + // Use default container behavior + return 0; + } +} diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestCommon.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestCommon.java new file mode 100644 index 000000000..0c0d7077f --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestCommon.java @@ -0,0 +1,66 @@ +package org.cyclops.integratedterminals.core.terminalstorage; + +import com.google.common.collect.Lists; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; + +import java.util.List; +import java.util.Optional; + +/** + * A common storage terminal tab for Ender Chest. + * @author rubensworks + */ +public class TerminalStorageTabEnderChestCommon implements ITerminalStorageTabCommon { + + private final ContainerTerminalStorageBase containerTerminalStorage; + private final ResourceLocation name; + + public TerminalStorageTabEnderChestCommon(ContainerTerminalStorageBase containerTerminalStorage, + ResourceLocation name) { + this.containerTerminalStorage = containerTerminalStorage; + this.name = name; + } + + @Override + public ResourceLocation getName() { + return this.name; + } + + @Override + public List> loadSlots(AbstractContainerMenu container, int startIndex, Player player, + Optional variableInventoryOptional) { + List> slots = Lists.newArrayList(); + + // Add Ender Chest slots (27 slots in 3 rows of 9) + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 9; col++) { + int slotIndex = col + row * 9; + int finalRow = row; + int finalCol = col; + + Slot slot = new Slot(player.getEnderChestInventory(), slotIndex, 0, 0); + ISlotPositionCallback positionCallback = (factors) -> { + int x = factors.offsetX() + finalCol * 18; + int y = factors.offsetY() + finalRow * 18; + return Pair.of(x, y); + }; + + slots.add(Pair.of(slot, positionCallback)); + } + } + + return slots; + } + + @Override + public void onUpdate(AbstractContainerMenu container, Player player, + Optional variableInventory) { + // No special update logic needed for Ender Chest + } +} diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestServer.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestServer.java new file mode 100644 index 000000000..588a6a184 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabEnderChestServer.java @@ -0,0 +1,40 @@ +package org.cyclops.integratedterminals.core.terminalstorage; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabServer; + +/** + * A server-side storage terminal tab for Ender Chest. + * @author rubensworks + */ +public class TerminalStorageTabEnderChestServer implements ITerminalStorageTabServer { + + private final ResourceLocation name; + private final ServerPlayer player; + + public TerminalStorageTabEnderChestServer(ResourceLocation name, ServerPlayer player) { + this.name = name; + this.player = player; + } + + @Override + public ResourceLocation getName() { + return this.name; + } + + @Override + public void init() { + // No initialization needed + } + + @Override + public void deInit() { + // No cleanup needed + } + + @Override + public void updateActive() { + // No active updates needed - Minecraft handles Ender Chest inventory updates automatically + } +} diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabs.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabs.java index 60f80bbf6..6026ee20c 100644 --- a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabs.java +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabs.java @@ -40,6 +40,9 @@ public static void afterIngredientComponentsRegistration(RegisterEvent event) { && ingredientComponentItemStack.getCapability(PositionedAddonsNetworkIngredientsHandlerConfig.CAPABILITY).isPresent()) { TerminalStorageTabs.REGISTRY.register(new TerminalStorageTabIngredientComponentItemStackCrafting(ingredientComponentItemStack)); } + + // Add Ender Chest tab (conditionally loaded per-terminal based on upgrade state) + TerminalStorageTabs.REGISTRY.register(new TerminalStorageTabEnderChest()); } } diff --git a/src/main/java/org/cyclops/integratedterminals/inventory/container/ContainerTerminalStorageBase.java b/src/main/java/org/cyclops/integratedterminals/inventory/container/ContainerTerminalStorageBase.java index 4f02593f0..a0c5f9774 100644 --- a/src/main/java/org/cyclops/integratedterminals/inventory/container/ContainerTerminalStorageBase.java +++ b/src/main/java/org/cyclops/integratedterminals/inventory/container/ContainerTerminalStorageBase.java @@ -100,6 +100,18 @@ public ContainerTerminalStorageBase(@Nullable MenuType type, int id, Inventor // Add all tabs from the registry for (ITerminalStorageTab tab : TerminalStorageTabs.REGISTRY.getTabs()) { String tabId = tab.getName().toString(); + + // Skip ender chest tab if terminal is not upgraded + if (tabId.equals("integratedterminals:ender_chest")) { + if (variableInventory.isPresent() && variableInventory.get() instanceof org.cyclops.integratedterminals.part.PartTypeTerminalStorage.State) { + org.cyclops.integratedterminals.part.PartTypeTerminalStorage.State partState = + (org.cyclops.integratedterminals.part.PartTypeTerminalStorage.State) variableInventory.get(); + if (!partState.isEnderUpgraded()) { + continue; // Skip this tab + } + } + } + if (this.getWorld().isClientSide()) { this.tabsClient.put(tabId, tab.createClientTab(this, player)); } else { diff --git a/src/main/java/org/cyclops/integratedterminals/part/PartTypeTerminalStorage.java b/src/main/java/org/cyclops/integratedterminals/part/PartTypeTerminalStorage.java index dcdcf274d..7ba67ff81 100644 --- a/src/main/java/org/cyclops/integratedterminals/part/PartTypeTerminalStorage.java +++ b/src/main/java/org/cyclops/integratedterminals/part/PartTypeTerminalStorage.java @@ -8,6 +8,11 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.MenuProvider; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionHand; +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.level.Level; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.nbt.ListTag; @@ -54,6 +59,30 @@ protected PartTypeTerminalStorage.State constructDefaultState() { return new PartTypeTerminalStorage.State(); } + @Override + public InteractionResult onPartActivated(State partState, BlockPos pos, Level world, Player player, InteractionHand hand, ItemStack heldItem, BlockHitResult hit) { + // Check if player is holding an Eye of Ender and terminal is not yet upgraded + if (!partState.isEnderUpgraded() && heldItem.getItem() == net.minecraft.world.item.Items.ENDER_EYE) { + if (!world.isClientSide) { + // Upgrade the terminal + partState.setEnderUpgraded(true); + + // Consume the Eye of Ender + if (!player.isCreative()) { + heldItem.shrink(1); + } + + // Play sound + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.END_PORTAL_FRAME_FILL, + net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + } + return InteractionResult.sidedSuccess(world.isClientSide); + } + + // Default behavior for opening GUI + return super.onPartActivated(partState, pos, world, player, hand, heldItem, hit); + } + @Override public Optional getContainerProvider(PartPos pos) { return Optional.of(new MenuProvider() { @@ -94,6 +123,12 @@ public void writeExtraGuiData(FriendlyByteBuf packetBuffer, PartPos pos, ServerP @Override public void addDrops(PartTarget target, State state, List itemStacks, boolean dropMainElement, boolean saveState) { + // If the terminal was ender-upgraded, drop an Eye of Ender and reset the upgrade + if (state.isEnderUpgraded() && dropMainElement) { + itemStacks.add(new ItemStack(net.minecraft.world.item.Items.ENDER_EYE)); + state.setEnderUpgraded(false); + } + for (Map.Entry> entry : state.getNamedInventories().entrySet()) { // TODO: for now hardcoded on crafting tab if (entry.getKey().equals(TerminalStorageTabIngredientComponentItemStackCrafting.NAME.toString())) { @@ -115,10 +150,21 @@ public static class State extends PartStateEmpty private final Map> namedInventories; private final Map playerStorageStates; + private boolean enderUpgraded; public State() { this.namedInventories = Maps.newHashMap(); this.playerStorageStates = Maps.newHashMap(); + this.enderUpgraded = false; + } + + public boolean isEnderUpgraded() { + return enderUpgraded; + } + + public void setEnderUpgraded(boolean enderUpgraded) { + this.enderUpgraded = enderUpgraded; + this.onDirty(); } @Override @@ -160,6 +206,9 @@ public TerminalStorageState getPlayerStorageState(Player player) { public void writeToNBT(CompoundTag tag) { super.writeToNBT(tag); + // Write enderUpgraded + tag.putBoolean("enderUpgraded", this.enderUpgraded); + // Write namedInventories ListTag namedInventoriesList = new ListTag(); for (Map.Entry> entry : this.namedInventories.entrySet()) { @@ -186,6 +235,9 @@ public void writeToNBT(CompoundTag tag) { public void readFromNBT(CompoundTag tag) { super.readFromNBT(tag); + // Read enderUpgraded + this.enderUpgraded = tag.getBoolean("enderUpgraded"); + // Read namedInventories for (Tag listEntry : tag.getList("namedInventories", Tag.TAG_COMPOUND)) { NonNullList list = NonNullList.withSize(((CompoundTag) listEntry).getInt("itemCount"), ItemStack.EMPTY); diff --git a/src/main/resources/assets/integratedterminals/lang/en_us.json b/src/main/resources/assets/integratedterminals/lang/en_us.json index 9dbbd48d4..428506531 100644 --- a/src/main/resources/assets/integratedterminals/lang/en_us.json +++ b/src/main/resources/assets/integratedterminals/lang/en_us.json @@ -11,6 +11,7 @@ "gui.integratedterminals.terminal_storage.tooltip.fluid.amount": "%s mB", "gui.integratedterminals.terminal_storage.storage_name": "%s Storage", "gui.integratedterminals.terminal_storage.crafting_name": "%s Crafting", + "gui.integratedterminals.terminal_storage.ender_chest": "Ender Storage", "gui.integratedterminals.terminal_storage.channel": "Chan:", "gui.integratedterminals.terminal_storage.craft": "craft", "gui.integratedterminals.terminal_storage.tooltip.requirements": "Crafting Requirements:",