diff --git a/src/generated/resources/assets/gtceu/lang/en_ud.json b/src/generated/resources/assets/gtceu/lang/en_ud.json index 18580338c0..43462b0be5 100644 --- a/src/generated/resources/assets/gtceu/lang/en_ud.json +++ b/src/generated/resources/assets/gtceu/lang/en_ud.json @@ -1958,6 +1958,10 @@ "cover.ender_fluid_link.private.tooltip.disabled.1": "ɹǝʌoɔ ǝɥʇ pǝɔɐןd ʎןןɐuıbıɹo oɥʍ ɹǝʎɐןd ǝɥʇ sǝsn ǝpoɯ ǝʇɐʌıɹԀ", "cover.ender_fluid_link.private.tooltip.enabled": "ǝpoɯ ʞuɐʇ ɔıןqnd oʇ ɥɔʇıʍS", "cover.ender_fluid_link.title": "ʞuıꞀ pınןℲ ɹǝpuƎ", + "cover.ender_fluid_link.tooltip.channel_description": "ʇxǝʇ ʇnduı ɥʇıʍ uoıʇdıɹɔsǝp ןǝuuɐɥɔ ʇǝS", + "cover.ender_fluid_link.tooltip.channel_name": "ʇxǝʇ ʇnduı ɥʇıʍ ǝɯɐu ןǝuuɐɥɔ ʇǝS", + "cover.ender_fluid_link.tooltip.clear_button": "uoıʇdıɹɔsǝp ןǝuuɐɥɔ ɹɐǝןƆ", + "cover.ender_fluid_link.tooltip.list_button": "ʇsıן ןǝuuɐɥɔ ʍoɥS", "cover.filter.blacklist.disabled": "ʇsıןǝʇıɥM", "cover.filter.blacklist.enabled": "ʇsıןʞɔɐןᗺ", "cover.filter.mode.filter_both": "ʇɔɐɹʇxƎ/ʇɹǝsuI ɹǝʇןıℲ", diff --git a/src/generated/resources/assets/gtceu/lang/en_us.json b/src/generated/resources/assets/gtceu/lang/en_us.json index b3fdb619c0..0c3fa2c889 100644 --- a/src/generated/resources/assets/gtceu/lang/en_us.json +++ b/src/generated/resources/assets/gtceu/lang/en_us.json @@ -1958,6 +1958,10 @@ "cover.ender_fluid_link.private.tooltip.disabled.1": "Private 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.title": "Ender Fluid Link", + "cover.ender_fluid_link.tooltip.channel_description": "Set channel description with input text", + "cover.ender_fluid_link.tooltip.channel_name": "Set channel name with input text", + "cover.ender_fluid_link.tooltip.clear_button": "Clear channel description", + "cover.ender_fluid_link.tooltip.list_button": "Show channel list", "cover.filter.blacklist.disabled": "Whitelist", "cover.filter.blacklist.enabled": "Blacklist", "cover.filter.mode.filter_both": "Filter Insert/Extract", diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java b/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java index 5c4e316863..7fcf5bac41 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java @@ -41,12 +41,16 @@ default ModularUI createUI(Player entityPlayer) { var widget = createUIWidget(); var size = widget.getSize(); widget.setSelfPosition(new Position((176 - size.width) / 2, 0)); - return new ModularUI(176, size.height + 82, this, entityPlayer) + var modularUI = new ModularUI(176, size.height + 82, this, entityPlayer) .background(GuiTextures.BACKGROUND) .widget(widget) .widget(UITemplate.bindPlayerInventory(entityPlayer.getInventory(), GuiTextures.SLOT, 7, size.height, true)); + modularUI.registerCloseListener(this::onUIClosed); + return modularUI; } + default void onUIClosed() {} + Widget createUIWidget(); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/GuiTextures.java b/src/main/java/com/gregtechceu/gtceu/api/gui/GuiTextures.java index da93638216..9f7b6a4d21 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/GuiTextures.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/GuiTextures.java @@ -129,6 +129,10 @@ public class GuiTextures { public static final ResourceTexture BUTTON_LEFT = new ResourceTexture("gtceu:textures/gui/widget/left.png"); public static final ResourceTexture BUTTON_PUBLIC_PRIVATE = new ResourceTexture( "gtceu:textures/gui/widget/button_public_private.png"); + public static final ResourceTexture BUTTON_CHECK = new ResourceTexture( + "gtceu:textures/gui/widget/button_check.png"); + public static final ResourceTexture BUTTON_LIST = new ResourceTexture( + "gtceu:textures/gui/widget/button_list.png"); public static final ResourceTexture BUTTON_RIGHT = new ResourceTexture("gtceu:textures/gui/widget/right.png"); public static final ResourceTexture BUTTON_SILK_TOUCH_MODE = new ResourceTexture( "gtceu:textures/gui/widget/button_silk_touch_mode.png"); diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ColorBlockWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ColorBlockWidget.java new file mode 100644 index 0000000000..9814698421 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ColorBlockWidget.java @@ -0,0 +1,54 @@ +package com.gregtechceu.gtceu.api.gui.widget; + +import com.lowdragmc.lowdraglib.gui.util.DrawerHelper; +import com.lowdragmc.lowdraglib.gui.widget.Widget; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; + +import java.util.function.IntSupplier; + +@Setter +@Accessors(chain = true) +public class ColorBlockWidget extends Widget { + + private IntSupplier colorSupplier; + @Getter + private int currentColor; + + public ColorBlockWidget(int x, int y, int width, int height) { + super(x, y, width, height); + this.currentColor = 0xFFFFFFFF; + } + + @Override + public void updateScreen() { + super.updateScreen(); + if (isClientSideWidget && colorSupplier != null) { + currentColor = colorSupplier.getAsInt(); + } + } + + @OnlyIn(Dist.CLIENT) + @Override + public void drawInBackground(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + int x = getPosition().x + 1; + int y = getPosition().y + 1; + int width = getSize().width - 2; + int height = getSize().height - 2; + + if (colorSupplier != null) { + currentColor = colorSupplier.getAsInt(); + } + final int BORDER_COLOR = 0xFF000000; + + graphics.fill(x, y, x + width, y + height, currentColor); + DrawerHelper.drawBorder(graphics, x, y, width, height, BORDER_COLOR, 1); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ConfirmTextInputWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ConfirmTextInputWidget.java new file mode 100644 index 0000000000..80d7a969cc --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ConfirmTextInputWidget.java @@ -0,0 +1,60 @@ +package com.gregtechceu.gtceu.api.gui.widget; + +import com.gregtechceu.gtceu.api.gui.GuiTextures; + +import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; +import com.lowdragmc.lowdraglib.gui.widget.ButtonWidget; +import com.lowdragmc.lowdraglib.gui.widget.TextFieldWidget; +import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.function.Consumer; +import java.util.function.Function; + +@Accessors(chain = true) +public class ConfirmTextInputWidget extends WidgetGroup { + + private final Consumer textResponder; + private final Function validator; + @Getter(AccessLevel.PRIVATE) + @Setter(AccessLevel.PRIVATE) + private String inputText = ""; + @Setter + private String tooltip = ""; + + public ConfirmTextInputWidget(int x, int y, int width, int height, String text, Consumer textResponder, + Function validator) { + super(x, y, width, height); + this.textResponder = textResponder; + this.validator = validator; + if (text != null) { + this.inputText = text; + } + } + + @Override + public void initWidget() { + super.initWidget(); + this.addWidget(new ButtonWidget( + getSizeWidth() - getSizeHeight(), + 0, + getSizeHeight(), + getSizeHeight(), + pressed -> textResponder.accept(inputText)) + .setButtonTexture( + new GuiTextureGroup(GuiTextures.VANILLA_BUTTON, GuiTextures.BUTTON_CHECK))); + this.addWidget(new TextFieldWidget( + 1, + 1, + getSizeWidth() - getSizeHeight() - 4, + getSizeHeight() - 2, + this::getInputText, + this::setInputText) + .setValidator(validator) + .setHoverTooltips(tooltip)); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/MachineCoverContainer.java b/src/main/java/com/gregtechceu/gtceu/api/machine/MachineCoverContainer.java index 12d216c0ef..326eaa0c03 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/MachineCoverContainer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/MachineCoverContainer.java @@ -40,6 +40,7 @@ public class MachineCoverContainer implements ICoverable, IEnhancedManaged { public static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder(MachineCoverContainer.class); @Getter private final FieldManagedStorage syncStorage = new FieldManagedStorage(this); + @Getter private final MetaMachine machine; @DescSynced @Persisted diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java new file mode 100644 index 0000000000..9910f108a8 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java @@ -0,0 +1,64 @@ +package com.gregtechceu.gtceu.api.misc.virtualregistry; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.misc.virtualregistry.entries.VirtualTank; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.function.Supplier; + +public final class EntryTypes { + + private static final Map> TYPES_MAP = new Object2ObjectOpenHashMap<>(); + public static final EntryTypes ENDER_FLUID = addEntryType(GTCEu.id("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; + } + + @Nullable + public static EntryTypes fromString(String name) { + return TYPES_MAP.getOrDefault(GTCEu.id(name), null); + } + + @Nullable + public static EntryTypes fromLocation(ResourceLocation location) { + return TYPES_MAP.getOrDefault(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 { + GTCEu.LOGGER.warn("Entry \"{}\" is already registered!", location); + } + return type; + } + + public T createInstance(CompoundTag nbt) { + var entry = createInstance(); + entry.deserializeNBT(nbt); + return entry; + } + + public T createInstance() { + return factory.get(); + } + + @Override + public String toString() { + return this.location.toString(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java new file mode 100644 index 0000000000..d76b54600b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java @@ -0,0 +1,137 @@ +package com.gregtechceu.gtceu.api.misc.virtualregistry; + +import com.gregtechceu.gtceu.GTCEu; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraftforge.server.ServerLifecycleHooks; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; + +public class VirtualEnderRegistry extends SavedData { + + private static final String DATA_ID = GTCEu.MOD_ID + ".virtual_entry_data"; + private static final String PUBLIC_KEY = "Public"; + private static final String PRIVATE_KEY = "Private"; + private static volatile VirtualEnderRegistry data; + private final Map VIRTUAL_REGISTRIES = new HashMap<>(); + + public VirtualEnderRegistry() {} + + public VirtualEnderRegistry(CompoundTag name) { + readFromNBT(name); + } + + public static VirtualEnderRegistry getInstance() { + if (data == null) { + var server = ServerLifecycleHooks.getCurrentServer(); + if (server != null) { + data = server.overworld().getDataStorage() + .computeIfAbsent(VirtualEnderRegistry::new, VirtualEnderRegistry::new, DATA_ID); + } + } + + return data; + } + + /** + * To be called on server stopped event + */ + public static void release() { + if (data != null) { + data = null; + GTCEu.LOGGER.debug("VirtualEnderRegistry has been unloaded"); + } + } + + public T getEntry(@Nullable UUID owner, EntryTypes type, String name) { + return getRegistry(owner).getEntry(type, name); + } + + public void addEntry(@Nullable UUID owner, String name, VirtualEntry entry) { + getRegistry(owner).addEntry(name, entry); + } + + public boolean hasEntry(@Nullable UUID owner, EntryTypes type, String name) { + return getRegistry(owner).contains(type, name); + } + + public @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 void deleteEntry(@Nullable UUID owner, EntryTypes type, String name) { + var registry = getRegistry(owner); + if (registry.contains(type, name)) { + registry.deleteEntry(type, name); + return; + } + GTCEu.LOGGER.warn("Attempted to delete {} entry {} of type {}, which does not exist", + owner == null ? "public" : String.format("private [%s]", owner), name, type); + } + + public void deleteEntryIf(@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 Set getEntryNames(UUID owner, EntryTypes type) { + return getRegistry(owner).getEntryNames(type); + } + + private VirtualRegistryMap getRegistry(UUID owner) { + if (data == null) getInstance(); + return data.VIRTUAL_REGISTRIES.computeIfAbsent(owner, key -> new VirtualRegistryMap()); + } + + public final void readFromNBT(CompoundTag nbt) { + if (nbt.contains(PUBLIC_KEY)) { + VIRTUAL_REGISTRIES.put(null, new VirtualRegistryMap(nbt.getCompound(PUBLIC_KEY))); + } + if (nbt.contains(PRIVATE_KEY)) { + CompoundTag privateEntries = nbt.getCompound(PRIVATE_KEY); + for (String owner : privateEntries.getAllKeys()) { + var privateMap = privateEntries.getCompound(owner); + VIRTUAL_REGISTRIES.put(UUID.fromString(owner), new VirtualRegistryMap(privateMap)); + } + } + } + + @NotNull + @Override + public final CompoundTag save(@NotNull CompoundTag tag) { + var privateTag = new CompoundTag(); + for (var owner : VIRTUAL_REGISTRIES.keySet()) { + var mapTag = VIRTUAL_REGISTRIES.get(owner).serializeNBT(); + if (owner != null) { + privateTag.put(owner.toString(), mapTag); + } else { + tag.put(PUBLIC_KEY, mapTag); + } + } + tag.put(PRIVATE_KEY, privateTag); + return tag; + } + + @Override + public boolean isDirty() { + // can't think of a good way to mark dirty other than always return true; + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java new file mode 100644 index 0000000000..f4bc95b609 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java @@ -0,0 +1,86 @@ +package com.gregtechceu.gtceu.api.misc.virtualregistry; + +import com.lowdragmc.lowdraglib.syncdata.ITagSerializable; + +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.common.util.INBTSerializable; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; + +@Getter +@Accessors(chain = true) +public abstract class VirtualEntry implements INBTSerializable, ITagSerializable { + + public static final String DEFAULT_COLOR = "FFFFFFFF"; + protected static final String COLOR_KEY = "color"; + protected static final String DESC_KEY = "description"; + + @Setter + @NotNull + private String description = ""; + private int color = 0xFFFFFFFF; + private String colorStr = DEFAULT_COLOR; + + public abstract EntryTypes getType(); + + public void setColor(String color) { + this.color = parseColor(color); + this.colorStr = color.toUpperCase(); + } + + public void setColor(int color) { + setColor(Integer.toHexString(color)); + } + + public static int parseColor(String colorString) { + colorString = formatColorString(colorString); + + if (colorString.length() > 8) { + colorString = colorString.substring(colorString.length() - 8); + } + + int alpha = 255; // make alpha 100% + int red = Integer.parseInt(colorString.substring(0, 2), 16); + int green = Integer.parseInt(colorString.substring(2, 4), 16); + int blue = Integer.parseInt(colorString.substring(4, 6), 16); + + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + public static @NotNull String formatColorString(String colorString) { + return String.format("%8s", colorString).replace(' ', '0').toUpperCase(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VirtualEntry other)) return false; + return this.getType() == other.getType() && this.color == other.color && + this.description.equals(other.description); + } + + @Override + public CompoundTag serializeNBT() { + var tag = new CompoundTag(); + tag.putString(COLOR_KEY, this.colorStr); + + if (!description.isEmpty()) + tag.putString(DESC_KEY, this.description); + + return tag; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + setColor(nbt.getString(COLOR_KEY)); + + if (nbt.contains(DESC_KEY)) + this.description = nbt.getString(DESC_KEY); + } + + public boolean canRemove() { + return this.description.isEmpty(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java new file mode 100644 index 0000000000..3d48f8b020 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java @@ -0,0 +1,86 @@ +package com.gregtechceu.gtceu.api.misc.virtualregistry; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.util.INBTSerializable; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class VirtualRegistryMap implements INBTSerializable { + + private final Map, Map> registryMap = new ConcurrentHashMap<>(); + + public VirtualRegistryMap() {} + + public VirtualRegistryMap(CompoundTag tag) { + deserializeNBT(tag); + } + + @SuppressWarnings("unchecked") + public @Nullable T getEntry(EntryTypes type, String name) { + return (T) registryMap.getOrDefault(type, Collections.emptyMap()).get(name); + } + + public void addEntry(String name, VirtualEntry entry) { + registryMap.computeIfAbsent(entry.getType(), k -> new ConcurrentHashMap<>()).put(name, entry); + } + + public boolean contains(EntryTypes type, String name) { + return registryMap.containsKey(type) && registryMap.get(type).containsKey(name); + } + + public void deleteEntry(EntryTypes type, String name) { + Map entries = registryMap.get(type); + if (entries != null) { + entries.remove(name); + if (entries.isEmpty()) { + registryMap.remove(type); + } + } + } + + public void clear() { + registryMap.clear(); + } + + public Set getEntryNames(EntryTypes type) { + return new HashSet<>(registryMap.getOrDefault(type, Collections.emptyMap()).keySet()); + } + + @Override + public @NotNull CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + for (Map.Entry, Map> entry : registryMap.entrySet()) { + CompoundTag entriesTag = new CompoundTag(); + for (Map.Entry subEntry : entry.getValue().entrySet()) { + entriesTag.put(subEntry.getKey(), subEntry.getValue().serializeNBT()); + } + tag.put(entry.getKey().toString(), entriesTag); + } + return tag; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + for (String entryTypeString : nbt.getAllKeys()) { + EntryTypes type = entryTypeString.contains(":") ? + EntryTypes.fromLocation(ResourceLocation.tryParse(entryTypeString)) : + EntryTypes.fromString(entryTypeString); + + if (type == null) continue; + + CompoundTag virtualEntries = nbt.getCompound(entryTypeString); + for (String name : virtualEntries.getAllKeys()) { + CompoundTag entryTag = virtualEntries.getCompound(name); + addEntry(name, type.createInstance(entryTag)); + } + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java new file mode 100644 index 0000000000..b142de09b9 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java @@ -0,0 +1,71 @@ +package com.gregtechceu.gtceu.api.misc.virtualregistry.entries; + +import com.gregtechceu.gtceu.api.misc.virtualregistry.EntryTypes; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEntry; + +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.templates.FluidTank; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +public class VirtualTank extends VirtualEntry { + + public static final int DEFAULT_CAPACITY = 160_000; // 160B for per second transfer + protected static final String CAPACITY_KEY = "capacity"; + protected static final String FLUID_KEY = "fluid"; + @NotNull + @Getter + private final FluidTank fluidTank; + private int capacity; + + public VirtualTank(int capacity) { + this.capacity = capacity; + fluidTank = new FluidTank(this.capacity); + } + + public VirtualTank() { + this(DEFAULT_CAPACITY); + } + + @Override + public EntryTypes getType() { + return EntryTypes.ENDER_FLUID; + } + + public void setFluid(FluidStack fluid) { + this.fluidTank.setFluid(fluid); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VirtualTank other)) return false; + return this.fluidTank == other.fluidTank; + } + + @Override + public CompoundTag serializeNBT() { + var tag = super.serializeNBT(); + tag.putInt(CAPACITY_KEY, this.capacity); + + if (this.fluidTank.getFluid() != FluidStack.EMPTY) + tag.put(FLUID_KEY, this.fluidTank.getFluid().writeToNBT(new CompoundTag())); + + return tag; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + super.deserializeNBT(nbt); + this.capacity = nbt.getInt(CAPACITY_KEY); + + if (nbt.contains(FLUID_KEY)) + setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound(FLUID_KEY))); + } + + @Override + public boolean canRemove() { + return super.canRemove() && this.fluidTank.isEmpty(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/common/cover/ender/AbstractEnderLinkCover.java b/src/main/java/com/gregtechceu/gtceu/common/cover/ender/AbstractEnderLinkCover.java new file mode 100644 index 0000000000..ba303a92ce --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/common/cover/ender/AbstractEnderLinkCover.java @@ -0,0 +1,516 @@ +package com.gregtechceu.gtceu.common.cover.ender; + +import com.gregtechceu.gtceu.api.capability.IControllable; +import com.gregtechceu.gtceu.api.capability.ICoverable; +import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.cover.CoverBehavior; +import com.gregtechceu.gtceu.api.cover.CoverDefinition; +import com.gregtechceu.gtceu.api.cover.IUICover; +import com.gregtechceu.gtceu.api.cover.filter.FilterHandler; +import com.gregtechceu.gtceu.api.gui.GuiTextures; +import com.gregtechceu.gtceu.api.gui.widget.ColorBlockWidget; +import com.gregtechceu.gtceu.api.gui.widget.ConfirmTextInputWidget; +import com.gregtechceu.gtceu.api.gui.widget.EnumSelectorWidget; +import com.gregtechceu.gtceu.api.gui.widget.ToggleButtonWidget; +import com.gregtechceu.gtceu.api.machine.ConditionalSubscriptionHandler; +import com.gregtechceu.gtceu.api.machine.MachineCoverContainer; +import com.gregtechceu.gtceu.api.misc.virtualregistry.EntryTypes; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEnderRegistry; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEntry; +import com.gregtechceu.gtceu.api.misc.virtualregistry.entries.VirtualTank; +import com.gregtechceu.gtceu.common.cover.data.ManualIOMode; +import com.gregtechceu.gtceu.common.machine.owner.IMachineOwner; + +import com.lowdragmc.lowdraglib.gui.editor.ColorPattern; +import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; +import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; +import com.lowdragmc.lowdraglib.gui.texture.TextTexture; +import com.lowdragmc.lowdraglib.gui.widget.*; +import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced; +import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; +import com.lowdragmc.lowdraglib.syncdata.annotation.RequireRerender; +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +import lombok.Getter; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Pattern; + +@SuppressWarnings("SameParameterValue") +public abstract class AbstractEnderLinkCover extends CoverBehavior + implements IUICover, IControllable { + + public static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder(AbstractEnderLinkCover.class, + CoverBehavior.MANAGED_FIELD_HOLDER); + public static final Pattern COLOR_INPUT_PATTERN = Pattern.compile("[0-9a-fA-F]*"); + + protected final ConditionalSubscriptionHandler subscriptionHandler; + + @Persisted + @DescSynced + protected String colorStr = VirtualEntry.DEFAULT_COLOR; + @Getter + @Persisted + @DescSynced + protected Permissions permission = Permissions.PUBLIC; + @Persisted + @Getter + protected boolean isWorkingEnabled = true; + @Getter + @Persisted + @DescSynced + protected ManualIOMode manualIOMode = ManualIOMode.DISABLED; + @Getter + @Persisted + @DescSynced + @RequireRerender + protected IO io = IO.OUT; + protected VirtualEntryWidget virtualEntryWidget; + @DescSynced + boolean isAnyChanged = false; + @Persisted + @DescSynced + private IMachineOwner owner; + + public AbstractEnderLinkCover(CoverDefinition definition, ICoverable coverHolder, Direction attachedSide) { + super(definition, coverHolder, attachedSide); + subscriptionHandler = new ConditionalSubscriptionHandler(coverHolder, this::update, this::isSubscriptionActive); + } + + @Override + public void onLoad() { + super.onLoad(); + subscriptionHandler.initialize(coverHolder.getLevel()); + } + + @Override + public abstract boolean canAttach(); + + @Override + public void onAttached(@NotNull ItemStack itemStack, @NotNull ServerPlayer player) { + super.onAttached(itemStack, player); + if (coverHolder instanceof MachineCoverContainer mcc) { + var owner = mcc.getMachine().getHolder().getOwner(); + if (owner != null) this.owner = owner; + } + } + + @Override + public void onRemoved() { + super.onRemoved(); + subscriptionHandler.unsubscribe(); + if (!isRemote()) { + VirtualEnderRegistry.getInstance() + .deleteEntryIf(getOwner(), getEntryType(), getChannelName(), VirtualEntry::canRemove); + } + } + + @Override + public void onUnload() { + super.onUnload(); + subscriptionHandler.unsubscribe(); + if (!isRemote()) { + VirtualEnderRegistry.getInstance() + .deleteEntryIf(getOwner(), getEntryType(), getChannelName(), VirtualEntry::canRemove); + } + } + + @Override + public void onUIClosed() { + virtualEntryWidget = null; + } + + @Override + public void setWorkingEnabled(boolean isWorkingAllowed) { + if (this.isWorkingEnabled != isWorkingAllowed) { + this.isWorkingEnabled = isWorkingAllowed; + subscriptionHandler.updateSubscription(); + } + } + + @Override + public Widget createUIWidget() { + virtualEntryWidget = new VirtualEntryWidget(this); + return virtualEntryWidget; + } + + @Override + public @NotNull ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + public void setIo(IO io) { + if (io == IO.IN || io == IO.OUT) { + this.io = io; + subscriptionHandler.updateSubscription(); + } + } + + public UUID getOwner() { + return permission == Permissions.PRIVATE ? owner.getPlayerUUID() : null; + } + + protected boolean isSubscriptionActive() { + return isWorkingEnabled(); + } + + protected abstract String identifier(); + + protected abstract VirtualEntry getEntry(); + + protected abstract void setEntry(VirtualEntry entry); + + protected final String getChannelName() { + return identifier() + this.colorStr; + } + + protected void setChannelName(String name) { + if (isRemote()) return; + VirtualEnderRegistry.getInstance().deleteEntryIf(getOwner(), getEntryType(), getChannelName(), + VirtualEntry::canRemove); + this.colorStr = VirtualEntry.formatColorString(name); + setVirtualEntry(); + } + + protected final String getChannelName(VirtualEntry entry) { + return identifier() + entry.getColorStr(); + } + + protected void setPermission(Permissions permission) { + if (isRemote()) return; + VirtualEnderRegistry.getInstance().deleteEntryIf(getOwner(), getEntryType(), getChannelName(), + VirtualEntry::canRemove); + this.permission = permission; + setVirtualEntry(); + } + + protected void setVirtualEntry() { + setEntry(VirtualEnderRegistry.getInstance().getOrCreateEntry(getOwner(), getEntryType(), getChannelName())); + getEntry().setColor(this.colorStr); + this.isAnyChanged = true; + subscriptionHandler.updateSubscription(); + } + + protected abstract EntryTypes getEntryType(); + + protected void update() { + long timer = coverHolder.getOffsetTimer(); + if (timer % 5 != 0) return; + + if (isWorkingEnabled() && !isRemote()) { + var entry = VirtualEnderRegistry.getInstance().getOrCreateEntry(getOwner(), getEntryType(), + getChannelName()); + if (!entry.getColorStr().equals(this.colorStr)) { + entry.setColor(this.colorStr); + } + if (!getEntry().equals(entry)) { + setEntry(entry); + } + transfer(); + } + + if (isAnyChanged) { + if (virtualEntryWidget != null) virtualEntryWidget.update(); + isAnyChanged = false; + } + subscriptionHandler.updateSubscription(); + } + + protected abstract void transfer(); + + protected void setManualIOMode(ManualIOMode manualIOMode) { + this.manualIOMode = manualIOMode; + subscriptionHandler.updateSubscription(); + } + + @Nullable + protected FilterHandler getFilterHandler() { + return null; + } + + protected abstract Widget addVirtualEntryWidget(VirtualEntry entry, int x, int y, int width, int height, + boolean canClick); + + protected abstract String getUITitle(); + + protected int getColor() { + return VirtualEntry.parseColor(this.colorStr); + } + + protected static class VirtualEntryWidget extends WidgetGroup { + + private static final int WIDGET_BOARD = 20; + private static final int GROUP_WIDTH = 176; + private static final int TOTAL_WIDTH = 156; + private static final int BUTTON_SIZE = 16; + private final AbstractEnderLinkCover cover; + private final MutableBoolean showChannels; + private final WidgetGroup mainGroup; + private final WidgetGroup mainChannelGroup; + private final DraggableScrollableWidgetGroup channelsGroup; // client only + + VirtualEntryWidget(AbstractEnderLinkCover cover) { + super(0, 0, GROUP_WIDTH, 137); + this.cover = cover; + this.showChannels = new MutableBoolean(false); + mainGroup = new WidgetGroup(0, 0, GROUP_WIDTH, 137); + channelsGroup = new DraggableScrollableWidgetGroup(0, 20, 170, 110) + .setYScrollBarWidth(2).setYBarStyle(null, ColorPattern.T_WHITE.rectTexture().setRadius(1)); + mainChannelGroup = new WidgetGroup(10, 20, 156, 20); + initWidgets(); + } + + public void update() { + if (isRemote()) return; + widgets.clear(); + mainGroup.widgets.clear(); + channelsGroup.widgets.clear(); + mainChannelGroup.widgets.clear(); + initWidgets(); + this.detectAndSendChanges(); + } + + private void initWidgets() { + int currentX = 0; + final var titleGroup = new WidgetGroup(10, 5, GROUP_WIDTH, 20); + + this.addWidget(titleGroup); + this.addWidget(mainGroup); + this.addWidget(channelsGroup.setVisible(false)); + + titleGroup.addWidget(createToggleButton()); + titleGroup.addWidget(new LabelWidget(15, 3, cover.getUITitle())); + + var toggleButtonWidget = createToggleButtonForPrivacy(currentX); + mainChannelGroup.addWidget(toggleButtonWidget); + currentX += WIDGET_BOARD + 2; + mainChannelGroup.addWidget(createColorBlockWidget(currentX)); + currentX += WIDGET_BOARD + 2; + mainChannelGroup.addWidget(createConfirmTextInputWidget(currentX)); + + mainChannelGroup.addWidget(new ConfirmTextInputWidget(0, WIDGET_BOARD + 2, GROUP_WIDTH - WIDGET_BOARD, + WIDGET_BOARD, cover.getEntry().getDescription(), cover.getEntry()::setDescription, + t -> t == null ? "" : t).setTooltip("cover.ender_fluid_link.tooltip.channel_description")); + + mainGroup.addWidget(mainChannelGroup); + mainGroup.addWidget(createWorkingEnabledButton()); + addEnumSelectorWidgets(); + mainGroup.addWidget( + cover.addVirtualEntryWidget(cover.getEntry(), 146, WIDGET_BOARD, WIDGET_BOARD, WIDGET_BOARD, true)); + + if (cover.getFilterHandler() != null) { + mainGroup.addWidget(cover.getFilterHandler().createFilterSlotUI(117, 108)); + mainGroup.addWidget(cover.getFilterHandler().createFilterConfigUI(10, 72, 156, 60)); + } + } + + @Contract(" -> new") + private @NotNull ToggleButtonWidget createToggleButton() { + return (ToggleButtonWidget) new ToggleButtonWidget(0, 0, 12, 12, showChannels::getValue, cd -> { + showChannels.setValue(!showChannels.getValue()); + mainGroup.setVisible(showChannels.isFalse()); + channelsGroup.setVisible(showChannels.isTrue()); + requestUpdate(); + }).setTexture( + new GuiTextureGroup(GuiTextures.TOGGLE_BUTTON_BACK.getSubTexture(0, 0, 1, 0.5), + GuiTextures.BUTTON_LIST), + new GuiTextureGroup(GuiTextures.TOGGLE_BUTTON_BACK.getSubTexture(0, 0.5, 1, 0.5), + GuiTextures.BUTTON_LIST)) + .setHoverTooltips("cover.ender_fluid_link.tooltip.list_button"); + } + + @Contract("_ -> new") + private @NotNull Widget createToggleButtonForPrivacy(int currentX) { + return new EnumSelectorWidget<>(currentX, 0, + WIDGET_BOARD, WIDGET_BOARD, Permissions.values(), cover.permission, cover::setPermission); + } + + private ColorBlockWidget createColorBlockWidget(int currentX) { + return new ColorBlockWidget(currentX, 0, WIDGET_BOARD, WIDGET_BOARD).setColorSupplier(cover::getColor); + } + + private ConfirmTextInputWidget createConfirmTextInputWidget(int currentX) { + int GROUP_X = 10; + int textInputWidth = (GROUP_WIDTH - GROUP_X * 2) - currentX - WIDGET_BOARD - 2; + return new ConfirmTextInputWidget(currentX, 0, textInputWidth, WIDGET_BOARD, cover.colorStr, + cover::setChannelName, text -> { + if (text == null || !COLOR_INPUT_PATTERN.matcher(text).matches() || text.length() > 8) { + return VirtualTank.DEFAULT_COLOR; + } + return text; + }).setTooltip("cover.ender_fluid_link.tooltip.channel_name"); + } + + @Contract(" -> new") + private @NotNull ToggleButtonWidget createWorkingEnabledButton() { + return new ToggleButtonWidget(116, 82, WIDGET_BOARD, WIDGET_BOARD, GuiTextures.BUTTON_POWER, + cover::isWorkingEnabled, cover::setWorkingEnabled); + } + + private void addEnumSelectorWidgets() { + mainGroup.addWidget(new EnumSelectorWidget<>(146, 82, WIDGET_BOARD, WIDGET_BOARD, List.of(IO.IN, IO.OUT), + cover.io, cover::setIo)); + mainGroup.addWidget(new EnumSelectorWidget<>(146, 107, WIDGET_BOARD, WIDGET_BOARD, ManualIOMode.VALUES, + cover.manualIOMode, cover::setManualIOMode) + .setHoverTooltips("cover.universal.manual_import_export.mode.description")); + } + + private void addChannelWidgets(List entries) { + channelsGroup.clearAllWidgets(); + int y = 1; + SelectableWidgetGroup selectedWidget = null; + for (var entry : entries.stream().sorted(Comparator.comparing(VirtualEntry::getColorStr)).toList()) { + SelectableWidgetGroup channelWidget = createChannelWidget(entry, 10, y); + if (cover.getChannelName(entry).equals(cover.getChannelName())) { + selectedWidget = channelWidget; + } + channelsGroup.addWidget(channelWidget); + y += 22; + } + channelsGroup.setSelected(selectedWidget); + if (selectedWidget != null) selectedWidget.onSelected(); + channelsGroup.setClientSideWidget(); + } + + private @NotNull SelectableWidgetGroup createChannelWidget(@NotNull VirtualEntry entry, int x, int y) { + int currentX = 0; + int MARGIN = 2; + int availableWidth = TOTAL_WIDTH - (BUTTON_SIZE + MARGIN) * 3; + + final MutableBoolean canSelect = new MutableBoolean(false); + var des = entry.getDescription(); + TextBoxWidget textBoxWidget = new TextBoxWidget(BUTTON_SIZE + MARGIN, + !des.isEmpty() ? 0 : 4, availableWidth, List.of(entry.getColorStr())).setCenter(true); + SelectableWidgetGroup channelGroup = new SelectableWidgetGroup(x, y, TOTAL_WIDTH, BUTTON_SIZE) { + + @Override + public boolean allowSelected(double mouseX, double mouseY, int button) { + return canSelect.getValue() && super.allowSelected(mouseX, mouseY, button); + } + }; + channelGroup.setOnSelected(group -> { + if (cover.getChannelName().equals(cover.getChannelName(entry))) return; + writeClientAction(0, buffer -> { + // send new channel name to server + String newChannelColorStr = entry.getColorStr(); + buffer.writeUtf(newChannelColorStr); + }); + playButtonClickSound(); + }).setSelectedTexture(1, -1); + + // Color block + ColorBlockWidget colorBlockWidget = new ColorBlockWidget(currentX, 0, BUTTON_SIZE, BUTTON_SIZE) + .setCurrentColor(VirtualEntry.parseColor(entry.getColorStr())); + channelGroup.addWidget(colorBlockWidget); + currentX += BUTTON_SIZE + MARGIN; + + // Text box + channelGroup.addWidget(textBoxWidget); + currentX += availableWidth + MARGIN; + if (!des.isEmpty()) { + var desText = new TextTexture(ChatFormatting.DARK_GRAY + des).setDropShadow(false); + desText.setType(TextTexture.TextType.ROLL).setRollSpeed(0.7f); + channelGroup.addWidget(new ImageWidget(BUTTON_SIZE + MARGIN, 10, availableWidth, 8, desText)); + } + + // Slot + Widget slotWidget = cover.addVirtualEntryWidget(entry, currentX, 0, BUTTON_SIZE, BUTTON_SIZE, false); + channelGroup.addWidget(slotWidget); + currentX += BUTTON_SIZE + MARGIN; + + // Clear Description button + channelGroup.addWidget( + new ButtonWidget(currentX, 0, BUTTON_SIZE, BUTTON_SIZE, GuiTextures.BUTTON_CLEAR_GRID, press -> { + writeClientAction(200, buffer -> buffer.writeUtf(cover.getChannelName(entry))); + requestUpdate(); + }) { + + @Override + public boolean isMouseOverElement(double mouseX, double mouseY) { + var isOver = super.isMouseOverElement(mouseX, mouseY); + if (canSelect.getValue() == isOver) canSelect.setValue(!isOver); + return isOver; + } + }.appendHoverTooltips("cover.ender_fluid_link.tooltip.clear_button")); + + return channelGroup; + } + + private void requestUpdate() { + writeClientAction(100, buffer -> buffer.writeBoolean(showChannels.isTrue())); + } + + @Override + public void handleClientAction(int id, FriendlyByteBuf buffer) { + super.handleClientAction(id, buffer); + if (id == 0) { + String newChannelColorStr = buffer.readUtf(); + cover.setChannelName(newChannelColorStr); + } else if (id == 100) { + if (!buffer.readBoolean()) return; + var entries = VirtualEnderRegistry.getInstance().getEntryNames(cover.getOwner(), cover.getEntryType()) + .stream().map(name -> VirtualEnderRegistry.getInstance().getEntry(cover.getOwner(), + cover.getEntryType(), name)) + .sorted(Comparator.comparing(VirtualEntry::getColorStr)); + writeUpdateInfo(101, buf -> { + var list = entries.toList(); + buf.writeVarInt(list.size()); + for (var entry : list) { + buf.writeNbt(entry.serializeNBT()); + } + }); + } else if (id == 200) { + String channelName = buffer.readUtf(); + VirtualEnderRegistry.getInstance().getEntry(cover.getOwner(), cover.getEntryType(), channelName) + .setDescription(""); + } + } + + @Override + public void readUpdateInfo(int id, FriendlyByteBuf buffer) { + super.readUpdateInfo(id, buffer); + if (id == 101) { + int size = buffer.readVarInt(); + List entries = new ArrayList<>(); + for (int i = 0; i < size; i++) { + VirtualEntry entry = cover.getEntryType().createInstance(); + entry.deserializeNBT(Objects.requireNonNull(buffer.readNbt())); + entries.add(entry); + } + addChannelWidgets(entries); + } + } + } + + protected enum Permissions implements EnumSelectorWidget.SelectableEnum { + + PUBLIC, + PRIVATE; + + @Override + public @NotNull String getTooltip() { + return switch (this) { + case PUBLIC -> "cover.ender_fluid_link.private.tooltip.disabled"; + case PRIVATE -> "cover.ender_fluid_link.private.tooltip.enabled"; + }; + } + + @Override + public @NotNull IGuiTexture getIcon() { + return switch (this) { + case PUBLIC -> GuiTextures.BUTTON_PUBLIC_PRIVATE.getSubTexture(0, 0, 1, 0.5); + case PRIVATE -> GuiTextures.BUTTON_PUBLIC_PRIVATE.getSubTexture(0, 0.5, 1, 0.5); + }; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/common/cover/ender/EnderFluidLinkCover.java b/src/main/java/com/gregtechceu/gtceu/common/cover/ender/EnderFluidLinkCover.java new file mode 100644 index 0000000000..724a52cd72 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/common/cover/ender/EnderFluidLinkCover.java @@ -0,0 +1,137 @@ +package com.gregtechceu.gtceu.common.cover.ender; + +import com.gregtechceu.gtceu.api.capability.ICoverable; +import com.gregtechceu.gtceu.api.cover.CoverDefinition; +import com.gregtechceu.gtceu.api.cover.filter.FilterHandler; +import com.gregtechceu.gtceu.api.cover.filter.FilterHandlers; +import com.gregtechceu.gtceu.api.cover.filter.FluidFilter; +import com.gregtechceu.gtceu.api.gui.GuiTextures; +import com.gregtechceu.gtceu.api.gui.widget.TankWidget; +import com.gregtechceu.gtceu.api.misc.virtualregistry.EntryTypes; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEnderRegistry; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEntry; +import com.gregtechceu.gtceu.api.misc.virtualregistry.entries.VirtualTank; +import com.gregtechceu.gtceu.api.transfer.fluid.IFluidHandlerModifiable; +import com.gregtechceu.gtceu.utils.GTTransferUtils; + +import com.lowdragmc.lowdraglib.gui.widget.*; +import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced; +import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.core.Direction; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +public class EnderFluidLinkCover extends AbstractEnderLinkCover { + + public static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder(EnderFluidLinkCover.class, + AbstractEnderLinkCover.MANAGED_FIELD_HOLDER); + public static final int TRANSFER_RATE = 8000; // mB/t + + @Persisted + @DescSynced + protected VirtualTank visualTank; + @Persisted + @DescSynced + protected final FilterHandler filterHandler; + protected int mBLeftToTransferLastSecond; + + public EnderFluidLinkCover(CoverDefinition definition, ICoverable coverHolder, Direction attachedSide) { + super(definition, coverHolder, attachedSide); + this.mBLeftToTransferLastSecond = TRANSFER_RATE * 20; + filterHandler = FilterHandlers.fluid(this); + if (!isRemote()) visualTank = VirtualEnderRegistry.getInstance() + .getOrCreateEntry(getOwner(), EntryTypes.ENDER_FLUID, getChannelName()); + } + + @Override + protected VirtualTank getEntry() { + return visualTank; + } + + @Override + protected void setEntry(VirtualEntry entry) { + visualTank = (VirtualTank) entry; + } + + @Override + public boolean canAttach() { + return FluidUtil.getFluidHandler(coverHolder.getLevel(), coverHolder.getPos(), attachedSide).isPresent(); + } + + @Override + protected EntryTypes getEntryType() { + return EntryTypes.ENDER_FLUID; + } + + @Override + protected String identifier() { + return "EFLink#"; + } + + @Override + protected void transfer() { + long timer = coverHolder.getOffsetTimer(); + if (mBLeftToTransferLastSecond > 0) { + int platformTransferredFluid = doTransferFluids(mBLeftToTransferLastSecond); + this.mBLeftToTransferLastSecond -= platformTransferredFluid; + } + + if (timer % 20 == 0) { + this.mBLeftToTransferLastSecond = TRANSFER_RATE * 20; + } + } + + protected @Nullable IFluidHandlerModifiable getOwnFluidHandler() { + return coverHolder.getFluidHandlerCap(attachedSide, false); + } + + private int doTransferFluids(int platformTransferLimit) { + var ownFluidHandler = getOwnFluidHandler(); + + if (ownFluidHandler != null) { + return switch (io) { + case IN -> GTTransferUtils.transferFluidsFiltered(ownFluidHandler, visualTank.getFluidTank(), + filterHandler.getFilter(), platformTransferLimit); + case OUT -> GTTransferUtils.transferFluidsFiltered(visualTank.getFluidTank(), ownFluidHandler, + filterHandler.getFilter(), platformTransferLimit); + default -> 0; + }; + + } + return 0; + } + + @Override + public @NotNull ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + ////////////////////////////////////// + // *********** GUI ************ // + /// /////////////////////////////////// + + @Override + protected Widget addVirtualEntryWidget(VirtualEntry entry, int x, int y, int width, int height, boolean canClick) { + return new TankWidget(((VirtualTank) entry).getFluidTank(), 0, x, y, width, height, canClick, canClick) + .setBackground(GuiTextures.FLUID_SLOT); + } + + @NotNull + @Override + protected String getUITitle() { + return "cover.ender_fluid_link.title"; + } + + @Override + protected FilterHandler getFilterHandler() { + return filterHandler; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/common/data/GTCovers.java b/src/main/java/com/gregtechceu/gtceu/common/data/GTCovers.java index 36fb5c432c..f9af96131f 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/data/GTCovers.java +++ b/src/main/java/com/gregtechceu/gtceu/common/data/GTCovers.java @@ -10,6 +10,7 @@ import com.gregtechceu.gtceu.client.renderer.cover.*; import com.gregtechceu.gtceu.common.cover.*; import com.gregtechceu.gtceu.common.cover.detector.*; +import com.gregtechceu.gtceu.common.cover.ender.EnderFluidLinkCover; import com.gregtechceu.gtceu.common.cover.voiding.AdvancedFluidVoidingCover; import com.gregtechceu.gtceu.common.cover.voiding.AdvancedItemVoidingCover; import com.gregtechceu.gtceu.common.cover.voiding.FluidVoidingCover; @@ -54,6 +55,10 @@ public class GTCovers { "infinite_water", InfiniteWaterCover::new, new SimpleCoverRenderer(GTCEu.id("block/cover/overlay_infinite_water"))); + public final static CoverDefinition ENDER_FLUID_LINK = register( + "ender_fluid_link", EnderFluidLinkCover::new, + new SimpleCoverRenderer(GTCEu.id("block/cover/overlay_ender_fluid_link"))); + public final static CoverDefinition SHUTTER = register( "shutter", ShutterCover::new, new SimpleCoverRenderer(GTCEu.id("block/cover/overlay_shutter"))); diff --git a/src/main/java/com/gregtechceu/gtceu/common/data/GTItems.java b/src/main/java/com/gregtechceu/gtceu/common/data/GTItems.java index 3d6a960319..8cbd22515a 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/data/GTItems.java +++ b/src/main/java/com/gregtechceu/gtceu/common/data/GTItems.java @@ -1958,6 +1958,7 @@ public Component getItemName(ItemStack stack) { public static ItemEntry COVER_ENDER_FLUID_LINK = REGISTRATE .item("ender_fluid_link_cover", ComponentItem::create) .lang("Ender Fluid Link") + .onRegister(attach(new CoverPlaceBehavior(GTCovers.ENDER_FLUID_LINK))) .register(); public static ItemEntry COVER_FLUID_VOIDING = REGISTRATE .item("fluid_voiding_cover", ComponentItem::create) diff --git a/src/main/java/com/gregtechceu/gtceu/common/data/GTSyncedFieldAccessors.java b/src/main/java/com/gregtechceu/gtceu/common/data/GTSyncedFieldAccessors.java index 0216883d2e..b7d205ee27 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/data/GTSyncedFieldAccessors.java +++ b/src/main/java/com/gregtechceu/gtceu/common/data/GTSyncedFieldAccessors.java @@ -2,13 +2,12 @@ import com.gregtechceu.gtceu.api.data.chemical.material.Material; import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import com.gregtechceu.gtceu.syncdata.FluidStackPayload; -import com.gregtechceu.gtceu.syncdata.GTRecipePayload; -import com.gregtechceu.gtceu.syncdata.GTRecipeTypeAccessor; -import com.gregtechceu.gtceu.syncdata.MaterialPayload; +import com.gregtechceu.gtceu.common.machine.owner.IMachineOwner; +import com.gregtechceu.gtceu.syncdata.*; import com.lowdragmc.lowdraglib.syncdata.IAccessor; import com.lowdragmc.lowdraglib.syncdata.payload.FriendlyBufPayload; +import com.lowdragmc.lowdraglib.syncdata.payload.NbtTagPayload; import net.minecraftforge.fluids.FluidStack; @@ -25,7 +24,9 @@ public class GTSyncedFieldAccessors { public static void init() { register(FriendlyBufPayload.class, FriendlyBufPayload::new, GT_RECIPE_TYPE_ACCESSOR, 1000); + register(NbtTagPayload.class, NbtTagPayload::new, VirtualTankAccessor.INSTANCE, 2); + registerSimple(MachineOwnerPayload.class, MachineOwnerPayload::new, IMachineOwner.class, 1); registerSimple(MaterialPayload.class, MaterialPayload::new, Material.class, 1); registerSimple(GTRecipePayload.class, GTRecipePayload::new, GTRecipe.class, 100); registerSimple(FluidStackPayload.class, FluidStackPayload::new, FluidStack.class, -1); diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/owner/IMachineOwner.java b/src/main/java/com/gregtechceu/gtceu/common/machine/owner/IMachineOwner.java index 01107662b5..9819376a2e 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/owner/IMachineOwner.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/owner/IMachineOwner.java @@ -11,6 +11,7 @@ import lombok.Getter; import java.util.List; +import java.util.UUID; import java.util.function.BooleanSupplier; public sealed interface IMachineOwner permits PlayerOwner, ArgonautsOwner, FTBOwner { @@ -23,6 +24,8 @@ public sealed interface IMachineOwner permits PlayerOwner, ArgonautsOwner, FTBOw void displayInfo(List compList); + UUID getPlayerUUID(); + static IMachineOwner create(CompoundTag tag) { MachineOwnerType type = MachineOwnerType.VALUES[tag.getInt("type")]; if (!type.isAvailable()) { @@ -77,12 +80,12 @@ enum MachineOwnerType { @Getter private final String name; - private MachineOwnerType(BooleanSupplier availabilitySupplier, String name) { + MachineOwnerType(BooleanSupplier availabilitySupplier, String name) { this.availabilitySupplier = availabilitySupplier; this.name = name; } - private MachineOwnerType() { + MachineOwnerType() { this.available = true; this.name = "Player"; } diff --git a/src/main/java/com/gregtechceu/gtceu/data/lang/LangHandler.java b/src/main/java/com/gregtechceu/gtceu/data/lang/LangHandler.java index ecd18e26a5..d0bb2f59f4 100644 --- a/src/main/java/com/gregtechceu/gtceu/data/lang/LangHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/data/lang/LangHandler.java @@ -443,6 +443,10 @@ public static void init(RegistrateLangProvider provider) { provider.add("cover.ender_fluid_link.title", "Ender Fluid Link"); provider.add("cover.ender_fluid_link.iomode.enabled", "I/O Enabled"); provider.add("cover.ender_fluid_link.iomode.disabled", "I/O Disabled"); + provider.add("cover.ender_fluid_link.tooltip.channel_description", "Set channel description with input text"); + provider.add("cover.ender_fluid_link.tooltip.channel_name", "Set channel name with input text"); + provider.add("cover.ender_fluid_link.tooltip.list_button", "Show channel list"); + provider.add("cover.ender_fluid_link.tooltip.clear_button", "Clear channel description"); multilineLang(provider, "cover.ender_fluid_link.private.tooltip.disabled", "Switch to private tank mode\nPrivate mode uses the player who originally placed the cover"); provider.add("cover.ender_fluid_link.private.tooltip.enabled", "Switch to public tank mode"); diff --git a/src/main/java/com/gregtechceu/gtceu/forge/ForgeCommonEventListener.java b/src/main/java/com/gregtechceu/gtceu/forge/ForgeCommonEventListener.java index 2edfabfc9e..d13161c3a5 100644 --- a/src/main/java/com/gregtechceu/gtceu/forge/ForgeCommonEventListener.java +++ b/src/main/java/com/gregtechceu/gtceu/forge/ForgeCommonEventListener.java @@ -21,6 +21,7 @@ import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.feature.IInteractedMachine; import com.gregtechceu.gtceu.api.misc.forge.FilteredFluidHandlerItemStack; +import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEnderRegistry; import com.gregtechceu.gtceu.api.pattern.MultiblockWorldSavedData; import com.gregtechceu.gtceu.api.registry.GTRegistries; import com.gregtechceu.gtceu.common.capability.EnvironmentalHazardSavedData; @@ -37,7 +38,10 @@ import com.gregtechceu.gtceu.common.item.armor.IJetpack; import com.gregtechceu.gtceu.common.machine.owner.IMachineOwner; import com.gregtechceu.gtceu.common.network.GTNetwork; -import com.gregtechceu.gtceu.common.network.packets.*; +import com.gregtechceu.gtceu.common.network.packets.SPacketSendWorldID; +import com.gregtechceu.gtceu.common.network.packets.SPacketSyncBedrockOreVeins; +import com.gregtechceu.gtceu.common.network.packets.SPacketSyncFluidVeins; +import com.gregtechceu.gtceu.common.network.packets.SPacketSyncOreVeins; import com.gregtechceu.gtceu.common.network.packets.hazard.SPacketAddHazardZone; import com.gregtechceu.gtceu.common.network.packets.hazard.SPacketRemoveHazardZone; import com.gregtechceu.gtceu.common.network.packets.hazard.SPacketSyncLevelHazards; @@ -325,6 +329,7 @@ public static void serverStarting(ServerStartingEvent event) { @SubscribeEvent public static void serverStopped(ServerStoppedEvent event) { ServerCache.instance.clear(); + VirtualEnderRegistry.release(); } @SubscribeEvent diff --git a/src/main/java/com/gregtechceu/gtceu/syncdata/MachineOwnerPayload.java b/src/main/java/com/gregtechceu/gtceu/syncdata/MachineOwnerPayload.java new file mode 100644 index 0000000000..ca7fcd15c7 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/syncdata/MachineOwnerPayload.java @@ -0,0 +1,23 @@ +package com.gregtechceu.gtceu.syncdata; + +import com.gregtechceu.gtceu.common.machine.owner.IMachineOwner; + +import com.lowdragmc.lowdraglib.syncdata.payload.ObjectTypedPayload; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; + +import org.jetbrains.annotations.Nullable; + +public class MachineOwnerPayload extends ObjectTypedPayload { + + @Override + public @Nullable Tag serializeNBT() { + return payload.write(); + } + + @Override + public void deserializeNBT(Tag tag) { + payload = IMachineOwner.create((CompoundTag) tag); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/syncdata/VirtualTankAccessor.java b/src/main/java/com/gregtechceu/gtceu/syncdata/VirtualTankAccessor.java new file mode 100644 index 0000000000..f469f0f5d1 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/syncdata/VirtualTankAccessor.java @@ -0,0 +1,31 @@ +package com.gregtechceu.gtceu.syncdata; + +import com.gregtechceu.gtceu.api.misc.virtualregistry.entries.VirtualTank; + +import com.lowdragmc.lowdraglib.syncdata.AccessorOp; +import com.lowdragmc.lowdraglib.syncdata.accessor.CustomObjectAccessor; +import com.lowdragmc.lowdraglib.syncdata.payload.ITypedPayload; +import com.lowdragmc.lowdraglib.syncdata.payload.NbtTagPayload; + +import net.minecraft.nbt.CompoundTag; + +public class VirtualTankAccessor extends CustomObjectAccessor { + + public static final VirtualTankAccessor INSTANCE = new VirtualTankAccessor(); + + protected VirtualTankAccessor() { + super(VirtualTank.class, true); + } + + @Override + public ITypedPayload serialize(AccessorOp op, VirtualTank value) { + return NbtTagPayload.of(value.serializeNBT()); + } + + @Override + public VirtualTank deserialize(AccessorOp op, ITypedPayload payload) { + var tank = new VirtualTank(); + tank.deserializeNBT((CompoundTag) payload.getPayload()); + return tank; + } +} diff --git a/src/main/resources/assets/gtceu/textures/gui/widget/button_check.png b/src/main/resources/assets/gtceu/textures/gui/widget/button_check.png new file mode 100644 index 0000000000..7c3435aefb Binary files /dev/null and b/src/main/resources/assets/gtceu/textures/gui/widget/button_check.png differ diff --git a/src/main/resources/assets/gtceu/textures/gui/widget/button_list.png b/src/main/resources/assets/gtceu/textures/gui/widget/button_list.png new file mode 100644 index 0000000000..59194f43cc Binary files /dev/null and b/src/main/resources/assets/gtceu/textures/gui/widget/button_list.png differ