diff --git a/src/main/java/gregtech/api/mui/GTGuiTextures.java b/src/main/java/gregtech/api/mui/GTGuiTextures.java index f17affee609..4f0345f49fc 100644 --- a/src/main/java/gregtech/api/mui/GTGuiTextures.java +++ b/src/main/java/gregtech/api/mui/GTGuiTextures.java @@ -206,6 +206,8 @@ public static class IDs { "textures/gui/widget/button_public_private.png", 18, 36, 18, 18, true); + public static final UITexture MENU_OVERLAY = fullImage("textures/gui/overlay/menu_overlay.png"); + // todo bronze/steel/primitive fluid slots? // SLOT OVERLAYS diff --git a/src/main/java/gregtech/api/mui/GTGuis.java b/src/main/java/gregtech/api/mui/GTGuis.java index b5599dd9cfd..614f6a08f1b 100644 --- a/src/main/java/gregtech/api/mui/GTGuis.java +++ b/src/main/java/gregtech/api/mui/GTGuis.java @@ -9,6 +9,7 @@ import net.minecraft.item.ItemStack; +import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.factory.GuiManager; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.utils.Alignment; @@ -62,7 +63,15 @@ public PopupPanel(@NotNull String name, int width, int height, boolean disableBe super(name); size(width, height).align(Alignment.Center); background(GTGuiTextures.BACKGROUND_POPUP); - child(ButtonWidget.panelCloseButton().top(5).right(5)); + child(ButtonWidget.panelCloseButton().top(5).right(5) + .onMousePressed(mouseButton -> { + if (mouseButton == 0 || mouseButton == 1) { + this.closeIfOpen(true); + Interactable.playButtonClickSound(); + return true; + } + return false; + })); this.disableBelow = disableBelow; this.closeOnOutsideClick = closeOnOutsideClick; } diff --git a/src/main/java/gregtech/api/mui/sync/GTFluidSyncHandler.java b/src/main/java/gregtech/api/mui/sync/GTFluidSyncHandler.java new file mode 100644 index 00000000000..b5be013da18 --- /dev/null +++ b/src/main/java/gregtech/api/mui/sync/GTFluidSyncHandler.java @@ -0,0 +1,224 @@ +package gregtech.api.mui.sync; + +import gregtech.api.util.GTUtility; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; + +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import org.jetbrains.annotations.NotNull; + +public class GTFluidSyncHandler extends SyncHandler { + + private static final int TRY_CLICK_CONTAINER = 1; + + private final IFluidTank tank; + private boolean canDrainSlot = true; + private boolean canFillSlot = true; + + public GTFluidSyncHandler(IFluidTank tank) { + this.tank = tank; + } + + public FluidStack getFluid() { + return this.tank.getFluid(); + } + + public int getCapacity() { + return this.tank.getCapacity(); + } + + public GTFluidSyncHandler canDrainSlot(boolean canDrainSlot) { + this.canDrainSlot = canDrainSlot; + return this; + } + + public boolean canDrainSlot() { + return this.canDrainSlot; + } + + public GTFluidSyncHandler canFillSlot(boolean canFillSlot) { + this.canFillSlot = canFillSlot; + return this; + } + + public boolean canFillSlot() { + return this.canFillSlot; + } + + @Override + public void readOnClient(int id, PacketBuffer buf) { + if (id == TRY_CLICK_CONTAINER) { + replaceCursorItemStack(NetworkUtils.readItemStack(buf)); + } + } + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == TRY_CLICK_CONTAINER) { + var stack = tryClickContainer(buf.readBoolean()); + if (!stack.isEmpty()) + syncToClient(TRY_CLICK_CONTAINER, buffer -> NetworkUtils.writeItemStack(buffer, stack)); + } + } + + public ItemStack tryClickContainer(boolean tryFillAll) { + ItemStack playerHeldStack = getSyncManager().getCursorItem(); + if (playerHeldStack.isEmpty()) + return ItemStack.EMPTY; + + ItemStack useStack = GTUtility.copy(1, playerHeldStack); + IFluidHandlerItem fluidHandlerItem = useStack + .getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null); + if (fluidHandlerItem == null) return ItemStack.EMPTY; + + FluidStack tankFluid = tank.getFluid(); + FluidStack heldFluid = fluidHandlerItem.drain(Integer.MAX_VALUE, false); + + // nothing to do, return + if (tankFluid == null && heldFluid == null) + return ItemStack.EMPTY; + + // tank is empty, try to fill tank + if (canFillSlot && tankFluid == null) { + return fillTankFromStack(fluidHandlerItem, heldFluid, tryFillAll); + + // hand is empty, try to drain tank + } else if (canDrainSlot && heldFluid == null) { + return drainTankFromStack(fluidHandlerItem, tankFluid, tryFillAll); + + // neither is empty but tank is not full, try to fill tank + } else if (canFillSlot && tank.getFluidAmount() < tank.getCapacity() && heldFluid != null) { + return fillTankFromStack(fluidHandlerItem, heldFluid, tryFillAll); + } + + return ItemStack.EMPTY; + } + + private ItemStack fillTankFromStack(IFluidHandlerItem fluidHandler, @NotNull FluidStack heldFluid, + boolean tryFillAll) { + ItemStack heldItem = getSyncManager().getCursorItem(); + if (heldItem.isEmpty()) return ItemStack.EMPTY; + + FluidStack currentFluid = tank.getFluid(); + // Fluid type does not match + if (currentFluid != null && !currentFluid.isFluidEqual(heldFluid)) return ItemStack.EMPTY; + + int freeSpace = tank.getCapacity() - tank.getFluidAmount(); + if (freeSpace <= 0) return ItemStack.EMPTY; + + ItemStack itemStackEmptied = ItemStack.EMPTY; + int fluidAmountTaken = 0; + + FluidStack drained = fluidHandler.drain(freeSpace, true); + if (drained != null && drained.amount > 0) { + itemStackEmptied = fluidHandler.getContainer(); + fluidAmountTaken = drained.amount; + } + if (itemStackEmptied == ItemStack.EMPTY) { + return ItemStack.EMPTY; + } + + // find out how many fills we can do + // same round down behavior as drain + int additional = tryFillAll ? Math.min(freeSpace / fluidAmountTaken, heldItem.getCount()) : 1; + FluidStack copiedFluidStack = heldFluid.copy(); + copiedFluidStack.amount = fluidAmountTaken * additional; + tank.fill(copiedFluidStack, true); + + itemStackEmptied.setCount(additional); + replaceCursorItemStack(itemStackEmptied); + playSound(heldFluid, true); + return itemStackEmptied; + } + + private ItemStack drainTankFromStack(IFluidHandlerItem fluidHandler, FluidStack tankFluid, boolean tryFillAll) { + ItemStack heldItem = getSyncManager().getCursorItem(); + if (heldItem.isEmpty()) return ItemStack.EMPTY; + + ItemStack fluidContainer = fluidHandler.getContainer(); + int filled = fluidHandler.fill(tankFluid, false); + if (filled > 0) { + tank.drain(filled, true); + fluidHandler.fill(tankFluid, true); + if (tryFillAll) { + // Determine how many more items we can fill. One item is already filled. + // Integer division means it will round down, so it will only fill equivalent fluid amounts. + // For example: + // Click with 3 cells, with 2500L of fluid in the tank. + // 2 cells will be filled, and 500L will be left behind in the tank. + int additional = Math.min(heldItem.getCount(), tankFluid.amount / filled) - 1; + tank.drain(filled * additional, true); + fluidContainer.grow(additional); + } + replaceCursorItemStack(fluidContainer); + playSound(tankFluid, false); + return fluidContainer; + } + return ItemStack.EMPTY; + } + + /** + * Replace the ItemStack on the player's cursor with the passed stack. Use to replace empty cells with filled, or + * filled cells with empty. If it is not fully emptied/filled, it will place the new items into the player inventory + * instead, and shrink the held stack by the appropriate amount. + */ + private void replaceCursorItemStack(ItemStack resultStack) { + int resultStackSize = resultStack.getMaxStackSize(); + ItemStack playerStack = getSyncManager().getCursorItem(); + + if (!getSyncManager().isClient()) + syncToClient(TRY_CLICK_CONTAINER, buffer -> NetworkUtils.writeItemStack(buffer, resultStack)); + + while (resultStack.getCount() > resultStackSize) { + playerStack.shrink(resultStackSize); + addItemToPlayerInventory(resultStack.splitStack(resultStackSize)); + } + if (playerStack.getCount() == resultStack.getCount()) { + // every item on the cursor is mutated, so leave it there + getSyncManager().setCursorItem(resultStack); + } else { + // some items not mutated. Mutated items go into the inventory/world. + playerStack.shrink(resultStack.getCount()); + getSyncManager().setCursorItem(playerStack); + addItemToPlayerInventory(resultStack); + } + } + + /** Place an item into the player's inventory, or drop it in-world as an item entity if it cannot fit. */ + private void addItemToPlayerInventory(ItemStack stack) { + if (stack == null) return; + var player = getSyncManager().getPlayer(); + + if (!player.inventory.addItemStackToInventory(stack) && !player.world.isRemote) { + EntityItem dropItem = player.entityDropItem(stack, 0); + if (dropItem != null) dropItem.setPickupDelay(0); + } + } + + /** + * Play the appropriate fluid interaction sound for the fluid.
+ * Must be called on server to work correctly + **/ + private void playSound(FluidStack fluid, boolean fill) { + if (fluid == null) return; + SoundEvent soundEvent; + if (fill) { + soundEvent = fluid.getFluid().getFillSound(fluid); + } else { + soundEvent = fluid.getFluid().getEmptySound(fluid); + } + EntityPlayer player = getSyncManager().getPlayer(); + player.world.playSound(null, player.posX, player.posY + 0.5, player.posZ, + soundEvent, SoundCategory.PLAYERS, 1.0F, 1.0F); + } +} diff --git a/src/main/java/gregtech/api/util/FluidTankSwitchShim.java b/src/main/java/gregtech/api/util/FluidTankSwitchShim.java index e0210eee145..0e4c556da3a 100644 --- a/src/main/java/gregtech/api/util/FluidTankSwitchShim.java +++ b/src/main/java/gregtech/api/util/FluidTankSwitchShim.java @@ -11,14 +11,17 @@ // probably causes problems public class FluidTankSwitchShim implements IFluidTank, IFluidHandler { - IFluidTank tank; + @Nullable + private IFluidTank tank; + private static final FluidTankInfo NO_INFO = new FluidTankInfo(null, 0); + private static final IFluidTankProperties[] NO_PROPS = new IFluidTankProperties[0]; public FluidTankSwitchShim(IFluidTank tank) { changeTank(tank); } public void changeTank(IFluidTank tank) { - if (!(tank instanceof IFluidHandler)) { + if (tank != null && !(tank instanceof IFluidHandler)) { throw new IllegalArgumentException("Shim tank must be both IFluidTank and IFluidHandler!"); } this.tank = tank; @@ -27,43 +30,49 @@ public void changeTank(IFluidTank tank) { @Nullable @Override public FluidStack getFluid() { - return tank.getFluid(); + return tank == null ? null : tank.getFluid(); } @Override public int getFluidAmount() { - return tank.getFluidAmount(); + return tank == null ? 0 : tank.getFluidAmount(); } @Override public int getCapacity() { - return tank.getCapacity(); + return tank == null ? 0 : tank.getCapacity(); } @Override public FluidTankInfo getInfo() { - return tank.getInfo(); + return tank == null ? NO_INFO : tank.getInfo(); } @Override public IFluidTankProperties[] getTankProperties() { + if (tank == null) + return NO_PROPS; + return ((IFluidHandler) tank).getTankProperties(); } @Override public int fill(FluidStack resource, boolean doFill) { + if (tank == null) return 0; return ((IFluidHandler) tank).fill(resource, doFill); } @Nullable @Override public FluidStack drain(FluidStack resource, boolean doDrain) { + if (tank == null) return null; return ((IFluidHandler) tank).drain(resource, doDrain); } @Nullable @Override public FluidStack drain(int maxDrain, boolean doDrain) { + if (tank == null) return null; return tank.drain(maxDrain, doDrain); } } diff --git a/src/main/java/gregtech/api/util/VirtualTankRegistry.java b/src/main/java/gregtech/api/util/VirtualTankRegistry.java deleted file mode 100644 index f43809d7eb3..00000000000 --- a/src/main/java/gregtech/api/util/VirtualTankRegistry.java +++ /dev/null @@ -1,338 +0,0 @@ -package gregtech.api.util; - -import gregtech.api.GTValues; - -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.world.World; -import net.minecraft.world.storage.MapStorage; -import net.minecraft.world.storage.WorldSavedData; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.FluidTankInfo; -import net.minecraftforge.fluids.IFluidTank; -import net.minecraftforge.fluids.capability.IFluidHandler; -import net.minecraftforge.fluids.capability.IFluidTankProperties; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class VirtualTankRegistry extends WorldSavedData { - - private static final int DEFAULT_CAPACITY = 64000; // 64B - private static final String DATA_ID = GTValues.MODID + ".vtank_data"; - - protected static Map> tankMap = new HashMap<>(); - - public VirtualTankRegistry() { - super(DATA_ID); - } - - // for some reason, MapStorage throws an error if this constructor is not present - @SuppressWarnings("unused") - public VirtualTankRegistry(String name) { - super(name); - } - - /** - * Retrieves a tank from the registry - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - * @return The tank object - */ - public static IFluidTank getTank(String key, UUID uuid) { - return tankMap.get(uuid).get(key); - } - - /** - * @return the internal Map of tanks. - * Do not use to modify the map! - */ - public static Map> getTankMap() { - return tankMap; - } - - /** - * Retrieves a tank from the registry, creating it if it does not exist - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - * @param capacity The initial capacity of the tank - * @return The tank object - */ - public static IFluidTank getTankCreate(String key, UUID uuid, int capacity) { - if (!tankMap.containsKey(uuid) || !tankMap.get(uuid).containsKey(key)) { - addTank(key, uuid, capacity); - } - return getTank(key, uuid); - } - - /** - * Retrieves a tank from the registry, creating it with {@link #DEFAULT_CAPACITY the default capacity} if it does - * not exist - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - * @return The tank object - */ - public static IFluidTank getTankCreate(String key, UUID uuid) { - return getTankCreate(key, uuid, DEFAULT_CAPACITY); - } - - /** - * Adds a tank to the registry - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - * @param capacity The initial capacity of the tank - */ - public static void addTank(String key, UUID uuid, int capacity) { - if (tankMap.containsKey(uuid) && tankMap.get(uuid).containsKey(key)) { - GTLog.logger.warn("Overwriting virtual tank " + key + "/" + (uuid == null ? "null" : uuid.toString()) + - ", this might cause fluid loss!"); - } else if (!tankMap.containsKey(uuid)) { - tankMap.put(uuid, new HashMap<>()); - } - tankMap.get(uuid).put(key, new VirtualTank(capacity)); - } - - /** - * Adds a tank to the registry with {@link #DEFAULT_CAPACITY the default capacity} - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - */ - public static void addTank(String key, UUID uuid) { - addTank(key, uuid, DEFAULT_CAPACITY); - } - - /** - * Removes a tank from the registry. Use with caution! - * - * @param key The name of the tank - * @param uuid The uuid of the player the tank is private to, or null if the tank is public - * @param removeFluid Whether to remove the tank if it has fluid in it - */ - public static void delTank(String key, UUID uuid, boolean removeFluid) { - if (tankMap.containsKey(uuid) && tankMap.get(uuid).containsKey(key)) { - if (removeFluid || tankMap.get(uuid).get(key).getFluidAmount() <= 0) { - tankMap.get(uuid).remove(key); - if (tankMap.get(uuid).size() == 0) { - tankMap.remove(uuid); - } - } - } else { - GTLog.logger.warn("Attempted to delete tank " + key + "/" + (uuid == null ? "null" : uuid.toString()) + - ", which does not exist!"); - } - } - - /** - * To be called on server stopped event - */ - public static void clearMaps() { - tankMap.clear(); - } - - @Override - public void readFromNBT(NBTTagCompound nbt) { - if (nbt.hasKey("Public")) { - NBTTagCompound publicTanks = nbt.getCompoundTag("Public"); - for (String key : publicTanks.getKeySet()) { - NBTTagCompound tankCompound = publicTanks.getCompoundTag(key); - VirtualTankRegistry.addTank(key, null, tankCompound.getInteger("Capacity")); - if (!tankCompound.hasKey("Empty")) { - VirtualTankRegistry.getTank(key, null).fill(FluidStack.loadFluidStackFromNBT(tankCompound), true); - } - } - } - if (nbt.hasKey("Private")) { - NBTTagCompound privateTankUUIDs = nbt.getCompoundTag("Private"); - for (String uuidStr : privateTankUUIDs.getKeySet()) { - UUID uuid = UUID.fromString(uuidStr); - NBTTagCompound privateTanks = privateTankUUIDs.getCompoundTag(uuidStr); - for (String key : privateTanks.getKeySet()) { - NBTTagCompound tankCompound = privateTanks.getCompoundTag(key); - VirtualTankRegistry.addTank(key, uuid, tankCompound.getInteger("Capacity")); - if (!tankCompound.hasKey("Empty")) { - VirtualTankRegistry.getTank(key, uuid).fill(FluidStack.loadFluidStackFromNBT(tankCompound), - true); - } - } - } - } - } - - @NotNull - @Override - public NBTTagCompound writeToNBT(NBTTagCompound compound) { - compound.setTag("Private", new NBTTagCompound()); - tankMap.forEach((uuid, map) -> { - NBTTagCompound mapCompound = new NBTTagCompound(); - map.forEach((key, tank) -> { - if (tank.getFluid() != null || tank.getCapacity() != DEFAULT_CAPACITY) { - NBTTagCompound tankCompound = new NBTTagCompound(); - tankCompound.setInteger("Capacity", tank.getCapacity()); - if (tank.getFluid() != null) { - tank.getFluid().writeToNBT(tankCompound); - } else { - tankCompound.setString("Empty", ""); - } - mapCompound.setTag(key, tankCompound); - } - }); - if (mapCompound.getSize() > 0) { - if (uuid == null) { - compound.setTag("Public", mapCompound); - } else { - compound.getCompoundTag("Private").setTag(uuid.toString(), mapCompound); - } - } - }); - return compound; - } - - @Override - public boolean isDirty() { - // can't think of a good way to mark dirty other than always - return true; - } - - /** - * To be called on world load event - */ - public static void initializeStorage(World world) { - MapStorage storage = world.getMapStorage(); - VirtualTankRegistry instance = (VirtualTankRegistry) storage.getOrLoadData(VirtualTankRegistry.class, DATA_ID); - - if (instance == null) { - instance = new VirtualTankRegistry(); - storage.setData(DATA_ID, instance); - } - } - - private static class VirtualTank implements IFluidTank, IFluidHandler { - - @Nullable - protected FluidStack fluid; - protected int capacity; - protected IFluidTankProperties[] tankProperties; - - public VirtualTank(int capacity) { - this.capacity = capacity; - } - - @Nullable - @Override - public FluidStack getFluid() { - return this.fluid; - } - - @Override - public int getFluidAmount() { - return this.fluid == null ? 0 : this.fluid.amount; - } - - @Override - public int getCapacity() { - return this.capacity; - } - - @Override - public FluidTankInfo getInfo() { - return new FluidTankInfo(this); - } - - @Override - public IFluidTankProperties[] getTankProperties() { - if (this.tankProperties == null) { - this.tankProperties = new IFluidTankProperties[] { new VirtualTankProperties(this) }; - } - return this.tankProperties; - } - - @Override - public int fill(FluidStack fluidStack, boolean doFill) { - if (fluidStack == null || fluidStack.amount <= 0 || - (this.fluid != null && !fluidStack.isFluidEqual(this.fluid))) - return 0; - - int fillAmt = Math.min(fluidStack.amount, this.capacity - this.getFluidAmount()); - if (doFill) { - if (this.fluid == null) { - this.fluid = new FluidStack(fluidStack, fillAmt); - } else { - this.fluid.amount += fillAmt; - } - } - return fillAmt; - } - - @Nullable - @Override - public FluidStack drain(FluidStack resource, boolean doDrain) { - return resource == null || !resource.isFluidEqual(this.fluid) ? null : drain(resource.amount, doDrain); - } - - @Nullable - @Override - public FluidStack drain(int amount, boolean doDrain) { - if (this.fluid == null || amount <= 0) - return null; - - int drainAmt = Math.min(this.getFluidAmount(), amount); - FluidStack drainedFluid = new FluidStack(fluid, drainAmt); - if (doDrain) { - this.fluid.amount -= drainAmt; - if (this.fluid.amount <= 0) { - this.fluid = null; - } - } - return drainedFluid; - } - - private static class VirtualTankProperties implements IFluidTankProperties { - - protected final VirtualTank tank; - - private VirtualTankProperties(VirtualTank tank) { - this.tank = tank; - } - - @Nullable - @Override - public FluidStack getContents() { - FluidStack contents = tank.getFluid(); - return contents == null ? null : contents.copy(); - } - - @Override - public int getCapacity() { - return tank.getCapacity(); - } - - @Override - public boolean canFill() { - return true; - } - - @Override - public boolean canDrain() { - return true; - } - - @Override - public boolean canFillFluidType(FluidStack fluidStack) { - return true; - } - - @Override - public boolean canDrainFluidType(FluidStack fluidStack) { - return true; - } - } - } -} diff --git a/src/main/java/gregtech/api/util/virtualregistry/EntryTypes.java b/src/main/java/gregtech/api/util/virtualregistry/EntryTypes.java new file mode 100644 index 00000000000..6324958b8c7 --- /dev/null +++ b/src/main/java/gregtech/api/util/virtualregistry/EntryTypes.java @@ -0,0 +1,66 @@ +package gregtech.api.util.virtualregistry; + +import gregtech.api.util.GTLog; +import gregtech.api.util.virtualregistry.entries.VirtualTank; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ResourceLocation; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.function.Supplier; + +import static gregtech.api.util.GTUtility.gregtechId; + +public final class EntryTypes { + + private static final Map> TYPES_MAP = new Object2ObjectOpenHashMap<>(); + public static final EntryTypes ENDER_FLUID = addEntryType(gregtechId("ender_fluid"), VirtualTank::new); + // ENDER_ITEM("ender_item", null), + // ENDER_ENERGY("ender_energy", null), + // ENDER_REDSTONE("ender_redstone", null); + private final ResourceLocation location; + private final Supplier factory; + + private EntryTypes(ResourceLocation location, Supplier supplier) { + this.location = location; + this.factory = supplier; + } + + public T createInstance(NBTTagCompound nbt) { + var entry = createInstance(); + entry.deserializeNBT(nbt); + return entry; + } + + public T createInstance() { + return factory.get(); + } + + @Override + public String toString() { + return this.location.toString(); + } + + @Nullable + public static EntryTypes fromString(String name) { + return TYPES_MAP.getOrDefault(gregtechId(name), null); + } + + @Nullable + public static EntryTypes fromLocation(String location) { + return TYPES_MAP.getOrDefault(new ResourceLocation(location), null); + } + + public static EntryTypes addEntryType(ResourceLocation location, Supplier supplier) { + var type = new EntryTypes<>(location, supplier); + if (!TYPES_MAP.containsKey(location)) { + TYPES_MAP.put(location, type); + } else { + GTLog.logger.warn("Entry \"{}\" is already registered!", location); + } + return type; + } +} diff --git a/src/main/java/gregtech/api/util/virtualregistry/VirtualEnderRegistry.java b/src/main/java/gregtech/api/util/virtualregistry/VirtualEnderRegistry.java new file mode 100644 index 00000000000..3d8ccd78224 --- /dev/null +++ b/src/main/java/gregtech/api/util/virtualregistry/VirtualEnderRegistry.java @@ -0,0 +1,175 @@ +package gregtech.api.util.virtualregistry; + +import gregtech.api.GTValues; +import gregtech.api.util.GTLog; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.storage.MapStorage; +import net.minecraft.world.storage.WorldSavedData; +import net.minecraftforge.fluids.IFluidTank; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; + +@SuppressWarnings("SameParameterValue") +public class VirtualEnderRegistry extends WorldSavedData { + + private static final String DATA_ID = GTValues.MODID + ".virtual_entry_data"; + private static final String OLD_DATA_ID = GTValues.MODID + ".vtank_data"; + private static final String PUBLIC_KEY = "Public"; + private static final String PRIVATE_KEY = "Private"; + private static final Map VIRTUAL_REGISTRIES = new HashMap<>(); + + public VirtualEnderRegistry(String name) { + super(name); + } + + public static T getEntry(@Nullable UUID owner, EntryTypes type, String name) { + return getRegistry(owner).getEntry(type, name); + } + + public static void addEntry(@Nullable UUID owner, String name, VirtualEntry entry) { + getRegistry(owner).addEntry(name, entry); + } + + public static boolean hasEntry(@Nullable UUID owner, EntryTypes type, String name) { + return getRegistry(owner).contains(type, name); + } + + public static @NotNull T getOrCreateEntry(@Nullable UUID owner, EntryTypes type, + String name) { + if (!hasEntry(owner, type, name)) + addEntry(owner, name, type.createInstance()); + + return getEntry(owner, type, name); + } + + /** + * Removes an entry from the registry. Use with caution! + * + * @param owner The uuid of the player the entry is private to, or null if the entry is public + * @param type Type of the registry to remove from + * @param name The name of the entry + */ + public static void deleteEntry(@Nullable UUID owner, EntryTypes type, String name) { + var registry = getRegistry(owner); + if (registry.contains(type, name)) { + registry.deleteEntry(type, name); + return; + } + GTLog.logger.warn("Attempted to delete {} entry {} of type {}, which does not exist", + owner == null ? "public" : String.format("private [%s]", owner), name, type); + } + + public static void deleteEntry(@Nullable UUID owner, EntryTypes type, String name, + Predicate shouldDelete) { + T entry = getEntry(owner, type, name); + if (entry != null && shouldDelete.test(entry)) + deleteEntry(owner, type, name); + } + + public static Set getEntryNames(UUID owner, EntryTypes type) { + return getRegistry(owner).getEntryNames(type); + } + + /** + * To be called on server stopped event + */ + public static void clearMaps() { + VIRTUAL_REGISTRIES.clear(); + } + + private static VirtualRegistryMap getRegistry(UUID owner) { + return VIRTUAL_REGISTRIES.computeIfAbsent(owner, key -> new VirtualRegistryMap()); + } + + // remove if tank app is removed + public static Map> createTankMap() { + Map> map = new HashMap<>(); + for (var uuid : VIRTUAL_REGISTRIES.keySet()) { + map.put(uuid, new HashMap<>()); + for (var name : getEntryNames(uuid, EntryTypes.ENDER_FLUID)) { + map.get(uuid).put(name, getEntry(uuid, EntryTypes.ENDER_FLUID, name)); + } + } + return map; + } + + @Override + public final void readFromNBT(NBTTagCompound nbt) { + if (nbt.hasKey(PUBLIC_KEY)) { + VIRTUAL_REGISTRIES.put(null, new VirtualRegistryMap(nbt.getCompoundTag(PUBLIC_KEY))); + } + if (nbt.hasKey(PRIVATE_KEY)) { + NBTTagCompound privateEntries = nbt.getCompoundTag(PRIVATE_KEY); + for (String owner : privateEntries.getKeySet()) { + var privateMap = privateEntries.getCompoundTag(owner); + VIRTUAL_REGISTRIES.put(UUID.fromString(owner), new VirtualRegistryMap(privateMap)); + } + } + } + + @NotNull + @Override + public final NBTTagCompound writeToNBT(@NotNull NBTTagCompound tag) { + var privateTag = new NBTTagCompound(); + for (var owner : VIRTUAL_REGISTRIES.keySet()) { + var mapTag = VIRTUAL_REGISTRIES.get(owner).serializeNBT(); + if (owner != null) { + privateTag.setTag(owner.toString(), mapTag); + } else { + tag.setTag(PUBLIC_KEY, mapTag); + } + } + tag.setTag(PRIVATE_KEY, privateTag); + return tag; + } + + @Override + public boolean isDirty() { + // can't think of a good way to mark dirty other than always + return true; + } + + /** + * To be called on world load event + */ + @SuppressWarnings("DataFlowIssue") + public static void initializeStorage(World world) { + MapStorage storage = world.getMapStorage(); + + VirtualEnderRegistry instance = (VirtualEnderRegistry) storage.getOrLoadData(VirtualEnderRegistry.class, + DATA_ID); + VirtualEnderRegistry old = (VirtualEnderRegistry) storage.getOrLoadData(VirtualEnderRegistry.class, + OLD_DATA_ID); + + if (instance == null) { + instance = new VirtualEnderRegistry(DATA_ID); + storage.setData(DATA_ID, instance); + } + + if (old != null) { + instance.readFromNBT(old.serializeNBT()); + var file = world.getSaveHandler().getMapFileFromName(OLD_DATA_ID); + var split = file.getName().split("\\."); + var stringBuilder = new StringBuilder(split[0]) + .append('.') + .append(split[1]) + .append(".backup") + .append('.') + .append(split[2]); + if (file.renameTo(new File(file.getParent(), stringBuilder.toString()))) { + file.deleteOnExit(); + GTLog.logger.warn("Moved Virtual Tank Data to new format, created backup!"); + } + } + } +} diff --git a/src/main/java/gregtech/api/util/virtualregistry/VirtualEntry.java b/src/main/java/gregtech/api/util/virtualregistry/VirtualEntry.java new file mode 100644 index 00000000000..2775069f0e3 --- /dev/null +++ b/src/main/java/gregtech/api/util/virtualregistry/VirtualEntry.java @@ -0,0 +1,79 @@ +package gregtech.api.util.virtualregistry; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.INBTSerializable; + +import org.jetbrains.annotations.NotNull; + +public abstract class VirtualEntry implements INBTSerializable { + + public static final String DEFAULT_COLOR = "FFFFFFFF"; + protected static final String COLOR_KEY = "color"; + protected static final String DESC_KEY = "description"; + + private int color = 0xFFFFFFFF; + private String colorStr = DEFAULT_COLOR; + private @NotNull String description = ""; + + public abstract EntryTypes getType(); + + public String getColorStr() { + return colorStr; + } + + public int getColor() { + return this.color; + } + + public void setColor(String color) { + this.color = parseColor(color); + this.colorStr = color.toUpperCase(); + } + + public void setColor(int color) { + setColor(Integer.toHexString(color)); + } + + private int parseColor(String s) { + // stupid java not having actual unsigned ints + long tmp = Long.parseLong(s, 16); + if (tmp > 0x7FFFFFFF) { + tmp -= 0x100000000L; + } + return (int) tmp; + } + + public @NotNull String getDescription() { + return this.description; + } + + public void setDescription(@NotNull String desc) { + this.description = desc; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VirtualEntry other)) return false; + return this.getType() == other.getType() && + this.color == other.color; + } + + @Override + public NBTTagCompound serializeNBT() { + var tag = new NBTTagCompound(); + tag.setString(COLOR_KEY, this.colorStr); + + if (description != null && !description.isEmpty()) + tag.setString(DESC_KEY, this.description); + + return tag; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + setColor(nbt.getString(COLOR_KEY)); + + if (nbt.hasKey(DESC_KEY)) + setDescription(nbt.getString(DESC_KEY)); + } +} diff --git a/src/main/java/gregtech/api/util/virtualregistry/VirtualRegistryMap.java b/src/main/java/gregtech/api/util/virtualregistry/VirtualRegistryMap.java new file mode 100644 index 00000000000..18a2b9a81e8 --- /dev/null +++ b/src/main/java/gregtech/api/util/virtualregistry/VirtualRegistryMap.java @@ -0,0 +1,87 @@ +package gregtech.api.util.virtualregistry; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.INBTSerializable; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class VirtualRegistryMap implements INBTSerializable { + + private final Map, Map> registryMap = new HashMap<>(); + + public VirtualRegistryMap(NBTTagCompound tag) { + deserializeNBT(tag); + } + + public VirtualRegistryMap() {} + + @SuppressWarnings("unchecked") + public @Nullable T getEntry(EntryTypes type, String name) { + if (!contains(type, name)) + return null; + + return (T) registryMap.get(type).get(name); + } + + public void addEntry(String name, VirtualEntry entry) { + registryMap.computeIfAbsent(entry.getType(), k -> new HashMap<>()) + .put(name, entry); + } + + public boolean contains(EntryTypes type, String name) { + if (!registryMap.containsKey(type)) + return false; + + return registryMap.get(type).containsKey(name); + } + + public void deleteEntry(EntryTypes type, String name) { + registryMap.get(type).remove(name); + } + + public void clear() { + registryMap.clear(); + } + + public Set getEntryNames(EntryTypes type) { + return registryMap.get(type).keySet(); + } + + @Override + public @NotNull NBTTagCompound serializeNBT() { + var tag = new NBTTagCompound(); + for (var type : registryMap.keySet()) { + var entriesTag = new NBTTagCompound(); + var entries = registryMap.get(type); + for (var name : entries.keySet()) { + entriesTag.setTag(name, entries.get(name).serializeNBT()); + } + tag.setTag(type.toString(), entriesTag); + } + return tag; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + for (var entryType : nbt.getKeySet()) { + EntryTypes type; + if (entryType.contains(":")) { + type = EntryTypes.fromLocation(entryType); + } else { + type = EntryTypes.fromString(entryType); + } + if (type == null) continue; + + var virtualEntries = nbt.getCompoundTag(entryType); + for (var name : virtualEntries.getKeySet()) { + var entry = virtualEntries.getCompoundTag(name); + addEntry(name, type.createInstance(entry)); + } + } + } +} diff --git a/src/main/java/gregtech/api/util/virtualregistry/entries/VirtualTank.java b/src/main/java/gregtech/api/util/virtualregistry/entries/VirtualTank.java new file mode 100644 index 00000000000..3ad02e5969c --- /dev/null +++ b/src/main/java/gregtech/api/util/virtualregistry/entries/VirtualTank.java @@ -0,0 +1,180 @@ +package gregtech.api.util.virtualregistry.entries; + +import gregtech.api.util.virtualregistry.EntryTypes; +import gregtech.api.util.virtualregistry.VirtualEntry; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidTankInfo; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidTankProperties; + +import org.jetbrains.annotations.Nullable; + +public class VirtualTank extends VirtualEntry implements IFluidTank, IFluidHandler { + + protected static final String CAPACITY_KEY = "capacity"; + protected static final String FLUID_KEY = "fluid"; + private static final int DEFAULT_CAPACITY = 64000; // 64B + + @Nullable + private FluidStack fluidStack = null; + private int capacity; + private final IFluidTankProperties[] props = new IFluidTankProperties[] { + createProperty(this) + }; + + public VirtualTank(int capacity) { + this.capacity = capacity; + } + + public VirtualTank() { + this(DEFAULT_CAPACITY); + } + + @Override + public EntryTypes getType() { + return EntryTypes.ENDER_FLUID; + } + + @Override + public FluidStack getFluid() { + return this.fluidStack; + } + + public void setFluid(FluidStack fluid) { + this.fluidStack = fluid; + } + + @Override + public int getFluidAmount() { + return fluidStack == null ? 0 : fluidStack.amount; + } + + @Override + public int getCapacity() { + return this.capacity; + } + + @Override + public FluidTankInfo getInfo() { + return new FluidTankInfo(this); + } + + @Override + public IFluidTankProperties[] getTankProperties() { + return this.props; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VirtualTank other)) return false; + if (this.fluidStack == null && other.fluidStack == null) + return super.equals(o); + if (this.fluidStack == null || other.fluidStack == null) + return false; + if (this.fluidStack.isFluidStackIdentical(other.fluidStack)) + return super.equals(o); + + return false; + } + + @Override + public NBTTagCompound serializeNBT() { + var tag = super.serializeNBT(); + tag.setInteger(CAPACITY_KEY, this.capacity); + + if (this.fluidStack != null) + tag.setTag(FLUID_KEY, this.fluidStack.writeToNBT(new NBTTagCompound())); + + return tag; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + super.deserializeNBT(nbt); + this.capacity = nbt.getInteger(CAPACITY_KEY); + + if (nbt.hasKey(FLUID_KEY)) + setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompoundTag(FLUID_KEY))); + } + + @Override + public int fill(FluidStack fluidStack, boolean doFill) { + if (fluidStack == null || fluidStack.amount <= 0 || + (this.fluidStack != null && !fluidStack.isFluidEqual(this.fluidStack))) + return 0; + + int fillAmt = Math.min(fluidStack.amount, getCapacity() - this.getFluidAmount()); + + if (doFill) { + if (this.fluidStack == null) { + this.fluidStack = new FluidStack(fluidStack, fillAmt); + } else { + this.fluidStack.amount += fillAmt; + } + } + return fillAmt; + } + + @Nullable + @Override + public FluidStack drain(FluidStack resource, boolean doDrain) { + return resource == null || !resource.isFluidEqual(this.fluidStack) ? null : drain(resource.amount, doDrain); + } + + @Nullable + @Override + public FluidStack drain(int amount, boolean doDrain) { + if (this.fluidStack == null || amount <= 0) + return null; + + int drainAmt = Math.min(this.getFluidAmount(), amount); + FluidStack drainedFluid = new FluidStack(this.fluidStack, drainAmt); + if (doDrain) { + this.fluidStack.amount -= drainAmt; + if (this.fluidStack.amount <= 0) { + this.fluidStack = null; + } + } + return drainedFluid; + } + + private static IFluidTankProperties createProperty(VirtualTank tank) { + return new IFluidTankProperties() { + + @Nullable + @Override + public FluidStack getContents() { + FluidStack contents = tank.getFluid(); + return contents == null ? null : contents.copy(); + } + + @Override + public int getCapacity() { + return tank.getCapacity(); + } + + @Override + public boolean canFill() { + return true; + } + + @Override + public boolean canDrain() { + return true; + } + + @Override + public boolean canFillFluidType(FluidStack fluidStack) { + return true; + } + + @Override + public boolean canDrainFluidType(FluidStack fluidStack) { + return true; + } + }; + } +} diff --git a/src/main/java/gregtech/common/EventHandlers.java b/src/main/java/gregtech/common/EventHandlers.java index 8fa23091b99..ecbe6b16097 100644 --- a/src/main/java/gregtech/common/EventHandlers.java +++ b/src/main/java/gregtech/common/EventHandlers.java @@ -13,7 +13,7 @@ import gregtech.api.util.CapesRegistry; import gregtech.api.util.GTUtility; import gregtech.api.util.Mods; -import gregtech.api.util.VirtualTankRegistry; +import gregtech.api.util.virtualregistry.VirtualEnderRegistry; import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinSaveData; import gregtech.common.entities.EntityGTExplosive; import gregtech.common.items.MetaItems; @@ -327,7 +327,7 @@ public static void onPlayerTickClient(TickEvent.PlayerTickEvent event) { @SubscribeEvent public static void onWorldLoadEvent(WorldEvent.Load event) { - VirtualTankRegistry.initializeStorage(event.getWorld()); + VirtualEnderRegistry.initializeStorage(event.getWorld()); CapesRegistry.checkAdvancements(event.getWorld()); } diff --git a/src/main/java/gregtech/common/covers/CoverBehaviors.java b/src/main/java/gregtech/common/covers/CoverBehaviors.java index 78413327163..355fe3b0dff 100644 --- a/src/main/java/gregtech/common/covers/CoverBehaviors.java +++ b/src/main/java/gregtech/common/covers/CoverBehaviors.java @@ -8,6 +8,7 @@ import gregtech.api.util.GTLog; import gregtech.client.renderer.texture.Textures; import gregtech.common.covers.detector.*; +import gregtech.common.covers.ender.CoverEnderFluidLink; import gregtech.common.items.MetaItems; import gregtech.common.items.behaviors.CoverDigitalInterfaceWirelessPlaceBehaviour; diff --git a/src/main/java/gregtech/common/covers/CoverEnderFluidLink.java b/src/main/java/gregtech/common/covers/CoverEnderFluidLink.java deleted file mode 100644 index a674b6fbd54..00000000000 --- a/src/main/java/gregtech/common/covers/CoverEnderFluidLink.java +++ /dev/null @@ -1,348 +0,0 @@ -package gregtech.common.covers; - -import gregtech.api.capability.GregtechTileCapabilities; -import gregtech.api.capability.IControllable; -import gregtech.api.cover.CoverBase; -import gregtech.api.cover.CoverDefinition; -import gregtech.api.cover.CoverWithUI; -import gregtech.api.cover.CoverableView; -import gregtech.api.mui.GTGuiTextures; -import gregtech.api.mui.GTGuis; -import gregtech.api.util.FluidTankSwitchShim; -import gregtech.api.util.GTTransferUtils; -import gregtech.api.util.VirtualTankRegistry; -import gregtech.client.renderer.texture.Textures; -import gregtech.common.covers.filter.FluidFilterContainer; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.BlockRenderLayer; -import net.minecraft.util.EnumActionResult; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.ITickable; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.fluids.capability.IFluidHandler; - -import codechicken.lib.raytracer.CuboidRayTraceResult; -import codechicken.lib.render.CCRenderState; -import codechicken.lib.render.pipeline.IVertexOperation; -import codechicken.lib.vec.Cuboid6; -import codechicken.lib.vec.Matrix4; -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.drawable.DynamicDrawable; -import com.cleanroommc.modularui.drawable.Rectangle; -import com.cleanroommc.modularui.factory.SidedPosGuiData; -import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.utils.Color; -import com.cleanroommc.modularui.value.sync.BooleanSyncValue; -import com.cleanroommc.modularui.value.sync.EnumSyncValue; -import com.cleanroommc.modularui.value.sync.FluidSlotSyncHandler; -import com.cleanroommc.modularui.value.sync.PanelSyncManager; -import com.cleanroommc.modularui.value.sync.StringSyncValue; -import com.cleanroommc.modularui.widgets.FluidSlot; -import com.cleanroommc.modularui.widgets.ToggleButton; -import com.cleanroommc.modularui.widgets.layout.Column; -import com.cleanroommc.modularui.widgets.layout.Row; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; -import java.util.regex.Pattern; - -public class CoverEnderFluidLink extends CoverBase implements CoverWithUI, ITickable, IControllable { - - public static final int TRANSFER_RATE = 8000; // mB/t - private static final Pattern COLOR_INPUT_PATTERN = Pattern.compile("[0-9a-fA-F]*"); - - protected CoverPump.PumpMode pumpMode = CoverPump.PumpMode.IMPORT; - private int color = 0xFFFFFFFF; - private UUID playerUUID = null; - private boolean isPrivate = false; - private boolean workingEnabled = true; - private boolean ioEnabled = false; - private String tempColorStr; - private boolean isColorTemp; - private final FluidTankSwitchShim linkedTank; - protected final FluidFilterContainer fluidFilter; - - protected CoverEnderFluidLink(@NotNull CoverDefinition definition, @NotNull CoverableView coverableView, - @NotNull EnumFacing attachedSide) { - super(definition, coverableView, attachedSide); - this.linkedTank = new FluidTankSwitchShim(VirtualTankRegistry.getTankCreate(makeTankName(), null)); - this.fluidFilter = new FluidFilterContainer(this); - } - - private String makeTankName() { - return "EFLink#" + Integer.toHexString(this.color).toUpperCase(); - } - - private UUID getTankUUID() { - return isPrivate ? playerUUID : null; - } - - public FluidFilterContainer getFluidFilterContainer() { - return this.fluidFilter; - } - - public boolean isIOEnabled() { - return this.ioEnabled; - } - - @Override - public boolean canAttach(@NotNull CoverableView coverable, @NotNull EnumFacing side) { - return coverable.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side); - } - - @Override - public void renderCover(@NotNull CCRenderState renderState, @NotNull Matrix4 translation, - IVertexOperation[] pipeline, @NotNull Cuboid6 plateBox, @NotNull BlockRenderLayer layer) { - Textures.ENDER_FLUID_LINK.renderSided(getAttachedSide(), plateBox, renderState, pipeline, translation); - } - - @Override - public @NotNull EnumActionResult onScrewdriverClick(@NotNull EntityPlayer playerIn, @NotNull EnumHand hand, - @NotNull CuboidRayTraceResult hitResult) { - if (!getWorld().isRemote) { - openUI((EntityPlayerMP) playerIn); - } - return EnumActionResult.SUCCESS; - } - - @Override - public void onAttachment(@NotNull CoverableView coverableView, @NotNull EnumFacing side, - @Nullable EntityPlayer player, @NotNull ItemStack itemStack) { - super.onAttachment(coverableView, side, player, itemStack); - if (player != null) { - this.playerUUID = player.getUniqueID(); - } - } - - @Override - public void onRemoval() { - dropInventoryContents(fluidFilter); - } - - @Override - public void update() { - if (workingEnabled && ioEnabled) { - transferFluids(); - } - } - - protected void transferFluids() { - IFluidHandler fluidHandler = getCoverableView().getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, - getAttachedSide()); - if (fluidHandler == null) return; - if (pumpMode == CoverPump.PumpMode.IMPORT) { - GTTransferUtils.transferFluids(fluidHandler, linkedTank, TRANSFER_RATE, fluidFilter::test); - } else if (pumpMode == CoverPump.PumpMode.EXPORT) { - GTTransferUtils.transferFluids(linkedTank, fluidHandler, TRANSFER_RATE, fluidFilter::test); - } - } - - public void setPumpMode(CoverPump.PumpMode pumpMode) { - this.pumpMode = pumpMode; - markDirty(); - } - - public CoverPump.PumpMode getPumpMode() { - return pumpMode; - } - - @Override - public void openUI(EntityPlayerMP player) { - CoverWithUI.super.openUI(player); - isColorTemp = false; - } - - @Override - public boolean usesMui2() { - return true; - } - - @Override - public ModularPanel buildUI(SidedPosGuiData guiData, PanelSyncManager guiSyncManager) { - var panel = GTGuis.createPanel(this, 176, 192); - - getFluidFilterContainer().setMaxTransferSize(1); - - return panel.child(CoverWithUI.createTitleRow(getPickItem())) - .child(createWidgets(panel, guiSyncManager)) - .bindPlayerInventory(); - } - - protected Column createWidgets(ModularPanel panel, PanelSyncManager syncManager) { - var isPrivate = new BooleanSyncValue(this::isPrivate, this::setPrivate); - isPrivate.updateCacheFromSource(true); - - var color = new StringSyncValue(this::getColorStr, this::updateColor); - color.updateCacheFromSource(true); - - var pumpMode = new EnumSyncValue<>(CoverPump.PumpMode.class, this::getPumpMode, this::setPumpMode); - syncManager.syncValue("pump_mode", pumpMode); - pumpMode.updateCacheFromSource(true); - - var ioEnabled = new BooleanSyncValue(this::isIOEnabled, this::setIoEnabled); - - var fluidTank = new FluidSlotSyncHandler(this.linkedTank); - fluidTank.updateCacheFromSource(true); - - return new Column().coverChildrenHeight().top(24) - .margin(7, 0).widthRel(1f) - .child(new Row().marginBottom(2) - .coverChildrenHeight() - .child(new ToggleButton() - .tooltip(tooltip -> tooltip.setAutoUpdate(true)) - .background(GTGuiTextures.PRIVATE_MODE_BUTTON[0]) - .hoverBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[0]) - .selectedBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[1]) - .selectedHoverBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[1]) - .tooltipBuilder(tooltip -> tooltip.addLine(IKey.lang(this.isPrivate ? - "cover.ender_fluid_link.private.tooltip.enabled" : - "cover.ender_fluid_link.private.tooltip.disabled"))) - .marginRight(2) - .value(isPrivate)) - .child(new DynamicDrawable(() -> new Rectangle() - .setColor(this.color) - .asIcon().size(16)) - .asWidget() - .background(GTGuiTextures.SLOT) - .size(18).marginRight(2)) - .child(new TextFieldWidget().height(18) - .value(color) - .setValidator(s -> { - if (s.length() != 8) { - return color.getStringValue(); - } - return s; - }) - .setPattern(COLOR_INPUT_PATTERN) - .widthRel(0.5f).marginRight(2)) - .child(new FluidSlot().size(18) - .syncHandler(fluidTank))) - .child(new Row().marginBottom(2) - .coverChildrenHeight() - .child(new ToggleButton() - .value(ioEnabled) - .overlay(IKey.dynamic(() -> IKey.lang(this.ioEnabled ? - "behaviour.soft_hammer.enabled" : - "behaviour.soft_hammer.disabled").get()) - .color(Color.WHITE.darker(1))) - .widthRel(0.6f) - .left(0))) - .child(getFluidFilterContainer().initUI(panel, syncManager)) - .child(new EnumRowBuilder<>(CoverPump.PumpMode.class) - .value(pumpMode) - .overlay(GTGuiTextures.CONVEYOR_MODE_OVERLAY) - .lang("cover.pump.mode") - .build()); - } - - public void updateColor(String str) { - if (str.length() == 8) { - isColorTemp = false; - // stupid java not having actual unsigned ints - long tmp = Long.parseLong(str, 16); - if (tmp > 0x7FFFFFFF) { - tmp -= 0x100000000L; - } - this.color = (int) tmp; - updateTankLink(); - } else { - tempColorStr = str; - isColorTemp = true; - } - } - - public String getColorStr() { - return isColorTemp ? tempColorStr : Integer.toHexString(this.color).toUpperCase(); - } - - public void updateTankLink() { - this.linkedTank.changeTank(VirtualTankRegistry.getTankCreate(makeTankName(), getTankUUID())); - markDirty(); - } - - @Override - public void writeToNBT(NBTTagCompound tagCompound) { - super.writeToNBT(tagCompound); - tagCompound.setInteger("Frequency", color); - tagCompound.setInteger("PumpMode", pumpMode.ordinal()); - tagCompound.setBoolean("WorkingAllowed", workingEnabled); - tagCompound.setBoolean("IOAllowed", ioEnabled); - tagCompound.setBoolean("Private", isPrivate); - tagCompound.setString("PlacedUUID", playerUUID.toString()); - tagCompound.setTag("Filter", fluidFilter.serializeNBT()); - } - - @Override - public void readFromNBT(NBTTagCompound tagCompound) { - super.readFromNBT(tagCompound); - this.color = tagCompound.getInteger("Frequency"); - this.pumpMode = CoverPump.PumpMode.values()[tagCompound.getInteger("PumpMode")]; - this.workingEnabled = tagCompound.getBoolean("WorkingAllowed"); - this.ioEnabled = tagCompound.getBoolean("IOAllowed"); - this.isPrivate = tagCompound.getBoolean("Private"); - this.playerUUID = UUID.fromString(tagCompound.getString("PlacedUUID")); - this.fluidFilter.deserializeNBT(tagCompound.getCompoundTag("Filter")); - updateTankLink(); - } - - @Override - public void writeInitialSyncData(PacketBuffer packetBuffer) { - packetBuffer.writeInt(this.color); - packetBuffer.writeString(this.playerUUID == null ? "null" : this.playerUUID.toString()); - } - - @Override - public void readInitialSyncData(PacketBuffer packetBuffer) { - this.color = packetBuffer.readInt(); - // does client even need uuid info? just in case - String uuidStr = packetBuffer.readString(36); - this.playerUUID = uuidStr.equals("null") ? null : UUID.fromString(uuidStr); - // client does not need the actual tank reference, the default one will do just fine - } - - @Override - public boolean isWorkingEnabled() { - return workingEnabled; - } - - @Override - public void setWorkingEnabled(boolean isActivationAllowed) { - this.workingEnabled = isActivationAllowed; - } - - public T getCapability(Capability capability, T defaultValue) { - if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { - return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(linkedTank); - } - if (capability == GregtechTileCapabilities.CAPABILITY_CONTROLLABLE) { - return GregtechTileCapabilities.CAPABILITY_CONTROLLABLE.cast(this); - } - return defaultValue; - } - - private boolean isIoEnabled() { - return ioEnabled; - } - - private void setIoEnabled(boolean ioEnabled) { - this.ioEnabled = ioEnabled; - } - - private boolean isPrivate() { - return isPrivate; - } - - private void setPrivate(boolean isPrivate) { - this.isPrivate = isPrivate; - updateTankLink(); - } -} diff --git a/src/main/java/gregtech/common/covers/ender/CoverAbstractEnderLink.java b/src/main/java/gregtech/common/covers/ender/CoverAbstractEnderLink.java new file mode 100644 index 00000000000..eb5c6a81d9d --- /dev/null +++ b/src/main/java/gregtech/common/covers/ender/CoverAbstractEnderLink.java @@ -0,0 +1,513 @@ +package gregtech.common.covers.ender; + +import gregtech.api.capability.GregtechDataCodes; +import gregtech.api.capability.IControllable; +import gregtech.api.cover.CoverBase; +import gregtech.api.cover.CoverDefinition; +import gregtech.api.cover.CoverWithUI; +import gregtech.api.cover.CoverableView; +import gregtech.api.mui.GTGuiTextures; +import gregtech.api.mui.GTGuis; +import gregtech.api.util.virtualregistry.EntryTypes; +import gregtech.api.util.virtualregistry.VirtualEnderRegistry; +import gregtech.api.util.virtualregistry.VirtualEntry; +import gregtech.common.mui.widget.InteractableText; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ITickable; + +import codechicken.lib.raytracer.CuboidRayTraceResult; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.drawable.DynamicDrawable; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.drawable.Rectangle; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.value.sync.BooleanSyncValue; +import com.cleanroommc.modularui.value.sync.PanelSyncHandler; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.value.sync.StringSyncValue; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ListWidget; +import com.cleanroommc.modularui.widgets.ToggleButton; +import com.cleanroommc.modularui.widgets.layout.Column; +import com.cleanroommc.modularui.widgets.layout.Row; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Pattern; + +@SuppressWarnings("SameParameterValue") +public abstract class CoverAbstractEnderLink extends CoverBase + implements CoverWithUI, ITickable, IControllable { + + protected static final Pattern COLOR_INPUT_PATTERN = Pattern.compile("[0-9a-fA-F]*"); + public static final int UPDATE_PRIVATE = GregtechDataCodes.assignId(); + + protected T activeEntry = null; + protected String color = VirtualEntry.DEFAULT_COLOR; + protected UUID playerUUID = null; + private boolean isPrivate = false; + private boolean workingEnabled = true; + private boolean ioEnabled = false; + + public CoverAbstractEnderLink(@NotNull CoverDefinition definition, @NotNull CoverableView coverableView, + @NotNull EnumFacing attachedSide) { + super(definition, coverableView, attachedSide); + updateLink(); + } + + protected void updateLink() { + this.activeEntry = VirtualEnderRegistry.getOrCreateEntry(getOwner(), getType(), createName()); + this.activeEntry.setColor(this.color); + markDirty(); + } + + protected abstract EntryTypes getType(); + + public String getColorStr() { + return this.color; + } + + protected final String createName() { + return identifier() + this.color; + } + + protected abstract String identifier(); + + protected final UUID getOwner() { + return isPrivate ? playerUUID : null; + } + + @Override + public void readCustomData(int discriminator, @NotNull PacketBuffer buf) { + super.readCustomData(discriminator, buf); + if (discriminator == UPDATE_PRIVATE) { + setPrivate(buf.readBoolean()); + updateLink(); + } + } + + @Override + public @NotNull EnumActionResult onScrewdriverClick(@NotNull EntityPlayer playerIn, @NotNull EnumHand hand, + @NotNull CuboidRayTraceResult hitResult) { + if (!getWorld().isRemote) { + openUI((EntityPlayerMP) playerIn); + } + return EnumActionResult.SUCCESS; + } + + @Override + public void onAttachment(@NotNull CoverableView coverableView, @NotNull EnumFacing side, + @Nullable EntityPlayer player, @NotNull ItemStack itemStack) { + super.onAttachment(coverableView, side, player, itemStack); + if (player != null) { + this.playerUUID = player.getUniqueID(); + } + } + + public void updateColor(String str) { + if (str.length() == 8) { + this.color = str.toUpperCase(); + updateLink(); + } + } + + @Override + public boolean usesMui2() { + return true; + } + + @Override + public ModularPanel buildUI(SidedPosGuiData guiData, PanelSyncManager guiSyncManager) { + var panel = GTGuis.createPanel(this, 176, 192); + + return panel.child(CoverWithUI.createTitleRow(getPickItem())) + .child(createWidgets(panel, guiSyncManager)) + .bindPlayerInventory(); + } + + protected Column createWidgets(ModularPanel panel, PanelSyncManager syncManager) { + var name = new StringSyncValue(this::getColorStr, this::updateColor); + + var entrySelectorSH = createEntrySelector(panel); + syncManager.syncValue("entry_selector", entrySelectorSH); + + return new Column().coverChildrenHeight().top(24) + .margin(7, 0).widthRel(1f) + .child(new Row().marginBottom(2) + .coverChildrenHeight() + .child(createPrivateButton()) + .child(createColorIcon()) + .child(new TextFieldWidget() + .height(18) + .value(name) + .setPattern(COLOR_INPUT_PATTERN) + .widthRel(0.5f) + .marginRight(2)) + .child(createEntrySlot()) + .child(new ButtonWidget<>() + .overlay(GTGuiTextures.MENU_OVERLAY) + .background(GTGuiTextures.MC_BUTTON) + .disableHoverBackground() + .addTooltipLine(IKey.lang("cover.generic.ender.open_selector")) + .onMousePressed(i -> { + if (entrySelectorSH.isPanelOpen()) { + entrySelectorSH.closePanel(); + } else { + entrySelectorSH.openPanel(); + } + Interactable.playButtonClickSound(); + return true; + }))) + .child(createIoRow()); + } + + protected abstract PanelSyncHandler createEntrySelector(ModularPanel panel); + + protected abstract IWidget createEntrySlot(); + + protected IWidget createColorIcon() { + // todo color selector popup panel + return new DynamicDrawable(() -> new Rectangle() + .setColor(this.activeEntry.getColor()) + .asIcon().size(16)) + .asWidget() + .background(GTGuiTextures.SLOT) + .size(18) + .marginRight(2); + } + + protected IWidget createPrivateButton() { + var isPrivate = new BooleanSyncValue(this::isPrivate, this::setPrivate); + isPrivate.updateCacheFromSource(true); + + return new ToggleButton() + .tooltip(tooltip -> tooltip.setAutoUpdate(true)) + .background(GTGuiTextures.PRIVATE_MODE_BUTTON[0]) + .hoverBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[0]) + .selectedBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[1]) + .selectedHoverBackground(GTGuiTextures.PRIVATE_MODE_BUTTON[1]) + .tooltipBuilder(tooltip -> tooltip.addLine(IKey.lang(this.isPrivate ? + "cover.ender_fluid_link.private.tooltip.enabled" : + "cover.ender_fluid_link.private.tooltip.disabled"))) + .marginRight(2) + .value(isPrivate); + } + + protected IWidget createIoRow() { + var ioEnabled = new BooleanSyncValue(this::isIoEnabled, this::setIoEnabled); + + return new Row().marginBottom(2) + .coverChildrenHeight() + .child(new ToggleButton() + .value(ioEnabled) + .overlay(IKey.dynamic(() -> IKey.lang(this.ioEnabled ? + "behaviour.soft_hammer.enabled" : + "behaviour.soft_hammer.disabled").get()) + .color(Color.WHITE.darker(1))) + .widthRel(0.6f) + .left(0)); + } + + @Override + public boolean isWorkingEnabled() { + return workingEnabled; + } + + @Override + public void setWorkingEnabled(boolean isActivationAllowed) { + this.workingEnabled = isActivationAllowed; + } + + public boolean isIoEnabled() { + return ioEnabled; + } + + protected void setIoEnabled(boolean ioEnabled) { + this.ioEnabled = ioEnabled; + } + + private boolean isPrivate() { + return isPrivate; + } + + private void setPrivate(boolean isPrivate) { + this.isPrivate = isPrivate; + updateLink(); + writeCustomData(UPDATE_PRIVATE, buffer -> buffer.writeBoolean(this.isPrivate)); + } + + @Override + public void writeInitialSyncData(PacketBuffer packetBuffer) { + packetBuffer.writeString(this.playerUUID == null ? "null" : this.playerUUID.toString()); + } + + @Override + public void readInitialSyncData(PacketBuffer packetBuffer) { + // does client even need uuid info? just in case + String uuidStr = packetBuffer.readString(36); + this.playerUUID = uuidStr.equals("null") ? null : UUID.fromString(uuidStr); + } + + @Override + public void readFromNBT(@NotNull NBTTagCompound nbt) { + super.readFromNBT(nbt); + this.ioEnabled = nbt.getBoolean("IOAllowed"); + this.isPrivate = nbt.getBoolean("Private"); + this.workingEnabled = nbt.getBoolean("WorkingAllowed"); + this.playerUUID = UUID.fromString(nbt.getString("PlacedUUID")); + int color = nbt.getInteger("Frequency"); + this.color = Integer.toHexString(color).toUpperCase(); + updateLink(); + } + + @Override + public void writeToNBT(@NotNull NBTTagCompound nbt) { + super.writeToNBT(nbt); + nbt.setBoolean("IOAllowed", ioEnabled); + nbt.setBoolean("Private", isPrivate); + nbt.setBoolean("WorkingAllowed", workingEnabled); + nbt.setString("PlacedUUID", playerUUID.toString()); + nbt.setInteger("Frequency", activeEntry.getColor()); + } + + protected abstract class EntrySelectorSH extends PanelSyncHandler { + + private static final int TRACK_SUBPANELS = 3; + private static final int DELETE_ENTRY = 1; + private final EntryTypes type; + private final ModularPanel mainPanel; + private static final String PANEL_NAME = "entry_selector"; + private final Set opened = new HashSet<>(); + protected UUID playerUUID; + + protected EntrySelectorSH(ModularPanel mainPanel, EntryTypes type) { + super(mainPanel, EntrySelectorSH::defaultPanel); + this.type = type; + this.mainPanel = mainPanel; + } + + @Override + public void init(String key, PanelSyncManager syncManager) { + super.init(key, syncManager); + this.playerUUID = syncManager.getPlayer().getUniqueID(); + } + + private static ModularPanel defaultPanel(PanelSyncManager syncManager, PanelSyncHandler syncHandler) { + return GTGuis.createPopupPanel(PANEL_NAME, 168, 112); + } + + public UUID getPlayerUUID() { + return isPrivate ? playerUUID : null; + } + + @Override + public ModularPanel createUI(PanelSyncManager syncManager) { + List names = new ArrayList<>(VirtualEnderRegistry.getEntryNames(getPlayerUUID(), type)); + return super.createUI(syncManager) + .child(IKey.lang("cover.generic.ender.known_channels") + .color(UI_TITLE_COLOR).asWidget() + .top(6) + .left(4)) + .child(ListWidget.builder(names, name -> createRow(name, this.mainPanel, syncManager)) + .background(GTGuiTextures.DISPLAY.asIcon() + .width(168 - 8) + .height(112 - 20)) + .paddingTop(1) + .size(168 - 12, 112 - 24) + .left(4) + .bottom(6)); + } + + protected IWidget createRow(String name, ModularPanel mainPanel, PanelSyncManager syncManager) { + T entry = VirtualEnderRegistry.getEntry(getPlayerUUID(), this.type, name); + String key = String.format("entry#%s_description", entry.getColorStr()); + var entryDescriptionSH = new EntryDescriptionSH(mainPanel, key, entry); + syncManager.syncValue(key, isPrivate ? 1 : 0, entryDescriptionSH); + + return new Row() + .left(4) + .marginBottom(2) + .height(18) + .widthRel(0.98f) + .setEnabledIf(row -> VirtualEnderRegistry.hasEntry(getOwner(), this.type, name)) + .child(new Rectangle() + .setColor(entry.getColor()) + .asWidget() + .marginRight(4) + .size(16) + .background(GTGuiTextures.SLOT.asIcon().size(18)) + .top(1)) + .child(new InteractableText<>(entry, CoverAbstractEnderLink.this::updateColor) + .tooltip(tooltip -> tooltip.setAutoUpdate(true)) + .tooltipBuilder(tooltip -> { + String desc = entry.getDescription(); + if (!desc.isEmpty()) + tooltip.addLine(desc); + }) + .width(64) + .height(16) + .top(1) + .marginRight(4)) + .child(new ButtonWidget<>() + .overlay(GuiTextures.GEAR) + .addTooltipLine(IKey.lang("cover.generic.ender.set_description.tooltip")) + .onMousePressed(i -> { + // open entry settings + if (entryDescriptionSH.isPanelOpen()) { + entryDescriptionSH.closePanel(); + } else { + entryDescriptionSH.openPanel(); + } + Interactable.playButtonClickSound(); + return true; + })) + .child(createSlotWidget(entry)) + .child(new ButtonWidget<>() + .overlay(GTGuiTextures.BUTTON_CROSS) + .setEnabledIf(w -> !Objects.equals(entry.getColor(), activeEntry.getColor())) + .addTooltipLine(IKey.lang("cover.generic.ender.delete_entry")) + .onMousePressed(i -> { + // todo option to force delete, maybe as a popup? + deleteEntry(getPlayerUUID(), name); + syncToServer(1, buffer -> { + NetworkUtils.writeStringSafe(buffer, getPlayerUUID().toString()); + NetworkUtils.writeStringSafe(buffer, name); + }); + Interactable.playButtonClickSound(); + return true; + })); + } + + @Override + public void closePanel() { + var manager = getSyncManager().getModularSyncManager().getPanelSyncManager(PANEL_NAME); + for (var key : opened) { + if (manager.getSyncHandler(key) instanceof PanelSyncHandler psh) { + psh.closePanel(); + } + } + super.closePanel(); + } + + @Override + @SuppressWarnings("UnstableApiUsage") + public void closePanelInternal() { + var manager = getSyncManager().getModularSyncManager().getPanelSyncManager(PANEL_NAME); + for (var key : opened) { + if (manager.getSyncHandler(key) instanceof PanelSyncHandler psh) { + psh.closePanel(); + } + } + super.closePanelInternal(); + } + + @Override + public void readOnClient(int i, PacketBuffer packetBuffer) throws IOException { + if (i == TRACK_SUBPANELS) { + handleTracking(packetBuffer); + } + super.readOnClient(i, packetBuffer); + } + + @Override + public void readOnServer(int i, PacketBuffer packetBuffer) throws IOException { + if (i == TRACK_SUBPANELS) { + handleTracking(packetBuffer); + } + super.readOnServer(i, packetBuffer); + if (i == DELETE_ENTRY) { + UUID uuid = UUID.fromString(NetworkUtils.readStringSafe(packetBuffer)); + String name = NetworkUtils.readStringSafe(packetBuffer); + deleteEntry(uuid, name); + } + } + + private void handleTracking(PacketBuffer buffer) { + boolean add = buffer.readBoolean(); + String key = NetworkUtils.readStringSafe(buffer); + if (key != null) { + if (add) opened.add(key); + else opened.remove(key); + } + } + + private class EntryDescriptionSH extends PanelSyncHandler { + + /** + * Creates a PanelSyncHandler + * + * @param mainPanel the main panel of the current GUI + */ + public EntryDescriptionSH(ModularPanel mainPanel, String key, VirtualEntry entry) { + super(mainPanel, (syncManager, syncHandler) -> defaultPanel(syncHandler, key, entry)); + } + + private static ModularPanel defaultPanel(@NotNull PanelSyncHandler syncHandler, String key, + VirtualEntry entry) { + return GTGuis.createPopupPanel(key, 168, 36 + 6) + .child(IKey.lang("cover.generic.ender.set_description.title", entry.getColorStr()) + .color(UI_TITLE_COLOR) + .asWidget() + .left(4) + .top(6)) + .child(new TextFieldWidget() + .setTextColor(Color.WHITE.darker(1)) + .widthRel(0.95f) + .height(18) + .value(new StringSyncValue(entry::getDescription, string -> { + entry.setDescription(string); + if (syncHandler.isPanelOpen()) { + syncHandler.closePanel(); + } + })) + .alignX(0.5f) + .bottom(6)); + } + + @Override + public void openPanel() { + opened.add(getKey()); + EntrySelectorSH.this.sync(3, buffer -> { + buffer.writeBoolean(true); + NetworkUtils.writeStringSafe(buffer, getKey()); + }); + super.openPanel(); + } + + @Override + public void closePanel() { + opened.remove(getKey()); + EntrySelectorSH.this.sync(3, buffer -> { + buffer.writeBoolean(false); + NetworkUtils.writeStringSafe(buffer, getKey()); + }); + super.closePanel(); + } + } + + protected abstract IWidget createSlotWidget(T entry); + + protected abstract void deleteEntry(UUID player, String name); + } +} diff --git a/src/main/java/gregtech/common/covers/ender/CoverEnderFluidLink.java b/src/main/java/gregtech/common/covers/ender/CoverEnderFluidLink.java new file mode 100644 index 00000000000..27c338154b1 --- /dev/null +++ b/src/main/java/gregtech/common/covers/ender/CoverEnderFluidLink.java @@ -0,0 +1,193 @@ +package gregtech.common.covers.ender; + +import gregtech.api.capability.GregtechTileCapabilities; +import gregtech.api.capability.IControllable; +import gregtech.api.cover.CoverDefinition; +import gregtech.api.cover.CoverWithUI; +import gregtech.api.cover.CoverableView; +import gregtech.api.mui.GTGuiTextures; +import gregtech.api.util.FluidTankSwitchShim; +import gregtech.api.util.GTTransferUtils; +import gregtech.api.util.virtualregistry.EntryTypes; +import gregtech.api.util.virtualregistry.VirtualEnderRegistry; +import gregtech.api.util.virtualregistry.entries.VirtualTank; +import gregtech.client.renderer.texture.Textures; +import gregtech.common.covers.CoverPump; +import gregtech.common.covers.filter.FluidFilterContainer; +import gregtech.common.mui.widget.GTFluidSlot; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ITickable; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.render.pipeline.IVertexOperation; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.value.sync.EnumSyncValue; +import com.cleanroommc.modularui.value.sync.PanelSyncHandler; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.widgets.layout.Column; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class CoverEnderFluidLink extends CoverAbstractEnderLink + implements CoverWithUI, ITickable, IControllable { + + public static final int TRANSFER_RATE = 8000; // mB/t + + protected CoverPump.PumpMode pumpMode = CoverPump.PumpMode.IMPORT; + private final FluidTankSwitchShim linkedTank; + protected final FluidFilterContainer fluidFilter; + + public CoverEnderFluidLink(@NotNull CoverDefinition definition, @NotNull CoverableView coverableView, + @NotNull EnumFacing attachedSide) { + super(definition, coverableView, attachedSide); + this.linkedTank = new FluidTankSwitchShim(this.activeEntry); + this.fluidFilter = new FluidFilterContainer(this); + } + + @Override + protected void updateLink() { + super.updateLink(); + if (this.linkedTank != null) + this.linkedTank.changeTank(this.activeEntry); + } + + @Override + protected EntryTypes getType() { + return EntryTypes.ENDER_FLUID; + } + + @Override + protected String identifier() { + return "EFLink#"; + } + + public FluidFilterContainer getFluidFilterContainer() { + return this.fluidFilter; + } + + @Override + public boolean canAttach(@NotNull CoverableView coverable, @NotNull EnumFacing side) { + return coverable.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side); + } + + @Override + public void renderCover(@NotNull CCRenderState renderState, @NotNull Matrix4 translation, + IVertexOperation[] pipeline, @NotNull Cuboid6 plateBox, @NotNull BlockRenderLayer layer) { + Textures.ENDER_FLUID_LINK.renderSided(getAttachedSide(), plateBox, renderState, pipeline, translation); + } + + @Override + public void onRemoval() { + dropInventoryContents(fluidFilter); + } + + @Override + public void update() { + if (isWorkingEnabled() && isIoEnabled()) { + transferFluids(); + } + } + + protected void transferFluids() { + IFluidHandler fluidHandler = getCoverableView().getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, + getAttachedSide()); + if (fluidHandler == null) return; + if (pumpMode == CoverPump.PumpMode.IMPORT) { + GTTransferUtils.transferFluids(fluidHandler, activeEntry, TRANSFER_RATE, fluidFilter::test); + } else if (pumpMode == CoverPump.PumpMode.EXPORT) { + GTTransferUtils.transferFluids(activeEntry, fluidHandler, TRANSFER_RATE, fluidFilter::test); + } + } + + public void setPumpMode(CoverPump.PumpMode pumpMode) { + this.pumpMode = pumpMode; + markDirty(); + } + + public CoverPump.PumpMode getPumpMode() { + return pumpMode; + } + + @Override + protected PanelSyncHandler createEntrySelector(ModularPanel panel) { + return new EntrySelectorSH(panel, EntryTypes.ENDER_FLUID) { + + @Override + protected IWidget createSlotWidget(VirtualTank entry) { + var fluidTank = GTFluidSlot.sync(entry) + .canFillSlot(false) + .canDrainSlot(false); + + return new GTFluidSlot() + .size(18) + .background(GTGuiTextures.FLUID_SLOT) + .syncHandler(fluidTank) + .marginRight(2); + } + + @Override + protected void deleteEntry(UUID uuid, String name) { + VirtualEnderRegistry.deleteEntry(uuid, getType(), name, tank -> tank.getFluidAmount() == 0); + } + }; + } + + @Override + protected IWidget createEntrySlot() { + return new GTFluidSlot() + .size(18) + .background(GTGuiTextures.FLUID_SLOT) + .syncHandler(this.linkedTank) + .marginRight(2); + } + + protected Column createWidgets(ModularPanel panel, PanelSyncManager syncManager) { + getFluidFilterContainer().setMaxTransferSize(1); + + var pumpMode = new EnumSyncValue<>(CoverPump.PumpMode.class, this::getPumpMode, this::setPumpMode); + syncManager.syncValue("pump_mode", pumpMode); + pumpMode.updateCacheFromSource(true); + + return super.createWidgets(panel, syncManager) + .child(getFluidFilterContainer().initUI(panel, syncManager)) + .child(new EnumRowBuilder<>(CoverPump.PumpMode.class) + .value(pumpMode) + .overlay(GTGuiTextures.CONVEYOR_MODE_OVERLAY) + .lang("cover.pump.mode") + .build()); + } + + @Override + public void writeToNBT(NBTTagCompound tagCompound) { + super.writeToNBT(tagCompound); + tagCompound.setInteger("PumpMode", pumpMode.ordinal()); + tagCompound.setTag("Filter", fluidFilter.serializeNBT()); + } + + @Override + public void readFromNBT(NBTTagCompound tagCompound) { + super.readFromNBT(tagCompound); + this.pumpMode = CoverPump.PumpMode.values()[tagCompound.getInteger("PumpMode")]; + this.fluidFilter.deserializeNBT(tagCompound.getCompoundTag("Filter")); + } + + public T getCapability(Capability capability, T defaultValue) { + if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(this.activeEntry); + } + if (capability == GregtechTileCapabilities.CAPABILITY_CONTROLLABLE) { + return GregtechTileCapabilities.CAPABILITY_CONTROLLABLE.cast(this); + } + return defaultValue; + } +} diff --git a/src/main/java/gregtech/common/mui/widget/GTFluidSlot.java b/src/main/java/gregtech/common/mui/widget/GTFluidSlot.java new file mode 100644 index 00000000000..1b0ccff4ebe --- /dev/null +++ b/src/main/java/gregtech/common/mui/widget/GTFluidSlot.java @@ -0,0 +1,142 @@ +package gregtech.common.mui.widget; + +import gregtech.api.GTValues; +import gregtech.api.mui.sync.GTFluidSyncHandler; +import gregtech.api.util.FluidTooltipUtil; +import gregtech.api.util.LocalizationUtils; +import gregtech.client.utils.TooltipHelper; + +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.drawable.GuiDraw; +import com.cleanroommc.modularui.drawable.TextRenderer; +import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; +import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetSlotTheme; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.NumberFormat; +import com.cleanroommc.modularui.widget.Widget; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class GTFluidSlot extends Widget implements Interactable, JeiIngredientProvider { + + private final TextRenderer textRenderer = new TextRenderer(); + private GTFluidSyncHandler syncHandler; + + public GTFluidSlot() { + tooltip().setAutoUpdate(true).setHasTitleMargin(true); + tooltipBuilder(tooltip -> { + if (!isSynced()) return; + var fluid = this.syncHandler.getFluid(); + if (fluid == null) return; + + tooltip.addLine(fluid.getLocalizedName()); + tooltip.addLine(IKey.lang("gregtech.fluid.amount", fluid.amount, this.syncHandler.getCapacity())); + + // Add various tooltips from the material + List formula = FluidTooltipUtil.getFluidTooltip(fluid); + if (formula != null) { + for (String s : formula) { + if (s.isEmpty()) continue; + tooltip.addLine(s); + } + } + + addIngotMolFluidTooltip(fluid, tooltip); + }); + } + + public static GTFluidSyncHandler sync(IFluidTank tank) { + return new GTFluidSyncHandler(tank); + } + + @Override + public void onInit() { + this.textRenderer.setShadow(true); + this.textRenderer.setScale(0.5f); + this.textRenderer.setColor(Color.WHITE.main); + } + + public GTFluidSlot syncHandler(IFluidTank fluidTank) { + return syncHandler(new GTFluidSyncHandler(fluidTank)); + } + + public GTFluidSlot syncHandler(GTFluidSyncHandler syncHandler) { + setSyncHandler(syncHandler); + this.syncHandler = syncHandler; + return this; + } + + @Override + public void draw(GuiContext context, WidgetTheme widgetTheme) { + FluidStack content = this.syncHandler.getFluid(); + if (content != null) { + GuiDraw.drawFluidTexture(content, 1, 1, getArea().w() - 2, getArea().h() - 2, 0); + + String s = NumberFormat.formatWithMaxDigits(getBaseUnitAmount(content.amount)) + getBaseUnit(); + this.textRenderer.setAlignment(Alignment.CenterRight, getArea().width - 1f); + this.textRenderer.setPos(0, 12); + this.textRenderer.draw(s); + } + if (isHovering()) { + GlStateManager.colorMask(true, true, true, false); + GuiDraw.drawRect(1, 1, getArea().w() - 2, getArea().h() - 2, + getWidgetTheme(context.getTheme()).getSlotHoverColor()); + GlStateManager.colorMask(true, true, true, true); + } + } + + protected double getBaseUnitAmount(double amount) { + return amount / 1000; + } + + protected String getBaseUnit() { + return "L"; + } + + @NotNull + @Override + public Result onMouseTapped(int mouseButton) { + if (this.syncHandler.canFillSlot() || this.syncHandler.canDrainSlot()) { + this.syncHandler.syncToServer(1, buffer -> buffer.writeBoolean(mouseButton == 0)); + Interactable.playButtonClickSound(); + return Result.SUCCESS; + } + return Result.IGNORE; + } + + @Override + public WidgetSlotTheme getWidgetTheme(ITheme theme) { + return theme.getFluidSlotTheme(); + } + + @Override + public @Nullable Object getIngredient() { + return this.syncHandler.getFluid(); + } + + public static void addIngotMolFluidTooltip(FluidStack fluidStack, Tooltip tooltip) { + // Add tooltip showing how many "ingot moles" (increments of 144) this fluid is if shift is held + if (TooltipHelper.isShiftDown() && fluidStack.amount > GTValues.L) { + int numIngots = fluidStack.amount / GTValues.L; + int extra = fluidStack.amount % GTValues.L; + String fluidAmount = String.format(" %,d L = %,d * %d L", fluidStack.amount, numIngots, GTValues.L); + if (extra != 0) { + fluidAmount += String.format(" + %d L", extra); + } + tooltip.addLine(TextFormatting.GRAY + LocalizationUtils.format("gregtech.gui.amount_raw") + fluidAmount); + } + } +} diff --git a/src/main/java/gregtech/common/mui/widget/InteractableText.java b/src/main/java/gregtech/common/mui/widget/InteractableText.java new file mode 100644 index 00000000000..eac6936059a --- /dev/null +++ b/src/main/java/gregtech/common/mui/widget/InteractableText.java @@ -0,0 +1,63 @@ +package gregtech.common.mui.widget; + +import gregtech.api.util.virtualregistry.VirtualEntry; + +import net.minecraft.network.PacketBuffer; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.widgets.TextWidget; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +public class InteractableText extends TextWidget implements Interactable { + + private final T entry; + private final EntryColorSH syncHandler; + + public InteractableText(T entry, Consumer setter) { + super(IKey.str(entry.getColorStr()) + .alignment(Alignment.CenterLeft) + .color(Color.WHITE.darker(1))); + this.entry = entry; + this.syncHandler = new EntryColorSH(setter); + setSyncHandler(this.syncHandler); + } + + @NotNull + @Override + public Result onMousePressed(int mouseButton) { + Interactable.playButtonClickSound(); + this.syncHandler.setColor(this.entry.getColorStr()); + this.syncHandler.syncToServer(1, buf -> NetworkUtils.writeStringSafe(buf, this.entry.getColorStr())); + return Result.SUCCESS; + } + + private static class EntryColorSH extends SyncHandler { + + private final Consumer setter; + + private EntryColorSH(Consumer setter) { + this.setter = setter; + } + + public void setColor(String c) { + this.setter.accept(c); + } + + @Override + public void readOnClient(int id, PacketBuffer buf) {} + + @Override + public void readOnServer(int id, PacketBuffer buf) { + if (id == 1) { + setColor(NetworkUtils.readStringSafe(buf)); + } + } + } +} diff --git a/src/main/java/gregtech/common/terminal/app/VirtualTankApp.java b/src/main/java/gregtech/common/terminal/app/VirtualTankApp.java index b6a24a190b9..ed0e63eefac 100644 --- a/src/main/java/gregtech/common/terminal/app/VirtualTankApp.java +++ b/src/main/java/gregtech/common/terminal/app/VirtualTankApp.java @@ -12,7 +12,7 @@ import gregtech.api.terminal.os.TerminalTheme; import gregtech.api.terminal.os.menu.IMenuComponent; import gregtech.api.util.GTLog; -import gregtech.api.util.VirtualTankRegistry; +import gregtech.api.util.virtualregistry.VirtualEnderRegistry; import gregtech.common.terminal.component.SearchComponent; import net.minecraft.nbt.NBTTagCompound; @@ -70,9 +70,8 @@ public AbstractApplication initApp() { return this; } - private List> findVirtualTanks() { + private List> findVirtualTanks(Map> tankMap) { List> result = new LinkedList<>(); - Map> tankMap = VirtualTankRegistry.getTankMap(); for (UUID uuid : tankMap.keySet().stream().sorted(Comparator.nullsLast(UUID::compareTo)) .collect(Collectors.toList())) { if (uuid == null || uuid.equals(gui.entityPlayer.getUniqueID())) { @@ -93,9 +92,9 @@ public void detectAndSendChanges() { } private void refresh() { - Map> tankMap = VirtualTankRegistry.getTankMap(); + Map> tankMap = VirtualEnderRegistry.createTankMap(); Map, FluidStack> access = new HashMap<>(); - for (Pair virtualTankEntry : findVirtualTanks()) { + for (Pair virtualTankEntry : findVirtualTanks(tankMap)) { UUID uuid = virtualTankEntry.getKey(); String key = virtualTankEntry.getValue(); FluidStack fluidStack = tankMap.get(uuid).get(key).getFluid(); @@ -229,7 +228,8 @@ public List getMenuComponents() { @Override public String resultDisplay(Pair result) { - FluidStack fluidStack = VirtualTankRegistry.getTankMap().get(result.getKey()).get(result.getValue()).getFluid(); + FluidStack fluidStack = VirtualEnderRegistry.createTankMap().get(result.getKey()).get(result.getValue()) + .getFluid(); return String.format("Lock: %b, ID: %s, Fluid: %s", result.getKey() != null, result.getValue(), fluidStack == null ? "-" : fluidStack.getLocalizedName()); } diff --git a/src/main/java/gregtech/core/CoreModule.java b/src/main/java/gregtech/core/CoreModule.java index 5e80c4b3b31..149c19344ec 100644 --- a/src/main/java/gregtech/core/CoreModule.java +++ b/src/main/java/gregtech/core/CoreModule.java @@ -29,9 +29,9 @@ import gregtech.api.unification.material.registry.MarkerMaterialRegistry; import gregtech.api.util.CapesRegistry; import gregtech.api.util.Mods; -import gregtech.api.util.VirtualTankRegistry; import gregtech.api.util.input.KeyBind; import gregtech.api.util.oreglob.OreGlob; +import gregtech.api.util.virtualregistry.VirtualEnderRegistry; import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinHandler; import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinSaveData; import gregtech.api.worldgen.config.WorldGenRegistry; @@ -347,7 +347,7 @@ public void serverStarted(FMLServerStartedEvent event) { @Override public void serverStopped(FMLServerStoppedEvent event) { - VirtualTankRegistry.clearMaps(); + VirtualEnderRegistry.clearMaps(); CapesRegistry.clearMaps(); } } diff --git a/src/main/java/gregtech/integration/opencomputers/drivers/DriverCoverHolder.java b/src/main/java/gregtech/integration/opencomputers/drivers/DriverCoverHolder.java index be85082485c..b05a8e3f7af 100644 --- a/src/main/java/gregtech/integration/opencomputers/drivers/DriverCoverHolder.java +++ b/src/main/java/gregtech/integration/opencomputers/drivers/DriverCoverHolder.java @@ -5,6 +5,7 @@ import gregtech.api.cover.CoverHolder; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.common.covers.*; +import gregtech.common.covers.ender.CoverEnderFluidLink; import gregtech.integration.opencomputers.InputValidator; import gregtech.integration.opencomputers.values.*; diff --git a/src/main/java/gregtech/integration/opencomputers/values/ValueCoverEnderFluidLink.java b/src/main/java/gregtech/integration/opencomputers/values/ValueCoverEnderFluidLink.java index 8f919385959..31c8b2ae10d 100644 --- a/src/main/java/gregtech/integration/opencomputers/values/ValueCoverEnderFluidLink.java +++ b/src/main/java/gregtech/integration/opencomputers/values/ValueCoverEnderFluidLink.java @@ -1,8 +1,8 @@ package gregtech.integration.opencomputers.values; import gregtech.api.cover.Cover; -import gregtech.common.covers.CoverEnderFluidLink; import gregtech.common.covers.CoverPump.PumpMode; +import gregtech.common.covers.ender.CoverEnderFluidLink; import gregtech.integration.opencomputers.InputValidator; import net.minecraft.util.EnumFacing; diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/CoverInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/CoverInfoProvider.java index 0aebf88036a..377ff7a51d7 100644 --- a/src/main/java/gregtech/integration/theoneprobe/provider/CoverInfoProvider.java +++ b/src/main/java/gregtech/integration/theoneprobe/provider/CoverInfoProvider.java @@ -6,6 +6,7 @@ import gregtech.api.cover.CoverHolder; import gregtech.api.util.TextFormattingUtil; import gregtech.common.covers.*; +import gregtech.common.covers.ender.CoverEnderFluidLink; import gregtech.common.covers.filter.*; import net.minecraft.entity.player.EntityPlayer; @@ -181,8 +182,8 @@ private static void fluidFilterInfo(@NotNull IProbeInfo probeInfo, @NotNull Cove * @param enderFluidLink the ender fluid link cover to get data from */ private static void enderFluidLinkInfo(@NotNull IProbeInfo probeInfo, @NotNull CoverEnderFluidLink enderFluidLink) { - transferRateText(probeInfo, enderFluidLink.getPumpMode(), " " + lang("cover.bucket.mode.milli_bucket_rate"), - enderFluidLink.isIOEnabled() ? CoverEnderFluidLink.TRANSFER_RATE : 0); + transferRateText(probeInfo, enderFluidLink.getPumpMode(), " " + lang("cover.ender_fluid_link.transfer_unit"), + enderFluidLink.isIoEnabled() ? CoverEnderFluidLink.TRANSFER_RATE : 0); fluidFilterText(probeInfo, enderFluidLink.getFluidFilterContainer().getFilter()); if (!enderFluidLink.getColorStr().isEmpty()) { diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 8576e93db24..042607af612 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -1358,6 +1358,13 @@ cover.ender_fluid_link.iomode.disabled=I/O Disabled cover.ender_fluid_link.private.tooltip.disabled=Switch to private tank mode\nPrivate mode uses the player who originally placed the cover cover.ender_fluid_link.private.tooltip.enabled=Switch to public tank mode cover.ender_fluid_link.incomplete_hex=Inputted color is incomplete!/nIt will be applied once complete (all 8 hex digits)/nClosing the gui will lose edits! +cover.ender_fluid_link.transfer_unit=L/t + +cover.generic.ender.known_channels=Known Channels +cover.generic.ender.open_selector=Open Entry Selector +cover.generic.ender.set_description.tooltip=Set Description +cover.generic.ender.set_description.title=Set Description [%s] +cover.generic.ender.delete_entry=Delete Entry cover.generic.advanced_detector.latched=Latched cover.generic.advanced_detector.continuous=Continuous diff --git a/src/main/resources/assets/gregtech/textures/gui/overlay/menu_overlay.png b/src/main/resources/assets/gregtech/textures/gui/overlay/menu_overlay.png new file mode 100644 index 00000000000..dc166fac7e4 Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/overlay/menu_overlay.png differ