diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java index a451fa2635c..8ac05f3924e 100644 --- a/src/main/java/gregtech/api/capability/GregtechDataCodes.java +++ b/src/main/java/gregtech/api/capability/GregtechDataCodes.java @@ -21,6 +21,7 @@ public static int assignId() { public static final int UPDATE_OUTPUT_FACING = assignId(); public static final int UPDATE_AUTO_OUTPUT_ITEMS = assignId(); public static final int UPDATE_AUTO_OUTPUT_FLUIDS = assignId(); + public static final int UPDATE_IS_VOIDING = assignId(); // Drum public static final int UPDATE_AUTO_OUTPUT = assignId(); diff --git a/src/main/java/gregtech/api/gui/GuiTextures.java b/src/main/java/gregtech/api/gui/GuiTextures.java index 02a8400adfd..69690446eee 100644 --- a/src/main/java/gregtech/api/gui/GuiTextures.java +++ b/src/main/java/gregtech/api/gui/GuiTextures.java @@ -44,7 +44,8 @@ public class GuiTextures { public static final TextureArea BUTTON_FLUID_OUTPUT = TextureArea.fullImage("textures/gui/widget/button_fluid_output_overlay.png"); public static final TextureArea BUTTON_ITEM_OUTPUT = TextureArea.fullImage("textures/gui/widget/button_item_output_overlay.png"); public static final TextureArea BUTTON_LOCK = TextureArea.fullImage("textures/gui/widget/button_lock.png"); - public static final TextureArea BUTTON_VOID = TextureArea.fullImage("textures/gui/widget/button_void.png"); + public static final TextureArea BUTTON_FLUID_VOID = TextureArea.fullImage("textures/gui/widget/button_fluid_void.png"); + public static final TextureArea BUTTON_ITEM_VOID = TextureArea.fullImage("textures/gui/widget/button_item_void.png"); public static final TextureArea BUTTON_VOID_NONE = TextureArea.fullImage("textures/gui/widget/button_void_none.png"); public static final TextureArea BUTTON_VOID_MULTIBLOCK = TextureArea.fullImage("textures/gui/widget/button_void_multiblock.png"); public static final TextureArea BUTTON_LEFT = TextureArea.fullImage("textures/gui/widget/left.png"); diff --git a/src/main/java/gregtech/api/util/TextFormattingUtil.java b/src/main/java/gregtech/api/util/TextFormattingUtil.java index 72aea3ce72d..d2e4ad4595a 100644 --- a/src/main/java/gregtech/api/util/TextFormattingUtil.java +++ b/src/main/java/gregtech/api/util/TextFormattingUtil.java @@ -59,4 +59,32 @@ public static String formatNumbers(double number) { public static String formatNumbers(Object number) { return NUMBER_FORMAT.format(number); } + + /** + * Formats a string to multiple lines, attempting to place a new line at the closest space from "maxLength" characters away. + * @param toFormat the string to be formatted to multiple lines. + * @param maxLength the length where a newline should be placed in the nearest space. + * @return a string formatted with newlines. + */ + public static String formatStringWithNewlines(String toFormat, int maxLength) { + String[] name = toFormat.split(" "); + StringBuilder builder = new StringBuilder(); + int length = 0; + for (String s : name) { + length += s.length(); + + if (length > maxLength) { + builder.append("\n"); + builder.append(s); + length = 0; + continue; + } + + if (builder.length() != 0) + builder.append(" "); + + builder.append(s); + } + return builder.toString(); + } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumChest.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumChest.java index 95c79ee5b07..55277cb4e4c 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumChest.java +++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumChest.java @@ -22,7 +22,9 @@ import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.util.GTLog; +import gregtech.api.util.GTTransferUtils; import gregtech.api.util.GTUtility; +import gregtech.api.util.TextFormattingUtil; import gregtech.client.renderer.texture.Textures; import gregtech.client.renderer.texture.custom.QuantumStorageRenderer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -58,20 +60,24 @@ public class MetaTileEntityQuantumChest extends MetaTileEntity implements ITieredMetaTileEntity, IActiveOutputSide, IFastRenderMetaTileEntity { + private final int tier; - private final long maxStoredItems; + protected final long maxStoredItems; + /** The ItemStack that the Quantum Chest is storing */ protected ItemStack virtualItemStack = ItemStack.EMPTY; - private long itemsStoredInside = 0L; + protected long itemsStoredInside = 0L; private boolean autoOutputItems; private EnumFacing outputFacing; private boolean allowInputFromOutputSide = false; private static final String NBT_ITEMSTACK = "ItemStack"; private static final String NBT_PARTIALSTACK = "PartialStack"; private static final String NBT_ITEMCOUNT = "ItemAmount"; + private static final String IS_VOIDING = "IsVoiding"; protected IItemHandler outputItemInventory; private ItemHandlerList combinedInventory; - private ItemStack previousStack; - private long previousStackSize; + protected ItemStack previousStack; + protected long previousStackSize; + protected boolean voiding; public MetaTileEntityQuantumChest(ResourceLocation metaTileEntityId, int tier, long maxStoredItems) { super(metaTileEntityId); @@ -122,41 +128,30 @@ public void update() { if (itemsStoredInside < maxStoredItems) { ItemStack inputStack = importItems.getStackInSlot(0); ItemStack outputStack = exportItems.getStackInSlot(0); - if (outputStack.isEmpty() || outputStack.isItemEqual(inputStack) && ItemStack.areItemStackTagsEqual(inputStack, outputStack)) { - if (!inputStack.isEmpty() && (virtualItemStack.isEmpty() || areItemStackIdentical(virtualItemStack, inputStack))) { - int amountOfItemsToInsert = (int) Math.min(inputStack.getCount(), maxStoredItems - itemsStoredInside); - if (this.itemsStoredInside == 0L || virtualItemStack.isEmpty()) { - this.virtualItemStack = GTUtility.copy(1, inputStack); - } - inputStack.shrink(amountOfItemsToInsert); - importItems.setStackInSlot(0, inputStack); - this.itemsStoredInside += amountOfItemsToInsert; - - markDirty(); - } + if (!inputStack.isEmpty() && (virtualItemStack.isEmpty() || areItemStackIdentical(outputStack, inputStack))) { + GTTransferUtils.moveInventoryItems(importItems, combinedInventory); + + markDirty(); } } if (itemsStoredInside > 0 && !virtualItemStack.isEmpty()) { ItemStack outputStack = exportItems.getStackInSlot(0); int maxStackSize = virtualItemStack.getMaxStackSize(); if (outputStack.isEmpty() || (areItemStackIdentical(virtualItemStack, outputStack) && outputStack.getCount() < maxStackSize)) { - int amountOfItemsToRemove = (int) Math.min(maxStackSize - outputStack.getCount(), itemsStoredInside); - if (outputStack.isEmpty()) { - outputStack = GTUtility.copy(amountOfItemsToRemove, virtualItemStack); - } else outputStack.grow(amountOfItemsToRemove); - exportItems.setStackInSlot(0, outputStack); - this.itemsStoredInside -= amountOfItemsToRemove; - if (this.itemsStoredInside == 0) { - this.virtualItemStack = ItemStack.EMPTY; - } + GTTransferUtils.moveInventoryItems(itemInventory, exportItems); markDirty(); } - } + if (isAutoOutputItems()) { pushItemsIntoNearbyHandlers(currentOutputFacing); } + + if (this.voiding && !importItems.getStackInSlot(0).isEmpty()) { + importItems.setStackInSlot(0, ItemStack.EMPTY); + } + if (previousStack == null || !areItemStackIdentical(previousStack, virtualItemStack)) { writeCustomData(UPDATE_ITEM, buf -> buf.writeItemStack(virtualItemStack)); previousStack = virtualItemStack; @@ -168,7 +163,7 @@ public void update() { } } - private static boolean areItemStackIdentical(ItemStack first, ItemStack second) { + protected static boolean areItemStackIdentical(ItemStack first, ItemStack second) { return ItemStack.areItemsEqual(first, second) && ItemStack.areItemStackTagsEqual(first, second); } @@ -176,6 +171,10 @@ private static boolean areItemStackIdentical(ItemStack first, ItemStack second) protected void addDisplayInformation(List textList) { textList.add(new TextComponentTranslation("gregtech.machine.quantum_chest.items_stored")); textList.add(new TextComponentString(String.format("%,d", itemsStoredInside))); + ItemStack export = exportItems.getStackInSlot(0); + if (!export.isEmpty()) { + textList.add(new TextComponentString(TextFormattingUtil.formatStringWithNewlines(export.getDisplayName(), 14))); + } } @Override @@ -188,17 +187,18 @@ public void addInformation(ItemStack stack, @Nullable World player, List if (compound != null) { String translationKey = null; long count = 0; + int exportCount = 0; if (compound.hasKey(NBT_ITEMSTACK)) { - translationKey = new ItemStack(compound.getCompoundTag(NBT_ITEMSTACK)).getDisplayName(); count = compound.getLong(NBT_ITEMCOUNT); - } else if (compound.hasKey(NBT_PARTIALSTACK)) { + } + if (compound.hasKey(NBT_PARTIALSTACK)) { ItemStack tempStack = new ItemStack(compound.getCompoundTag(NBT_PARTIALSTACK)); translationKey = tempStack.getDisplayName(); - count = tempStack.getCount(); + exportCount = tempStack.getCount(); } if (translationKey != null) { tooltip.add(I18n.format("gregtech.universal.tooltip.item_stored", - I18n.format(translationKey), count)); + I18n.format(translationKey), count, exportCount)); } } } @@ -214,22 +214,35 @@ public void addToolUsages(ItemStack stack, @Nullable World world, List t protected void initializeInventory() { super.initializeInventory(); this.itemInventory = new QuantumChestItemHandler(); - this.outputItemInventory = new ItemHandlerProxy(new GTItemStackHandler(this, 0), exportItems); List temp = new ArrayList<>(); - temp.add(outputItemInventory); - temp.add(itemInventory); - combinedInventory = new ItemHandlerList(temp); + temp.add(this.exportItems); + temp.add(this.itemInventory); + this.combinedInventory = new ItemHandlerList(temp); + this.outputItemInventory = new ItemHandlerProxy(new GTItemStackHandler(this, 0), combinedInventory); } @Override protected IItemHandlerModifiable createImportItemHandler() { return new GTItemStackHandler(this, 1) { + + @Nonnull + @Override + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (!isItemValid(slot, stack)) return stack; + return GTTransferUtils.insertItem(getCombinedInventory(), stack, simulate); + } + @Override public boolean isItemValid(int slot, @Nonnull ItemStack stack) { NBTTagCompound compound = stack.getTagCompound(); + ItemStack outStack = getExportItems().getStackInSlot(0); + boolean outStackMatch = true; + if (!outStack.isEmpty()) { + outStackMatch = areItemStackIdentical(stack, outStack); + } if (compound == null) return true; - return !(compound.hasKey(NBT_ITEMSTACK, NBT.TAG_COMPOUND) || compound.hasKey("Fluid", NBT.TAG_COMPOUND)); //prevents inserting items with NBT to the Quantum Chest + return outStackMatch && !(compound.hasKey(NBT_ITEMSTACK, NBT.TAG_COMPOUND) || compound.hasKey("Fluid", NBT.TAG_COMPOUND)); //prevents inserting items with NBT to the Quantum Chest } }; } @@ -249,6 +262,7 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) { tagCompound.setTag(NBT_ITEMSTACK, virtualItemStack.writeToNBT(new NBTTagCompound())); tagCompound.setLong(NBT_ITEMCOUNT, itemsStoredInside); } + data.setBoolean(IS_VOIDING, voiding); return tagCompound; } @@ -264,6 +278,9 @@ public void readFromNBT(NBTTagCompound data) { this.itemsStoredInside = data.getLong(NBT_ITEMCOUNT); } } + if (data.hasKey(IS_VOIDING)){ + this.voiding = data.getBoolean(IS_VOIDING); + } } @Override @@ -274,9 +291,14 @@ public void initFromItemStackData(NBTTagCompound itemStack) { if (!this.virtualItemStack.isEmpty()) { this.itemsStoredInside = itemStack.getLong(NBT_ITEMCOUNT); } - } else if (itemStack.hasKey(NBT_PARTIALSTACK, NBT.TAG_COMPOUND)) { + } + if (itemStack.hasKey(NBT_PARTIALSTACK, NBT.TAG_COMPOUND)) { exportItems.setStackInSlot(0, new ItemStack(itemStack.getCompoundTag(NBT_PARTIALSTACK))); } + + if (itemStack.getBoolean(IS_VOIDING)) { + setVoiding(true); + } } @Override @@ -284,13 +306,18 @@ public void writeItemStackData(NBTTagCompound itemStack) { super.writeItemStackData(itemStack); if (!this.virtualItemStack.isEmpty()) { itemStack.setTag(NBT_ITEMSTACK, this.virtualItemStack.writeToNBT(new NBTTagCompound())); - itemStack.setLong(NBT_ITEMCOUNT, itemsStoredInside + this.exportItems.getStackInSlot(0).getCount()); - } else { - ItemStack partialStack = exportItems.extractItem(0, 64, false); - if (!partialStack.isEmpty()) { - itemStack.setTag(NBT_PARTIALSTACK, partialStack.writeToNBT(new NBTTagCompound())); - } + itemStack.setLong(NBT_ITEMCOUNT, itemsStoredInside); + } + ItemStack partialStack = exportItems.extractItem(0, 64, false); + if (!partialStack.isEmpty()) { + itemStack.setTag(NBT_PARTIALSTACK, partialStack.writeToNBT(new NBTTagCompound())); + } + + + if (this.voiding) { + itemStack.setBoolean(IS_VOIDING, true); } + this.virtualItemStack = ItemStack.EMPTY; this.itemsStoredInside = 0; exportItems.setStackInSlot(0, ItemStack.EMPTY); @@ -299,18 +326,23 @@ public void writeItemStackData(NBTTagCompound itemStack) { @Override protected ModularUI createUI(EntityPlayer entityPlayer) { Builder builder = ModularUI.defaultBuilder(); - int leftButtonStartX = 7; - builder.image(7, 16, 81, 55, GuiTextures.DISPLAY); + builder.image(7, 16, 81, 46, GuiTextures.DISPLAY); builder.widget(new AdvancedTextWidget(11, 20, this::addDisplayInformation, 0xFFFFFF)); - return builder.label(6, 6, getMetaFullName()) + builder.label(6, 6, getMetaFullName()) .widget(new SlotWidget(importItems, 0, 90, 17, true, true) .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.IN_SLOT_OVERLAY)) - .widget(new SlotWidget(exportItems, 0, 90, 54, true, false) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.OUT_SLOT_OVERLAY)).widget(new ToggleButtonWidget(leftButtonStartX, 53, 18, 18, + .widget(new SlotWidget(exportItems, 0, 90, 44, true, false) + .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.OUT_SLOT_OVERLAY)) + .widget(new ToggleButtonWidget(7, 64, 18, 18, GuiTextures.BUTTON_ITEM_OUTPUT, this::isAutoOutputItems, this::setAutoOutputItems).shouldUseBaseBackground() .setTooltipText("gregtech.gui.item_auto_output.tooltip")) - .bindPlayerInventory(entityPlayer.inventory) - .build(getHolder(), entityPlayer); + .widget(new ToggleButtonWidget(25, 64, 18, 18, + GuiTextures.BUTTON_ITEM_VOID, this::isVoiding, this::setVoiding) + .setTooltipText("gregtech.gui.item_voiding.tooltip") + .shouldUseBaseBackground()) + .bindPlayerInventory(entityPlayer.inventory); + + return builder.build(getHolder(), entityPlayer); } public EnumFacing getOutputFacing() { @@ -347,6 +379,7 @@ public void writeInitialSyncData(PacketBuffer buf) { buf.writeBoolean(autoOutputItems); buf.writeItemStack(virtualItemStack); buf.writeLong(itemsStoredInside); + buf.writeBoolean(voiding); } @Override @@ -360,6 +393,7 @@ public void receiveInitialSyncData(PacketBuffer buf) { GTLog.logger.warn("Failed to load item from NBT in a quantum chest at " + this.getPos() + " on initial server/client sync"); } this.itemsStoredInside = buf.readLong(); + this.voiding = buf.readBoolean(); } @Override @@ -386,6 +420,8 @@ public void receiveCustomData(int dataId, PacketBuffer buf) { } } else if (dataId == UPDATE_ITEM_COUNT) { this.itemsStoredInside = buf.readLong(); + } else if (dataId == UPDATE_IS_VOIDING) { + setVoiding(buf.readBoolean()); } } @@ -397,6 +433,18 @@ public void setAutoOutputItems(boolean autoOutputItems) { } } + protected boolean isVoiding() { + return this.voiding; + } + + protected void setVoiding(boolean isVoiding) { + this.voiding = isVoiding; + if (!getWorld().isRemote) { + writeCustomData(UPDATE_IS_VOIDING, buf -> buf.writeBoolean(this.voiding)); + markDirty(); + } + } + @Override public T getCapability(Capability capability, EnumFacing side) { if (capability == GregtechTileCapabilities.CAPABILITY_ACTIVE_OUTPUT_SIDE) { @@ -405,11 +453,28 @@ public T getCapability(Capability capability, EnumFacing side) { } return null; } else if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { - return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.cast(combinedInventory); + + // for TOP/Waila + if (side == null) return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.cast(combinedInventory); + + // try fix being able to insert through output hole when input on output is disabled + IItemHandler itemHandler = (side == getOutputFacing() && !isAllowInputFromOutputSideItems()) ? outputItemInventory : combinedInventory; + if (itemHandler.getSlots() > 0) { + return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.cast(itemHandler); + } + return null; } return super.getCapability(capability, side); } + public IItemHandler getCombinedInventory() { + return this.combinedInventory; + } + + public IItemHandler getOutputItemInventory() { + return this.outputItemInventory; + } + @Override public void setFrontFacing(EnumFacing frontFacing) { super.setFrontFacing(frontFacing); @@ -485,9 +550,11 @@ public int getSlots() { public ItemStack getStackInSlot(int slot) { ItemStack itemStack = MetaTileEntityQuantumChest.this.virtualItemStack; long itemsStored = MetaTileEntityQuantumChest.this.itemsStoredInside; + if (itemStack.isEmpty() || itemsStored == 0L) { return ItemStack.EMPTY; } + ItemStack resultStack = itemStack.copy(); resultStack.setCount((int) itemsStored); return resultStack; @@ -505,8 +572,10 @@ public ItemStack extractItem(int slot, int amount, boolean simulate) { if (virtualItemStack.isEmpty() || extractedAmount == 0) { return ItemStack.EMPTY; } + ItemStack extractedStack = virtualItemStack.copy(); extractedStack.setCount(extractedAmount); + if (!simulate) { MetaTileEntityQuantumChest.this.itemsStoredInside -= extractedAmount; if (itemsStoredInside == 0L) { @@ -523,72 +592,48 @@ public ItemStack insertItem(int slot, @Nonnull ItemStack insertedStack, boolean return ItemStack.EMPTY; } + ItemStack exportItems = getExportItems().getStackInSlot(0); + // If there is a virtualized stack and the stack to insert does not match it, do not insert anything if (itemsStoredInside > 0L && - !virtualItemStack.isEmpty() && - !areItemStackIdentical(virtualItemStack, insertedStack)) { + !virtualItemStack.isEmpty() && ( + !areItemStackIdentical(virtualItemStack, insertedStack) || + !areItemStackIdentical(exportItems, insertedStack))) { return insertedStack; } - // The Quantum Chest automatically populates the export slot, so we need to check what is contained in it - ItemStack exportItems = getExportItems().getStackInSlot(0); - - // If the item being inserted does not match the item in the export slot, insert into the input slot and do not virtualize - if (!areItemStackIdentical(insertedStack, exportItems)) { - return MetaTileEntityQuantumChest.this.importItems.insertItem(0, insertedStack, simulate); - } - - int spaceInExport = Math.abs(exportItems.getMaxStackSize() - exportItems.getCount()); - - // Attempt to insert into the export slot first - int amountCanInsertIntoExport = Math.min(spaceInExport, insertedStack.getCount()); - - if (insertedStack.getCount() <= amountCanInsertIntoExport) { - // If all the items can fit into export slot, store it there - return MetaTileEntityQuantumChest.this.exportItems.insertItem(0, insertedStack, simulate); - } - - // Have more items than would fit into the export slot, so virtualize the remainder - long amountLeftInChest = virtualItemStack.isEmpty() ? maxStoredItems : maxStoredItems - itemsStoredInside; + ItemStack remainingStack = insertedStack.copy(); - int maxVirtualAmount = insertedStack.getCount() - amountCanInsertIntoExport; - int virtualizedAmount = (int) Math.min(maxVirtualAmount, amountLeftInChest); + // Virtualize the items + long amountLeftInChest = maxStoredItems - itemsStoredInside; + int maxPotentialVirtualizedAmount = insertedStack.getCount(); - ItemStack remainingStack = ItemStack.EMPTY; + int actualVirtualizedAmount = (int) Math.min(maxPotentialVirtualizedAmount, amountLeftInChest); - // If we are at the maximum that the chest can hold, the remainder stack has all items that could not fit - if (virtualizedAmount < maxVirtualAmount) { - remainingStack = insertedStack.copy(); - remainingStack.setCount(insertedStack.getCount() - virtualizedAmount); - } + // if there are any items left over, shrink it by the amount virtualized, + // which should always be between 0 and the amount left in chest + remainingStack.shrink(actualVirtualizedAmount); if (!simulate) { - if (remainingStack.isEmpty()) { - // inserted everything + if (actualVirtualizedAmount > 0) { if (virtualItemStack.isEmpty()) { - // have no virtual stack, so set it to the inserted stack ItemStack virtualStack = insertedStack.copy(); - virtualStack.setCount(virtualizedAmount); + + // set the virtual stack to 1, since it's mostly for display + virtualStack.setCount(1); MetaTileEntityQuantumChest.this.virtualItemStack = virtualStack; - MetaTileEntityQuantumChest.this.itemsStoredInside = virtualizedAmount; + MetaTileEntityQuantumChest.this.itemsStoredInside = actualVirtualizedAmount; } else { - // update the virtualized total count - MetaTileEntityQuantumChest.this.itemsStoredInside += virtualizedAmount; - } - - if (amountCanInsertIntoExport != 0) { - // fill the export slot as much as possible - ItemStack insertedStackCopy = insertedStack.copy(); - insertedStackCopy.setCount(amountCanInsertIntoExport); - MetaTileEntityQuantumChest.this.exportItems.insertItem(0, insertedStackCopy, simulate); + MetaTileEntityQuantumChest.this.itemsStoredInside += actualVirtualizedAmount; } - } else { - // could not fit everything, but still need to update the virtualized total count - MetaTileEntityQuantumChest.this.itemsStoredInside += virtualizedAmount; } } - return remainingStack; + if (isVoiding() && remainingStack.getCount() > 0) { + return ItemStack.EMPTY; + } else { + return remainingStack; + } } } diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java index 4fc4b9e4027..2661749ac3d 100644 --- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java +++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java @@ -73,9 +73,9 @@ public class MetaTileEntityQuantumTank extends MetaTileEntity implements ITiered protected IFluidHandler outputFluidInventory; @Nullable - private FluidStack previousFluid; - private boolean locked; - private boolean voiding; + protected FluidStack previousFluid; + protected boolean locked; + protected boolean voiding; @Nullable private FluidStack lockedFluid; @@ -135,7 +135,7 @@ public void update() { } // should only be called on the server - private void updatePreviousFluid(FluidStack currentFluid) { + protected void updatePreviousFluid(FluidStack currentFluid) { previousFluid = currentFluid == null ? null : currentFluid.copy(); writeCustomData(UPDATE_FLUID, buf -> buf.writeCompoundTag(currentFluid == null ? null : currentFluid.writeToNBT(new NBTTagCompound()))); } @@ -337,7 +337,7 @@ protected ModularUI createUI(EntityPlayer entityPlayer) { .setTooltipText("gregtech.gui.fluid_lock.tooltip") .shouldUseBaseBackground()) .widget(new ToggleButtonWidget(43, 64, 18, 18, - GuiTextures.BUTTON_VOID, this::isVoiding, this::setVoiding) + GuiTextures.BUTTON_FLUID_VOID, this::isVoiding, this::setVoiding) .setTooltipText("gregtech.gui.fluid_voiding.tooltip") .shouldUseBaseBackground()) .bindPlayerInventory(entityPlayer.inventory) @@ -440,6 +440,8 @@ public void receiveCustomData(int dataId, PacketBuffer buf) { stack.amount = Math.min(buf.readInt(), fluidTank.getCapacity()); scheduleRenderUpdate(); } + } else if (dataId == UPDATE_IS_VOIDING) { + setVoiding(buf.readBoolean()); } } @@ -455,6 +457,7 @@ public void writeInitialSyncData(PacketBuffer buf) { buf.writeBoolean(autoOutputFluids); buf.writeBoolean(locked); buf.writeCompoundTag(fluidTank.getFluid() == null ? null : fluidTank.getFluid().writeToNBT(new NBTTagCompound())); + buf.writeBoolean(voiding); } @Override @@ -476,6 +479,7 @@ public void receiveInitialSyncData(PacketBuffer buf) { } catch (IOException e) { GTLog.logger.warn("Failed to load fluid from NBT in a quantum tank at " + this.getPos() + " on initial server/client sync"); } + this.voiding = buf.readBoolean(); } public void setOutputFacing(EnumFacing outputFacing) { @@ -559,11 +563,11 @@ public void setAutoOutputFluids(boolean autoOutputFluids) { } } - private boolean isLocked() { + protected boolean isLocked() { return this.locked; } - private void setLocked(boolean locked) { + protected void setLocked(boolean locked) { if (this.locked == locked) return; this.locked = locked; if (!getWorld().isRemote) { @@ -577,13 +581,14 @@ private void setLocked(boolean locked) { this.lockedFluid = null; } - private boolean isVoiding() { + protected boolean isVoiding() { return voiding; } - private void setVoiding(boolean isPartialVoid) { + protected void setVoiding(boolean isPartialVoid) { this.voiding = isPartialVoid; if (!getWorld().isRemote) { + writeCustomData(UPDATE_IS_VOIDING, buf -> buf.writeBoolean(this.voiding)); markDirty(); } } diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 644a65a77a8..bb25aca185d 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -5190,7 +5190,7 @@ gregtech.universal.tooltip.energy_storage_capacity=§cEnergy Capacity: §f%,d EU gregtech.universal.tooltip.energy_tier_range=§aAllowed Voltage Tiers: §f%s §f- %s gregtech.universal.tooltip.item_storage_capacity=§6Item Slots: §f%,d gregtech.universal.tooltip.item_storage_total=§6Item Capacity: §f%,d items -gregtech.universal.tooltip.item_stored=§dItem Stored: §f%s, %,d items +gregtech.universal.tooltip.item_stored=§dItem Stored: §f%s, %,d + %,d items gregtech.universal.tooltip.item_transfer_rate=§bTransfer Rate: §f%,d items/s gregtech.universal.tooltip.item_transfer_rate_stacks=§bTransfer Rate: §f%,d stacks/s gregtech.universal.tooltip.fluid_storage_capacity=§9Fluid Capacity: §f%,d L @@ -5295,6 +5295,8 @@ gregtech.gui.fluid_lock.tooltip.enabled=Fluid Locking Enabled gregtech.gui.fluid_lock.tooltip.disabled=Fluid Locking Disabled gregtech.gui.fluid_voiding.tooltip.enabled=Excess Fluid Voiding Enabled gregtech.gui.fluid_voiding.tooltip.disabled=Excess Fluid Voiding Disabled +gregtech.gui.item_voiding.tooltip.enabled=Excess Item Voiding Enabled +gregtech.gui.item_voiding.tooltip.disabled=Excess Item Voiding Disabled gregtech.gui.multiblock_item_voiding=Voiding Mode/n§7Voiding §6Items gregtech.gui.multiblock_fluid_voiding=Voiding Mode/n§7Voiding §9Fluids gregtech.gui.multiblock_item_fluid_voiding=Voiding Mode/n§7Voiding §6Items §7and §9Fluids diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_void.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_fluid_void.png similarity index 100% rename from src/main/resources/assets/gregtech/textures/gui/widget/button_void.png rename to src/main/resources/assets/gregtech/textures/gui/widget/button_fluid_void.png diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_item_void.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_item_void.png new file mode 100644 index 00000000000..d7b1ddb860e Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_item_void.png differ diff --git a/src/test/java/gregtech/common/metatileentities/storage/QuantumChestTest.java b/src/test/java/gregtech/common/metatileentities/storage/QuantumChestTest.java new file mode 100644 index 00000000000..ae51478a28f --- /dev/null +++ b/src/test/java/gregtech/common/metatileentities/storage/QuantumChestTest.java @@ -0,0 +1,249 @@ +package gregtech.common.metatileentities.storage; + +import gregtech.Bootstrap; +import gregtech.api.GTValues; +import gregtech.api.util.GTUtility; +import gregtech.api.util.world.DummyWorld; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static gregtech.api.util.GTStringUtils.itemStackToString; +import static gregtech.api.util.GTTransferUtils.insertItem; +import static gregtech.api.util.GTUtility.gregtechId; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +public class QuantumChestTest { + private static ItemStack GRAVEL; + private static ItemStack SAND; + + @BeforeAll + public static void bootstrap() { + Bootstrap.perform(); + GRAVEL = new ItemStack(Blocks.GRAVEL); + GRAVEL.setCount(64); + SAND = new ItemStack(Blocks.SAND); + SAND.setCount(64); + } + + @Test + public void Test_Valid() { + for (var quantumChest : createInstances()) { + assertThat(quantumChest, notNullValue()); + } + } + + @Test + public void Test_Single_Insertion() { + for (var quantumChest : createInstances()) { + IItemHandler combinedInventory = quantumChest.getCombinedInventory(); + IItemHandler virtualInventory = quantumChest.getItemInventory(); + IItemHandlerModifiable exportItems = quantumChest.getExportItems(); + IItemHandlerModifiable importItems = quantumChest.getImportItems(); + + ItemStack stack = insertItem(combinedInventory, GRAVEL.copy(), true); + String reason = String.format("%s should be Empty!", itemStackToString(stack)); + assertThat(reason, stack.isEmpty()); + + insertItem(combinedInventory, GRAVEL.copy(), false); + int expected = 64; + stack = exportItems.getStackInSlot(0); + reason = String.format("Got %d in the export slot when it should be %d", exportItems.getStackInSlot(0).getCount(), expected); + assertThat(reason, stack.getCount(), is(expected)); + + stack = insertItem(combinedInventory, GRAVEL.copy(), true); + reason = String.format("%s should be Empty!", itemStackToString(stack)); + assertThat(reason, stack.isEmpty()); + + insertItem(combinedInventory, GRAVEL.copy(), false); + stack = virtualInventory.getStackInSlot(0); + reason = String.format("Got %d in the virtualized inventory when it should be %d", stack.getCount(), expected); + assertThat(reason, stack.getCount(), is(expected)); + + stack = importItems.insertItem(0, SAND.copy(), true); + reason = String.format("%s should not be Empty!", itemStackToString(stack)); + assertThat(reason, !stack.isEmpty()); + + stack = importItems.insertItem(0, SAND.copy(), false); + reason = String.format("%s should be Sand!", itemStackToString(stack)); + assertThat(reason, !stack.isEmpty() && stack.isItemEqual(SAND)); + + stack = virtualInventory.getStackInSlot(0); + reason = String.format("Got %d in the virtualized inventory when it should be %d", stack.getCount(), expected); + assertThat(reason, stack.getCount(), is(expected)); + } + } + + @Test + public void Test_Stack_In_Slot() { + for (var quantumChest : createInstances()) { + IItemHandler itemInventory = quantumChest.getItemInventory(); + int expected = 256; + + insertItem(quantumChest.getCombinedInventory(), GTUtility.copy(expected, SAND), false); + + expected = 64; + int exportCount = quantumChest.getExportItems().getStackInSlot(0).getCount(); + String reason = String.format("The combined count using the chest's handler and the export slot got %d, should've been %d", exportCount, expected); + assertThat(reason, exportCount, is(expected)); + + expected = 192; + int virtualizedAmount = itemInventory.getStackInSlot(0).getCount(); + reason = String.format("The virtualized amount in the chest's handler got %d, should've been %d", exportCount, expected); + assertThat(reason, virtualizedAmount, is(expected)); + } + } + + @Test + public void Test_Multiple_Insertions() { + for (var quantumChest : createInstances()) { + IItemHandler itemInventory = quantumChest.getItemInventory(); + IItemHandlerModifiable exportItems = quantumChest.getExportItems(); + IItemHandlerModifiable importItems = quantumChest.getImportItems(); + + for (int i = 0; i < 16; i++) { + importItems.insertItem(0, GRAVEL.copy(), false); + quantumChest.update(); + } + + ItemStack virtualized = itemInventory.getStackInSlot(0); + ItemStack export = exportItems.getStackInSlot(0); + + assertThat("Virtualized amount is wrong!", virtualized.getCount(), is(64 * 15)); + assertThat("Export slot is empty!", export.getCount() == 64 && !export.isEmpty()); + assertThat("Import slot has an item in it!", importItems.getStackInSlot(0).isEmpty()); + } + } + + @Test + public void Test_Insertion_And_Update() { + for (var quantumChest : createInstances()) { + IItemHandlerModifiable exportItems = quantumChest.getExportItems(); + IItemHandlerModifiable importItems = quantumChest.getImportItems(); + + importItems.insertItem(0, GRAVEL.copy(), false); + quantumChest.update(); + ItemStack stack = importItems.getStackInSlot(0); + + assertThat(String.format("%s should be Empty!", itemStackToString(stack)), stack.isEmpty()); + assertThat("Virtual stack should be empty!", quantumChest.virtualItemStack.isEmpty()); + assertThat("Export slot was not filled!", !exportItems.getStackInSlot(0).isEmpty()); + + importItems.insertItem(0, GRAVEL.copy(), false); + quantumChest.update(); + assertThat("Virtual stack should not be empty!", !quantumChest.virtualItemStack.isEmpty()); + } + } + + @Test + public void Test_Insertion_Near_Full() { + for (var quantumChest : createInstances()) { + if (quantumChest.getTier() >= GTValues.UHV) continue; // UHV can't be tested due to int overflow :floppaxd: + IItemHandler combinedInventory = quantumChest.getCombinedInventory(); + int toInsert = quantumChest.getItemInventory().getSlotLimit(0) + 32; + insertItem(combinedInventory, GTUtility.copy(toInsert, SAND), false); + + int remainder = insertItem(quantumChest.getImportItems(), SAND.copy(), true).getCount(); + quantumChest.update(); + String reason = String.format("Remainder should be exactly %d, but was %d!", 32, remainder); + assertThat(reason, remainder, is(32)); + } + } + + @Test + public void Test_Voiding() { + for (var quantumChest : createInstances()) { + ItemStack stack = GRAVEL.copy(); + stack.setCount(Integer.MAX_VALUE); + + insertItem(quantumChest.getCombinedInventory(), stack, false); + + // UHV qchest stores exactly Integer.MAX_VALUE, so it will be 64 items less than expected + long transferred = quantumChest.getTier() == GTValues.UHV ? quantumChest.itemsStoredInside + 64 : quantumChest.itemsStoredInside; + + assertThat(String.format("%s voided %s too early!", quantumChest.getMetaFullName(), stack), transferred == quantumChest.maxStoredItems); + + quantumChest.setVoiding(true); + ItemStack remainder = insertItem(quantumChest.getItemInventory(), stack, false); + assertThat(String.format("%s was not voided!", remainder), remainder.isEmpty()); + + stack = SAND.copy(); + stack.setCount(Integer.MAX_VALUE); + remainder = insertItem(quantumChest.getItemInventory(), stack, false); + assertThat("Quantum Chest voided the wrong item!", remainder.getCount(), is(stack.getCount())); + } + } + + @Test + public void Test_Extraction() { + for (var quantumChest : createInstances()) { + insertItem(quantumChest.getCombinedInventory(), GTUtility.copy(256, GRAVEL), false); + + int extractedCount = testAllSlots(quantumChest.getExportItems(), true); + int expected = 64; + + String reason = String.format("Quantum chest failed to insert %d items into export slot, actually was %d!", expected, extractedCount); + assertThat(reason, extractedCount, is(expected)); + + quantumChest.getExportItems().extractItem(0, 64, false); + quantumChest.update(); + extractedCount = quantumChest.getItemInventory().getStackInSlot(0).getCount(); + + expected = 128; + reason = String.format("Virtualized count is %d, should be %d!", extractedCount, expected); + assertThat(reason, extractedCount, is(expected)); + + expected = 192; + extractedCount = testAllSlots(quantumChest.getCombinedInventory(), true); + + reason = String.format("Extracted %d items, should've extracted %d!", extractedCount, expected); + assertThat(reason, extractedCount, is(expected)); + } + } + + private static QuantumChestWrapper[] createInstances() { + QuantumChestWrapper[] quantumChests = new QuantumChestWrapper[10]; + for (int i = 0; i < 5; i++) { + String voltageName = GTValues.VN[i + 1].toLowerCase(); + quantumChests[i] = new QuantumChestWrapper(gregtechId("super_chest." + voltageName), i + 1, 4000000L * (int) Math.pow(2, i)); + } + + for (int i = 5; i < quantumChests.length; i++) { + String voltageName = GTValues.VN[i].toLowerCase(); + long capacity = i == GTValues.UHV ? Integer.MAX_VALUE : 4000000L * (int) Math.pow(2, i); + quantumChests[i] = new QuantumChestWrapper(gregtechId("quantum_chest." + voltageName), i, capacity); + } + return quantumChests; + } + + private static int testAllSlots(IItemHandler handler, boolean simulate) { + int extractedCount = 0; + for (int i = 0; i < handler.getSlots(); i++) { + extractedCount += handler.extractItem(i, Integer.MAX_VALUE, simulate).getCount(); + } + return extractedCount; + } + + private static class QuantumChestWrapper extends MetaTileEntityQuantumChest { + public QuantumChestWrapper(ResourceLocation metaTileEntityId, int tier, long maxStoredItems) { + super(metaTileEntityId, tier, maxStoredItems); + } + + @Override + protected void setVoiding(boolean isVoiding) { + this.voiding = isVoiding; + } + + @Override + public World getWorld() { + return DummyWorld.INSTANCE; + } + } +} diff --git a/src/test/java/gregtech/common/metatileentities/storage/QuantumTankTest.java b/src/test/java/gregtech/common/metatileentities/storage/QuantumTankTest.java new file mode 100644 index 00000000000..b2ae0c6c021 --- /dev/null +++ b/src/test/java/gregtech/common/metatileentities/storage/QuantumTankTest.java @@ -0,0 +1,161 @@ +package gregtech.common.metatileentities.storage; + +import gregtech.Bootstrap; +import gregtech.api.GTValues; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.fluids.capability.IFluidHandler; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static gregtech.api.capability.GregtechDataCodes.UPDATE_FLUID_AMOUNT; +import static gregtech.api.util.GTUtility.gregtechId; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +public class QuantumTankTest { + + private static FluidStack WATER; + private static FluidStack LAVA; + private static ItemStack BUCKET_WATER; + private static ItemStack BUCKET_LAVA; + @BeforeAll + public static void bootstrap() { + Bootstrap.perform(); + WATER = new FluidStack(FluidRegistry.WATER, 1000); + LAVA = new FluidStack(FluidRegistry.LAVA, 1000); + BUCKET_WATER = FluidUtil.getFilledBucket(WATER); + BUCKET_LAVA = FluidUtil.getFilledBucket(LAVA); + } + + @Test + public void Test_Valid() { + for (var quantumTank : createInstances()) { + assertThat(quantumTank, is(notNullValue())); + } + } + + @Test + public void Test_Insertion() { + for (var quantumTank : createInstances()) { + IFluidHandler handler = quantumTank.getFluidInventory(); + + int filled = handler.fill(WATER.copy(), false); + assertThat("Not all fluid was inserted!", filled == 1000); + + handler.fill(WATER.copy(), true); + assertThat("Quantum tank was not fully filled!", quantumTank.fluidTank.getFluidAmount() == 1000); + + filled = handler.fill(LAVA.copy(), true); + assertThat("Quantum tank inserted fluid different from it's internal fluid!", filled == 0); + + // todo test here to check inserting via filled fluid containers + } + } + + @Test + public void Test_Voiding() { + for (var quantumTank : createInstances()) { + IFluidHandler handler = quantumTank.getFluidInventory(); + FluidStack resource = WATER.copy(); + resource.amount = Integer.MAX_VALUE; + int inserted = handler.fill(resource, true); + assertThat("Quantum Tank accepted too much fluid!", inserted == handler.getTankProperties()[0].getCapacity()); + + quantumTank.setVoiding(true); + inserted = handler.fill(resource, true); + assertThat("Fluid was not properly voided!", inserted == resource.amount); + + inserted = handler.fill(LAVA.copy(), true); + assertThat("Quantum tank voided the wrong fluid!", inserted == 0); + } + } + + @Test + public void Test_Extraction() { + for (var quantumTank : createInstances()) { + IFluidHandler handler = quantumTank.getFluidInventory(); + FluidStack inTank = LAVA.copy(); + inTank.amount = 5000; + handler.fill(inTank, true); + + FluidStack extracted = handler.drain(2500, true); + assertThat("extracted was null!", extracted != null); + assertThat("Too much/little fluid was extracted!", extracted.amount == 2500); + + inTank = handler.getTankProperties()[0].getContents(); + assertThat("tank contents was null!", inTank != null); + assertThat("Too much/little fluid in quantum tank!", inTank.amount == 2500); + + // todo tests for extracting with an empty fluid container + } + } + + private QuantumTankWrapper[] createInstances() { + QuantumTankWrapper[] quantumTanks = new QuantumTankWrapper[10]; + for (int i = 0; i < 5; i++) { + String voltageName = GTValues.VN[i + 1].toLowerCase(); + quantumTanks[i] = new QuantumTankWrapper(gregtechId("super_tank." + voltageName), i + 1, 4000000 * (int) Math.pow(2, i)); + } + + for (int i = 5; i < quantumTanks.length; i++) { + String voltageName = GTValues.VN[i].toLowerCase(); + int capacity = i == GTValues.UHV ? Integer.MAX_VALUE : 4000000 * (int) Math.pow(2, i); + quantumTanks[i] = new QuantumTankWrapper(gregtechId("quantum_tank." + voltageName), i, capacity); + } + return quantumTanks; + } + + private static class QuantumTankWrapper extends MetaTileEntityQuantumTank { + + public QuantumTankWrapper(ResourceLocation metaTileEntityId, int tier, int maxFluidCapacity) { + super(metaTileEntityId, tier, maxFluidCapacity); + } + + @Override + protected void setVoiding(boolean isVoiding) { + this.voiding = isVoiding; + } + + private void fakeUpdate(boolean isRemote) { + EnumFacing currentOutputFacing = getOutputFacing(); + if (!isRemote) { + fillContainerFromInternalTank(); + fillInternalTankFromFluidContainer(); + if (isAutoOutputFluids()) { + pushFluidsIntoNearbyHandlers(currentOutputFacing); + } + + FluidStack currentFluid = fluidTank.getFluid(); + if (previousFluid == null) { + // tank was empty, but now is not + if (currentFluid != null) { + updatePreviousFluid(currentFluid); + } + } else { + if (currentFluid == null) { + // tank had fluid, but now is empty + updatePreviousFluid(null); + } else if (previousFluid.getFluid().equals(currentFluid.getFluid()) && previousFluid.amount != currentFluid.amount) { + // tank has fluid with changed amount + previousFluid.amount = currentFluid.amount; + writeCustomData(UPDATE_FLUID_AMOUNT, buf -> buf.writeInt(currentFluid.amount)); + } else if (!previousFluid.equals(currentFluid)) { + // tank has a different fluid from before + updatePreviousFluid(currentFluid); + } + } + } + } + + @Override + protected void updatePreviousFluid(FluidStack currentFluid) { + previousFluid = currentFluid == null ? null : currentFluid.copy(); + } + } +}