aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/kubatech/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/kubatech/api')
-rw-r--r--src/main/java/kubatech/api/DynamicInventory.java469
-rw-r--r--src/main/java/kubatech/api/EIGDynamicInventory.java510
-rw-r--r--src/main/java/kubatech/api/IBlockStemAccesor.java8
-rw-r--r--src/main/java/kubatech/api/Variables.java63
-rw-r--r--src/main/java/kubatech/api/eig/EIGBucket.java247
-rw-r--r--src/main/java/kubatech/api/eig/EIGDropTable.java224
-rw-r--r--src/main/java/kubatech/api/eig/EIGMode.java154
-rw-r--r--src/main/java/kubatech/api/eig/IEIGBucketFactory.java15
-rw-r--r--src/main/java/kubatech/api/enums/EIGModes.java42
-rw-r--r--src/main/java/kubatech/api/enums/EIGSetupPhase.java16
-rw-r--r--src/main/java/kubatech/api/enums/ItemList.java232
-rw-r--r--src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java72
-rw-r--r--src/main/java/kubatech/api/helpers/GTHelper.java99
-rw-r--r--src/main/java/kubatech/api/helpers/ReflectionHelper.java205
-rw-r--r--src/main/java/kubatech/api/helpers/UUIDFinder.java43
-rw-r--r--src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java346
-rw-r--r--src/main/java/kubatech/api/tea/TeaNetwork.java93
-rw-r--r--src/main/java/kubatech/api/tileentity/CustomTileEntityPacketHandler.java28
-rw-r--r--src/main/java/kubatech/api/utils/ItemUtils.java26
-rw-r--r--src/main/java/kubatech/api/utils/ModUtils.java38
-rw-r--r--src/main/java/kubatech/api/utils/StringUtils.java58
21 files changed, 2988 insertions, 0 deletions
diff --git a/src/main/java/kubatech/api/DynamicInventory.java b/src/main/java/kubatech/api/DynamicInventory.java
new file mode 100644
index 0000000000..ef89c3a341
--- /dev/null
+++ b/src/main/java/kubatech/api/DynamicInventory.java
@@ -0,0 +1,469 @@
+package kubatech.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+
+import org.lwjgl.opengl.GL11;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.internal.Theme;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.ChangeableWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import kubatech.api.helpers.GTHelper;
+import kubatech.api.utils.ModUtils;
+
+public class DynamicInventory<T> {
+
+ int width, height;
+ Supplier<Integer> slotsGetter;
+ private int slots = 0;
+ private int usedSlots = 0;
+ List<T> inventory;
+ TInventoryGetter<T> inventoryGetter;
+ TInventoryInjector inventoryInjector = null;
+ TInventoryExtractor<T> inventoryExtractor = null;
+ TInventoryReplacerOrMerger inventoryReplacer = null;
+ Supplier<Boolean> isEnabledGetter = null;
+ boolean isEnabled = true;
+
+ public DynamicInventory(int width, int height, Supplier<Integer> slotsGetter, List<T> inventory,
+ TInventoryGetter<T> inventoryGetter) {
+ this.width = width;
+ this.height = height;
+ this.slotsGetter = slotsGetter;
+ this.inventory = inventory;
+ this.inventoryGetter = inventoryGetter;
+ }
+
+ public DynamicInventory<T> allowInventoryInjection(TInventoryInjector inventoryInjector) {
+ this.inventoryInjector = inventoryInjector;
+ return this;
+ }
+
+ public DynamicInventory<T> allowInventoryExtraction(TInventoryExtractor<T> inventoryExtractor) {
+ this.inventoryExtractor = inventoryExtractor;
+ return this;
+ }
+
+ public DynamicInventory<T> allowInventoryReplace(TInventoryReplacerOrMerger inventoryReplacer) {
+ this.inventoryReplacer = inventoryReplacer;
+ return this;
+ }
+
+ public DynamicInventory<T> setEnabled(Supplier<Boolean> isEnabled) {
+ this.isEnabledGetter = isEnabled;
+ return this;
+ }
+
+ public UITexture getItemSlot() {
+ return ModularUITextures.ITEM_SLOT;
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ public Widget asWidget(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ ChangeableWidget container = new ChangeableWidget(() -> createWidget(buildContext.getPlayer()));
+
+ // TODO: Only reset the widget when there are more slot stacks, otherwise just refresh them somehow
+
+ container.attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ if (slots != slotsGetter.get()) {
+ slots = slotsGetter.get();
+ container.notifyChangeNoSync();
+ }
+ return slots;
+ }, i -> {
+ if (slots != i) {
+ slots = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ if (usedSlots != inventory.size()) {
+ usedSlots = inventory.size();
+ container.notifyChangeNoSync();
+ }
+ return usedSlots;
+ }, i -> {
+ if (usedSlots != i) {
+ usedSlots = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ .attachSyncer(new FakeSyncWidget.ListSyncer<>(() -> {
+ HashMap<ItemID, Integer> itemMap = new HashMap<>();
+ HashMap<ItemID, ItemStack> stackMap = new HashMap<>();
+ HashMap<ItemID, ArrayList<Integer>> realSlotMap = new HashMap<>();
+ for (int i = 0, mStorageSize = inventory.size(); i < mStorageSize; i++) {
+ ItemStack stack = inventoryGetter.get(inventory.get(i));
+ ItemID id = ItemID.createNoCopy(stack, false);
+ itemMap.merge(id, 1, Integer::sum);
+ stackMap.putIfAbsent(id, stack);
+ realSlotMap.computeIfAbsent(id, unused -> new ArrayList<>())
+ .add(i);
+ }
+ List<GTHelper.StackableItemSlot> newDrawables = new ArrayList<>();
+ for (Map.Entry<ItemID, Integer> entry : itemMap.entrySet()) {
+ newDrawables.add(
+ new GTHelper.StackableItemSlot(
+ entry.getValue(),
+ stackMap.get(entry.getKey()),
+ realSlotMap.get(entry.getKey())));
+ }
+ if (!Objects.equals(newDrawables, drawables)) {
+ drawables = newDrawables;
+ container.notifyChangeNoSync();
+ }
+ return drawables;
+ }, l -> {
+ drawables.clear();
+ drawables.addAll(l);
+ container.notifyChangeNoSync();
+ }, (buffer, i) -> {
+ try {
+ i.write(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, buffer -> {
+ try {
+ return GTHelper.StackableItemSlot.read(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }), builder);
+ if (isEnabledGetter != null) {
+ container.attachSyncer(new FakeSyncWidget.BooleanSyncer(isEnabledGetter, i -> isEnabled = i), builder);
+ }
+ return container;
+ }
+
+ List<GTHelper.StackableItemSlot> drawables = new ArrayList<>();
+
+ private Widget createWidget(EntityPlayer player) {
+ Scrollable dynamicInventoryWidget = new Scrollable().setVerticalScroll();
+
+ ArrayList<Widget> buttons = new ArrayList<>();
+
+ if (!ModUtils.isClientThreaded()) {
+ HashMap<ItemID, Integer> itemMap = new HashMap<>();
+ HashMap<ItemID, ItemStack> stackMap = new HashMap<>();
+ HashMap<ItemID, ArrayList<Integer>> realSlotMap = new HashMap<>();
+ for (int i = 0, inventorySize = inventory.size(); i < inventorySize; i++) {
+ ItemStack stack = inventoryGetter.get(inventory.get(i));
+ ItemID id = ItemID.createNoCopy(stack, false);
+ itemMap.merge(id, 1, Integer::sum);
+ stackMap.putIfAbsent(id, stack);
+ realSlotMap.computeIfAbsent(id, unused -> new ArrayList<>())
+ .add(i);
+ }
+ drawables = new ArrayList<>();
+ for (Map.Entry<ItemID, Integer> entry : itemMap.entrySet()) {
+ drawables.add(
+ new GTHelper.StackableItemSlot(
+ entry.getValue(),
+ stackMap.get(entry.getKey()),
+ realSlotMap.get(entry.getKey())));
+ }
+ }
+
+ for (int ID = 0; ID < drawables.size(); ID++) {
+ final int finalID = ID;
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+
+ if (clickData.mouseButton == 2) {
+ // special button handler goes here
+ if (drawables.size() <= finalID) return;
+ if (player.capabilities.isCreativeMode && player.inventory.getItemStack() == null) {
+ int realID = drawables.get(finalID).realSlots.get(0);
+ ItemStack stack = inventoryGetter.get(inventory.get(realID))
+ .copy();
+ stack.stackSize = stack.getMaxStackSize();
+ player.inventory.setItemStack(stack);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ } else if (clickData.shift) {
+ if (inventoryExtractor == null) return;
+ if (drawables.size() <= finalID) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T removed = inventoryExtractor.extract(realID);
+ if (removed != null) {
+ ItemStack stack = inventoryGetter.get(removed);
+ if (player.inventory.addItemStackToInventory(stack))
+ player.inventoryContainer.detectAndSendChanges();
+ else player.entityDropItem(stack, 0.f);
+ return;
+ }
+ } else {
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (drawables.size() > finalID) {
+ if (inventoryReplacer == null) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ ItemStack removed = inventoryReplacer.replaceOrMerge(realID, input);
+ if (removed == null) return;
+ player.inventory.setItemStack(removed.stackSize == 0 ? null : removed);
+ } else {
+ if (inventoryInjector == null) return;
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ ItemStack leftover = inventoryInjector.inject(copy);
+ if (leftover == null) return;
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ ((EntityPlayerMP) player).isChangingQuantityOnly = true;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ ItemStack leftover = inventoryInjector.inject(input);
+ if (leftover == null) return;
+ if (input.stackSize > 0) {
+ ((EntityPlayerMP) player).isChangingQuantityOnly = true;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ if (drawables.size() > finalID) {
+ if (inventoryExtractor == null) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T removed = inventoryExtractor.extract(realID);
+ if (removed != null) {
+ ItemStack stack = inventoryGetter.get(removed);
+ player.inventory.setItemStack(stack);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ }
+ }
+ })
+ .setBackground(
+ () -> new IDrawable[] { getItemSlot(),
+ new ItemDrawable(drawables.size() > finalID ? drawables.get(finalID).stack : null)
+ .withFixedSize(16, 16, 1, 1),
+ new Text(
+ (drawables.size() > finalID && drawables.get(finalID).count > 1)
+ ? (drawables.get(finalID).count > 99 ? "+99"
+ : String.valueOf(drawables.get(finalID).count))
+ : "").color(Color.WHITE.normal)
+ .alignment(Alignment.TopLeft)
+ .withOffset(1, 1),
+ new Text(
+ (drawables.size() > finalID && drawables.get(finalID).stack.stackSize > 1)
+ ? String.valueOf(drawables.get(finalID).stack.stackSize)
+ : "").color(Color.WHITE.normal)
+ .shadow()
+ .alignment(Alignment.BottomRight) })
+ .dynamicTooltip(() -> {
+ if (drawables.size() > finalID) {
+ List<String> tip = new ArrayList<>(
+ Collections.singletonList(drawables.get(finalID).stack.getDisplayName()));
+ if (drawables.get(finalID).count > 1) tip.add(
+ EnumChatFormatting.DARK_PURPLE + "There are "
+ + drawables.get(finalID).count
+ + " identical slots");
+ return tip;
+ }
+ return Collections.emptyList();
+ })
+ .setSize(18, 18));
+ }
+
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ ItemStack leftover = inventoryInjector.inject(copy);
+ if (leftover == null) return;
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ ((EntityPlayerMP) player).isChangingQuantityOnly = true;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ ItemStack leftover = inventoryInjector.inject(input);
+ if (leftover == null) return;
+ if (input.stackSize > 0) {
+ ((EntityPlayerMP) player).isChangingQuantityOnly = true;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ })
+ .setBackground(
+ () -> new IDrawable[] { getItemSlot(),
+ new Text(
+ (slots - usedSlots) <= 1 ? ""
+ : ((slots - usedSlots) > 99 ? "+99" : String.valueOf((slots - usedSlots))))
+ .color(Color.WHITE.normal)
+ .alignment(Alignment.TopLeft)
+ .withOffset(1, 1) })
+ .dynamicTooltip(() -> {
+ List<String> tip = new ArrayList<>(Collections.singleton(EnumChatFormatting.GRAY + "Empty slot"));
+ if (slots - usedSlots > 1)
+ tip.add(EnumChatFormatting.DARK_PURPLE + "There are " + (slots - usedSlots) + " identical slots");
+ return tip;
+ })
+ .setSize(18, 18));
+
+ final int perRow = width / 18;
+ for (int i = 0, imax = ((buttons.size() - 1) / perRow); i <= imax; i++) {
+ DynamicPositionedRow row = new DynamicPositionedRow().setSynced(false);
+ for (int j = 0, jmax = (i == imax ? (buttons.size() - 1) % perRow : (perRow - 1)); j <= jmax; j++) {
+ final int finalI = i * perRow;
+ final int finalJ = j;
+ final int ID = finalI + finalJ;
+ row.widget(buttons.get(ID));
+ }
+ dynamicInventoryWidget.widget(row.setPos(0, i * 18));
+ }
+
+ return dynamicInventoryWidget.setSize(width, height);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryGetter<T> {
+
+ /**
+ * Allows to get an ItemStack from the dynamic inventory
+ *
+ * @param from Dynamic inventory item from which we want to take an item out
+ * @return ItemStack or null if inaccessible
+ */
+ ItemStack get(T from);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryInjector {
+
+ /**
+ * Allows to insert an item to the dynamic inventory
+ *
+ * @param what ItemStack which we are trying to insert
+ * @return Leftover ItemStack (stackSize == 0 if everything has been inserted) or null
+ */
+ ItemStack inject(ItemStack what);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryExtractor<T> {
+
+ /**
+ * Allows to extract an item from the dynamic inventory
+ *
+ * @param where Index from where we want to take an item out
+ * @return Item that we took out or null
+ */
+ T extract(int where);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryReplacerOrMerger {
+
+ /**
+ * Allows to replace an item in Dynamic Inventory
+ *
+ * @param where which index we want to replace
+ * @param stack what stack we want to replace it with
+ * @return Stack that we are left with or null
+ */
+ ItemStack replaceOrMerge(int where, ItemStack stack);
+ }
+
+}
diff --git a/src/main/java/kubatech/api/EIGDynamicInventory.java b/src/main/java/kubatech/api/EIGDynamicInventory.java
new file mode 100644
index 0000000000..1c703fe2fa
--- /dev/null
+++ b/src/main/java/kubatech/api/EIGDynamicInventory.java
@@ -0,0 +1,510 @@
+package kubatech.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+
+import org.lwjgl.opengl.GL11;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.internal.Theme;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.ChangeableWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import kubatech.api.gui.AutoScalingStackSizeText;
+import kubatech.api.helpers.GTHelper;
+import kubatech.api.utils.ModUtils;
+
+public class EIGDynamicInventory<T> {
+
+ int width, height;
+ Supplier<Integer> maxSeedCountGetter;
+ Supplier<Integer> maxSeedTypeGetter;
+ Supplier<Integer> usedSeedCountGetter;
+ Supplier<Integer> usedSeedTypesGetter;
+ private int maxSeedTypes = 0;
+ private int maxSeedCount = 0;
+ private int usedSeedTypes = 0;
+ private int usedSeedCount = 0;
+ List<T> inventory;
+ TInventoryGetter<T> inventoryGetter;
+ TInventoryInjector inventoryInjector = null;
+ TInventoryExtractor<T> inventoryExtractor = null;
+ TInventoryReplacerOrMerger inventoryReplacer = null;
+ Supplier<Boolean> isEnabledGetter = null;
+ boolean isEnabled = true;
+
+ public EIGDynamicInventory(int width, int height, Supplier<Integer> maxSeedTypeGetter,
+ Supplier<Integer> maxSeedCountGetter, Supplier<Integer> usedSeedTypesGetter,
+ Supplier<Integer> usedSeedCountGetter, List<T> inventory, TInventoryGetter<T> inventoryGetter) {
+ this.width = width;
+ this.height = height;
+ this.maxSeedTypeGetter = maxSeedTypeGetter;
+ this.maxSeedCountGetter = maxSeedCountGetter;
+ this.usedSeedTypesGetter = usedSeedTypesGetter;
+ this.usedSeedCountGetter = usedSeedCountGetter;
+ this.inventory = inventory;
+ this.inventoryGetter = inventoryGetter;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryInjection(TInventoryInjector inventoryInjector) {
+ this.inventoryInjector = inventoryInjector;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryExtraction(TInventoryExtractor<T> inventoryExtractor) {
+ this.inventoryExtractor = inventoryExtractor;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryReplace(TInventoryReplacerOrMerger inventoryReplacer) {
+ this.inventoryReplacer = inventoryReplacer;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> setEnabled(Supplier<Boolean> isEnabled) {
+ this.isEnabledGetter = isEnabled;
+ return this;
+ }
+
+ public UITexture getItemSlot() {
+ return ModularUITextures.ITEM_SLOT;
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ public Widget asWidget(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ ChangeableWidget container = new ChangeableWidget(() -> createWidget(buildContext.getPlayer()));
+ // TODO: Only reset the widget when there are more slot stacks, otherwise just refresh them somehow
+
+ container
+ // max seed types
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.maxSeedTypeGetter.get();
+ if (this.maxSeedTypes != i) {
+ this.maxSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.maxSeedTypes != i) {
+ this.maxSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // used seed types
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.usedSeedTypesGetter.get();
+ if (this.usedSeedTypes != i) {
+ this.usedSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.usedSeedTypes != i) {
+ this.usedSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // max seed count
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.maxSeedCountGetter.get();
+ if (this.maxSeedCount != i) {
+ this.maxSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.maxSeedCount != i) {
+ this.maxSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // used seed count
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.usedSeedCountGetter.get();
+ if (this.usedSeedCount != i) {
+ this.usedSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.usedSeedCount != i) {
+ this.usedSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+
+ .attachSyncer(new FakeSyncWidget.ListSyncer<>(() -> {
+ List<GTHelper.StackableItemSlot> newDrawables = new ArrayList<>();
+ for (int i = 0, mStorageSize = inventory.size(); i < mStorageSize; i++) {
+ T slot = inventory.get(i);
+ if (slot == null) {
+ continue;
+ }
+ ItemStack stack = inventoryGetter.get(slot);
+ newDrawables
+ .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList<>(Collections.singletonList(i))));
+ }
+ if (!Objects.equals(newDrawables, drawables)) {
+ drawables = newDrawables;
+ container.notifyChangeNoSync();
+ }
+ return drawables;
+ }, l -> {
+ drawables.clear();
+ drawables.addAll(l);
+ container.notifyChangeNoSync();
+ }, (buffer, i) -> {
+ try {
+ i.write(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, buffer -> {
+ try {
+ return GTHelper.StackableItemSlot.read(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }), builder);
+ if (isEnabledGetter != null) {
+ container.attachSyncer(new FakeSyncWidget.BooleanSyncer(isEnabledGetter, i -> isEnabled = i), builder);
+ }
+ return container;
+ }
+
+ List<GTHelper.StackableItemSlot> drawables = new ArrayList<>();
+
+ private Widget createWidget(EntityPlayer player) {
+ Scrollable dynamicInventoryWidget = new Scrollable().setVerticalScroll();
+
+ ArrayList<Widget> buttons = new ArrayList<>();
+
+ if (!ModUtils.isClientThreaded()) {
+ HashMap<ItemID, Integer> itemMap = new HashMap<>();
+ HashMap<ItemID, ItemStack> stackMap = new HashMap<>();
+ HashMap<ItemID, ArrayList<Integer>> realSlotMap = new HashMap<>();
+ drawables = new ArrayList<>();
+ for (int i = 0, inventorySize = inventory.size(); i < inventorySize; i++) {
+ T slot = inventory.get(i);
+ if (slot == null) {
+ continue;
+ }
+ ItemStack stack = inventoryGetter.get(slot);
+ drawables
+ .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList<Integer>(Collections.singleton(i))));
+ }
+ }
+
+ for (int ID = 0; ID < drawables.size(); ID++) {
+ final int finalID = ID;
+
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+
+ if (clickData.mouseButton == 2) {
+ // special button handler goes here
+ if (drawables.size() <= finalID) return;
+ if (player.capabilities.isCreativeMode && player.inventory.getItemStack() == null) {
+ int realID = drawables.get(finalID).realSlots.get(0);
+ ItemStack stack = inventoryGetter.get(inventory.get(realID))
+ .copy();
+ stack.stackSize = stack.getMaxStackSize();
+ player.inventory.setItemStack(stack);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ } else if (clickData.shift) {
+ if (inventoryExtractor == null) return;
+ if (drawables.size() <= finalID) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T toRemoveFrom = this.inventory.get(realID);
+ ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player);
+ if (removed != null) {
+ if (player.inventory.addItemStackToInventory(removed))
+ player.inventoryContainer.detectAndSendChanges();
+ else player.entityDropItem(removed, 0.f);
+ return;
+ }
+ } else {
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (inventoryInjector == null) return;
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ inventoryInjector.inject(copy);
+ if (copy.stackSize == 1) return;
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ inventoryInjector.inject(input);
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ if (drawables.size() > finalID) {
+ if (inventoryExtractor == null) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T toRemoveFrom = this.inventory.get(realID);
+ ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player);
+ if (removed != null) {
+ player.inventory.setItemStack(removed);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ }
+ }
+ })
+ .setBackground(() -> {
+ ItemStack stack = drawables.get(finalID).stack;
+ float slotSize = 16.0f;
+ IDrawable itemDrawable = new ItemDrawable(stack).withFixedSize(slotSize, slotSize, 1, 1);
+ IDrawable stackSizeText = new AutoScalingStackSizeText(stack.stackSize).color(Color.WHITE.normal)
+ .shadow()
+ .alignment(Alignment.BottomRight)
+ .measure();
+
+ return new IDrawable[] { getItemSlot(), itemDrawable, stackSizeText };
+ })
+ .dynamicTooltip(() -> {
+ if (drawables.size() > finalID) {
+ ItemStack stack = drawables.get(finalID).stack;
+ List<String> tip = new LinkedList<>();
+ for (Object o : stack.getTooltip(player, false)) {
+ tip.add(o.toString());
+ }
+ if (tip.size() >= 1 && tip.get(0) != null) {
+ tip.set(0, stack.stackSize + " x " + tip.get(0));
+ }
+ return tip;
+ }
+ return Collections.emptyList();
+ })
+ .setSize(18, 18));
+ }
+
+ // only add the extra slot if we are still able to insert
+ if (this.usedSeedCount < this.maxSeedCount) {
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ inventoryInjector.inject(copy);
+ if (copy.stackSize == 1) return;
+
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ inventoryInjector.inject(input);
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ })
+ .setBackground(() -> {
+ IDrawable itemSlot = getItemSlot();
+
+ IDrawable stackSizeText = new AutoScalingStackSizeText(this.maxSeedCount - this.usedSeedCount)
+ .color(Color.WHITE.normal)
+ .shadow()
+ .alignment(Alignment.BottomRight)
+ .measure();
+
+ return new IDrawable[] { itemSlot, stackSizeText };
+ })
+ .dynamicTooltip(() -> {
+ // TODO: all l10n for insertion slot tooltip.
+ List<String> tip = new ArrayList<>();
+ tip.add(
+ EnumChatFormatting.DARK_PURPLE + "Remaining seed types: "
+ + (this.maxSeedTypes - this.usedSeedTypes));
+ tip.add(
+ EnumChatFormatting.DARK_GREEN + "Remaining seed capacity: "
+ + (this.maxSeedCount - this.usedSeedCount));
+ return tip;
+ })
+ .setSize(18, 18));
+ }
+
+ final int perRow = width / 18;
+ for (int i = 0, imax = ((buttons.size() - 1) / perRow); i <= imax; i++) {
+ DynamicPositionedRow row = new DynamicPositionedRow().setSynced(false);
+ for (int j = 0, jmax = (i == imax ? (buttons.size() - 1) % perRow : (perRow - 1)); j <= jmax; j++) {
+ final int finalI = i * perRow;
+ final int finalJ = j;
+ final int ID = finalI + finalJ;
+ row.widget(buttons.get(ID));
+ }
+ dynamicInventoryWidget.widget(row.setPos(0, i * 18));
+ }
+ dynamicInventoryWidget.setSize(width, height);
+ return dynamicInventoryWidget;
+ }
+
+ @FunctionalInterface
+ public interface TInventoryGetter<T> {
+
+ /**
+ * Allows to get an ItemStack from the dynamic inventory
+ *
+ * @param from Dynamic inventory item from which we want to take an item out
+ * @return ItemStack or null if inaccessible
+ */
+ ItemStack get(T from);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryInjector {
+
+ /**
+ * Allows to insert an item to the dynamic inventory
+ *
+ * @param what ItemStack which we are trying to insert
+ * @return Leftover ItemStack (stackSize == 0 if everything has been inserted) or null
+ */
+ ItemStack inject(ItemStack what);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryExtractor<T> {
+
+ /**
+ * Allows to extract an item from the dynamic inventory
+ *
+ * @return Item that we took out or null
+ */
+ ItemStack extract(T container, EntityPlayerMP player);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryReplacerOrMerger {
+
+ /**
+ * Allows to replace an item in Dynamic Inventory
+ *
+ * @param where which index we want to replace
+ * @param stack what stack we want to replace it with
+ * @return Stack that we are left with or null
+ */
+ ItemStack replaceOrMerge(int where, ItemStack stack);
+ }
+
+}
diff --git a/src/main/java/kubatech/api/IBlockStemAccesor.java b/src/main/java/kubatech/api/IBlockStemAccesor.java
new file mode 100644
index 0000000000..8f2c37e15f
--- /dev/null
+++ b/src/main/java/kubatech/api/IBlockStemAccesor.java
@@ -0,0 +1,8 @@
+package kubatech.api;
+
+import net.minecraft.block.Block;
+
+public interface IBlockStemAccesor {
+
+ Block getCropBlock();
+}
diff --git a/src/main/java/kubatech/api/Variables.java b/src/main/java/kubatech/api/Variables.java
new file mode 100644
index 0000000000..a5821325e2
--- /dev/null
+++ b/src/main/java/kubatech/api/Variables.java
@@ -0,0 +1,63 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+import net.minecraft.util.EnumChatFormatting;
+
+import kubatech.api.utils.StringUtils;
+
+public class Variables {
+
+ public static final String Author = "Author: "
+ + StringUtils.applyRainbow("kuba6000", 0, EnumChatFormatting.BOLD.toString());
+
+ public static String buildAuthorList(String... authors) {
+ if (authors.length == 0) return "Author: Unknown";
+ StringBuilder b = new StringBuilder("Author: ")
+ .append(StringUtils.applyRainbow(authors[0], 0, EnumChatFormatting.BOLD.toString()));
+ for (int i = 1; i < authors.length; i++) {
+ String author = authors[i];
+ b.append(EnumChatFormatting.RESET)
+ .append(" & ")
+ .append(EnumChatFormatting.GOLD)
+ .append(author);
+ }
+ return b.toString();
+ }
+
+ public static final String StructureHologram = "To see the structure, use a " + EnumChatFormatting.BLUE
+ + "Structure"
+ + EnumChatFormatting.DARK_BLUE
+ + "Lib"
+ + EnumChatFormatting.RESET
+ + ""
+ + EnumChatFormatting.GRAY
+ + " Hologram Projector on the Controller!";
+
+ public static final double ln4 = Math.log(4d);
+ public static final double ln2 = Math.log(2d);
+
+ public static final NumberFormat numberFormatScientific = new DecimalFormat("0.00E0");
+ public static final NumberFormat numberFormat = NumberFormat.getInstance();
+}
diff --git a/src/main/java/kubatech/api/eig/EIGBucket.java b/src/main/java/kubatech/api/eig/EIGBucket.java
new file mode 100644
index 0000000000..6a3dbdb642
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGBucket.java
@@ -0,0 +1,247 @@
+package kubatech.api.eig;
+
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
+
+import java.util.LinkedList;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+
+import gregtech.api.util.GT_Utility;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public abstract class EIGBucket {
+
+ protected ItemStack seed;
+ protected int seedCount;
+ protected ItemStack[] supportItems;
+
+ public EIGBucket(ItemStack seed, int seedCount, ItemStack[] supportItem) {
+ this.seed = seed.copy();
+ this.seed.stackSize = 1;
+ this.seedCount = seedCount;
+ this.supportItems = supportItem;
+ }
+
+ public EIGBucket(NBTTagCompound nbt) {
+ this.seed = readItemStackFromNBT(nbt.getCompoundTag("seed"));
+ this.seedCount = nbt.getInteger("count");
+
+ // parse support items
+ if (nbt.hasKey("supportItems", 9)) {
+ NBTTagList supportItemsNBTList = nbt.getTagList("supportItems", 10);
+ if (supportItemsNBTList.tagCount() > 0) {
+ this.supportItems = new ItemStack[supportItemsNBTList.tagCount()];
+ for (int i = 0; i < supportItemsNBTList.tagCount(); i++) {
+ this.supportItems[i] = readItemStackFromNBT(supportItemsNBTList.getCompoundTagAt(i));
+ }
+ } else {
+ supportItems = null;
+ }
+ } else {
+ supportItems = null;
+ }
+ }
+
+ /**
+ * Creates a persistent save of the bucket's current data.
+ *
+ * @return The nbt data for this bucket.
+ */
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = new NBTTagCompound();
+ nbt.setString("type", this.getNBTIdentifier());
+ nbt.setTag("seed", writeItemStackToNBT(this.seed));
+ nbt.setInteger("count", this.seedCount);
+ if (this.supportItems != null && this.supportItems.length > 0) {
+ NBTTagList supportItemNBT = new NBTTagList();
+ for (ItemStack supportItem : this.supportItems) {
+ supportItemNBT.appendTag(writeItemStackToNBT(supportItem));
+ }
+ nbt.setTag("supportItems", supportItemNBT);
+ }
+ return nbt;
+ }
+
+ /**
+ * Gets an item stack representing the seeds in this bucket
+ *
+ * @return an item stack representing the seeds in this bucket.
+ */
+ public ItemStack getSeedStack() {
+ ItemStack copied = this.seed.copy();
+ copied.stackSize = this.seedCount;
+ return copied;
+ }
+
+ /**
+ * Gets the number of seeds in this bucket
+ *
+ * @return gets the number of seeds in this bucket.
+ */
+ public int getSeedCount() {
+ return this.seedCount;
+ }
+
+ /**
+ * Gets the display name of the seed in this bucket
+ *
+ * @return The display name of the seed.
+ */
+ public String getDisplayName() {
+ return this.seed.getDisplayName();
+ }
+
+ public String getInfoData() {
+ StringBuilder sb = new StringBuilder();
+ // display invalid buckets, we don't want people to think they lost their seeds or something.
+ sb.append(this.isValid() ? EnumChatFormatting.GREEN : EnumChatFormatting.RED);
+ sb.append("x");
+ sb.append(this.getSeedCount());
+ sb.append(" ");
+ sb.append(this.getDisplayName());
+ this.getAdditionalInfoData(sb);
+ sb.append(EnumChatFormatting.RESET);
+ return sb.toString();
+ }
+
+ protected void getAdditionalInfoData(StringBuilder sb) {}
+
+ /**
+ * Attempts to add seeds to tbe bucket if the input is compatible
+ *
+ * @param input A stack of an item that may be able to be added to our current bucket.
+ * @param maxConsume The maximum amount of seeds to add to this bucket.
+ * @param simulate True if you want to see if you can add more seeds (useful for support item checks)
+ * @return number of seeds consumed, 0 for wrong item, -1 if it missed the support items, -2 if you tried to consume
+ * 0 or less items;
+ */
+ public int tryAddSeed(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input, int maxConsume,
+ boolean simulate) {
+ // Abort is input if empty
+ if (input == null || input.stackSize <= 0) return -2;
+ // Cap max to input count
+ maxConsume = Math.min(maxConsume, input.stackSize);
+ // Abort if item isn't an identical seed.
+ if (!GT_Utility.areStacksEqual(this.seed, input, false)) return 0;
+
+ // no support items, consume and exit early.
+ if (this.supportItems == null || this.supportItems.length <= 0) {
+ if (!simulate) {
+ input.stackSize -= maxConsume;
+ this.seedCount += maxConsume;
+ }
+ return maxConsume;
+ }
+
+ // Check if the item is found
+ LinkedList<ItemStack> toConsumeFrom = new LinkedList<>();
+ supportLoop: for (ItemStack supportItem : this.supportItems) {
+ for (ItemStack otherInput : greenhouse.getStoredInputs()) {
+ // filter usable inputs
+ if (otherInput == null || otherInput.stackSize <= 0) continue;
+ if (!GT_Utility.areStacksEqual(supportItem, otherInput, false)) continue;
+ // update max consume again
+ maxConsume = Math.min(maxConsume, otherInput.stackSize);
+ toConsumeFrom.addLast(otherInput);
+ continue supportLoop;
+ }
+ // no support found, no seeds added
+ return -1;
+ }
+
+ // consume items
+ if (!simulate) {
+ input.stackSize -= maxConsume;
+ for (ItemStack stack : toConsumeFrom) {
+ stack.stackSize -= maxConsume;
+ }
+ this.seedCount += maxConsume;
+ }
+ return maxConsume;
+ }
+
+ /**
+ * Attempts to remove a seed from the bucket
+ *
+ * @param toRemove The maximum amount of items to remove.
+ * @return The items that were removed from the bucket. Null if the bucket is empty.
+ */
+ public ItemStack[] tryRemoveSeed(int toRemove, boolean simulate) {
+ // validate inputs
+ toRemove = Math.min(this.seedCount, toRemove);
+ if (toRemove <= 0) return null;
+
+ // consume and return output
+ ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)];
+ ret[0] = this.seed.copy();
+ ret[0].stackSize = toRemove;
+ if (this.supportItems != null) {
+ for (int i = 0; i < this.supportItems.length; i++) {
+ ret[i + 1] = this.supportItems[i].copy();
+ ret[i + 1].stackSize = toRemove;
+ }
+ }
+ if (!simulate) {
+ this.seedCount -= toRemove;
+ }
+ return ret;
+ }
+
+ /**
+ * Sets the seed count to 0 and returns item stacks representing every item in this bucket.
+ *
+ * @return The contents of the bucket
+ */
+ public ItemStack[] emptyBucket() {
+ if (this.seedCount <= 0) return null;
+ ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)];
+ ret[0] = this.seed.copy();
+ ret[0].stackSize = this.seedCount;
+ if (this.supportItems != null) {
+ for (int i = 0; i < this.supportItems.length; i++) {
+ ret[i + 1] = this.supportItems[i].copy();
+ ret[i + 1].stackSize = this.seedCount;
+ }
+ }
+ this.seedCount = 0;
+ return ret;
+ }
+
+ /**
+ * Returns true if the bucket can output items.
+ *
+ * @return true if the bucket is valid.
+ */
+ public boolean isValid() {
+ return this.seed != null && this.seedCount > 0;
+ }
+
+ /**
+ * Gets the identifier used to identify this class during reconstruction
+ *
+ * @return the identifier for this bucket type.
+ */
+ protected abstract String getNBTIdentifier();
+
+ /**
+ * Adds item drops to the item tracker.
+ *
+ * @param multiplier A multiplier to apply to the output.
+ * @param tracker The item drop tracker
+ */
+ public abstract void addProgress(double multiplier, EIGDropTable tracker);
+
+ /**
+ * Attempts to revalidate a seed bucket. If it returns false, attempt to seed and support items and delete the
+ * bucket.
+ *
+ * @param greenhouse The greenhouse that contains the bucket.
+ * @return True if the bucket was successfully validated. {@link EIGBucket#isValid()} should also return true.
+ */
+ public abstract boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse);
+
+}
diff --git a/src/main/java/kubatech/api/eig/EIGDropTable.java b/src/main/java/kubatech/api/eig/EIGDropTable.java
new file mode 100644
index 0000000000..bb5bbe6456
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGDropTable.java
@@ -0,0 +1,224 @@
+package kubatech.api.eig;
+
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
+
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+
+import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
+
+public class EIGDropTable {
+
+ private static final String NBT_DROP_TABLE_ITEM_KEY = "item";
+ private static final String NBT_DROP_TABLE_COUNT_KEY = "count";
+
+ private final ItemStackMap<Double> dropTable;
+
+ /**
+ * Initialises a new empty drop table.
+ */
+ public EIGDropTable() {
+ this.dropTable = new ItemStackMap<>(true);
+ }
+
+ /**
+ * Loads a serialised drop table from nbt.
+ *
+ * @param nbt The nbt tag that contains the key for a drop table
+ * @param key The name of the key name for the drop table.
+ */
+ public EIGDropTable(NBTTagCompound nbt, String key) {
+ // should create an empty table if no drops are found.
+ this(nbt.getTagList(key, 10));
+ }
+
+ /**
+ * Loads a serialised drop table from nbt.
+ *
+ * @param nbt The nbt tag that contains the key for a drop table
+ */
+ public EIGDropTable(NBTTagList nbt) {
+ this();
+ for (int i = 0; i < nbt.tagCount(); i++) {
+ NBTTagCompound drop = nbt.getCompoundTagAt(i);
+ dropTable.merge(
+ readItemStackFromNBT(drop.getCompoundTag(NBT_DROP_TABLE_ITEM_KEY)),
+ drop.getDouble(NBT_DROP_TABLE_COUNT_KEY),
+ Double::sum);
+ }
+ }
+
+ /**
+ * Serialises the drop table to nbt
+ *
+ * @return The serialised drop table.
+ */
+ public NBTTagList save() {
+ NBTTagList nbt = new NBTTagList();
+ for (Map.Entry<ItemStack, Double> entry : this.dropTable.entrySet()) {
+ NBTTagCompound entryNBT = new NBTTagCompound();
+ entryNBT.setTag(NBT_DROP_TABLE_ITEM_KEY, writeItemStackToNBT(entry.getKey()));
+ entryNBT.setDouble(NBT_DROP_TABLE_COUNT_KEY, entry.getValue());
+ nbt.appendTag(entryNBT);
+ }
+ return nbt;
+ }
+
+ /**
+ * Adds a drop to the drop table
+ *
+ * @param itemStack The item to add to the table.
+ * @param amount The amount to add to the table.
+ */
+ public void addDrop(ItemStack itemStack, double amount) {
+ ItemStack key = itemStack.copy();
+ key.stackSize = 1;
+ this.dropTable.merge(key, amount, Double::sum);
+ }
+
+ /**
+ * Adds the values from this drop table to another, but multiplies the amount by a random amount bound by variance.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz
+ * @param rand The random source for the variance.
+ */
+ public void addTo(EIGDropTable target, double variance, Random rand) {
+ this.addTo(target, 1.0, variance, rand);
+ }
+
+ /**
+ * Adds the values from this drop table to another, but multiplies the amount by a multiplier and a random amount
+ * bound by variance.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param multiplier A multiplier to apply to all amounts from this drop table.
+ * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz.
+ * @param rand The random source for the variance.
+ */
+ public void addTo(EIGDropTable target, double multiplier, double variance, Random rand) {
+ this.addTo(target, variance * (rand.nextDouble() - 0.5) * multiplier);
+ }
+
+ /**
+ * Adds the values from this drop table to another.
+ *
+ * @param target The drop table that you want to add the value to.
+ */
+ public void addTo(EIGDropTable target) {
+ this.addTo(target, 1.0);
+ }
+
+ /**
+ * Adds the values from this drop table to another but multiplies the values by a multiplier.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param multiplier A multiplier to apply to all amounts from this drop table.
+ */
+ public void addTo(EIGDropTable target, double multiplier) {
+ for (Map.Entry<ItemStack, Double> entry : this.dropTable.entrySet()) {
+ target.dropTable.merge(entry.getKey(), entry.getValue() * multiplier, Double::sum);
+ }
+ }
+
+ /**
+ * Checks if the drop table is empty;
+ *
+ * @return true if empty.
+ */
+ public boolean isEmpty() {
+ return this.dropTable.isEmpty();
+ }
+
+ /**
+ * Returns the entry set for this drop table.
+ *
+ * @return ItemStack -> amount
+ */
+ public Set<Map.Entry<ItemStack, Double>> entrySet() {
+ return this.dropTable.entrySet();
+ }
+
+ /**
+ * Gets the amount for a specific item.
+ *
+ * @param item The item to look for.
+ * @return 0 if nothing is found else a positive value.
+ */
+ public double getItemAmount(ItemStack item) {
+ if (this.dropTable.containsKey(item)) {
+ return this.dropTable.get(item);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the amount for a specific item.
+ *
+ * @param item The item to look for.
+ */
+ public void setItemAmount(ItemStack item, double value) {
+ this.dropTable.put(item, value);
+ }
+
+ /**
+ * Removes an item from the drop table
+ *
+ * @param item The item to remove from the drop table.
+ */
+ public void removeItem(ItemStack item) {
+ this.dropTable.remove(item);
+ }
+
+ /**
+ * Creates a new drop table that is the intersection of this drop table and another.
+ *
+ *
+ * @param with The drop table to intersect with.
+ * @return The result of the intersection.
+ */
+ public EIGDropTable intersect(EIGDropTable with) {
+ EIGDropTable ret = new EIGDropTable();
+ for (ItemStack key : with.dropTable.keySet()) {
+ if (this.dropTable.containsKey(key)) {
+ ret.addDrop(key, this.dropTable.get(key));
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Consumes drops with drop counts above 1 and returns a list of the consumed item stacks.
+ *
+ * @return The list of consumed items;
+ */
+ public ItemStack[] getDrops() {
+ // doesn't need to filter for less than 0 so that the EIG displays the progress of incomplete items.
+ return this.dropTable.entrySet()
+ .parallelStream()
+ .map(EIGDropTable::computeDrops)
+ .toArray(ItemStack[]::new);
+ }
+
+ /**
+ * Consumes the items in the entry and returns the consumed item without removing partial items.
+ *
+ * @param entry The entry to consume from
+ * @return The item tha twas removed.
+ */
+ private static ItemStack computeDrops(Map.Entry<ItemStack, Double> entry) {
+ ItemStack copied = entry.getKey()
+ .copy();
+ copied.stackSize = (int) Math.floor(entry.getValue());
+ if (entry.getValue() >= 1.0d) {
+ entry.setValue(entry.getValue() % 1);
+ }
+ return copied;
+ }
+}
diff --git a/src/main/java/kubatech/api/eig/EIGMode.java b/src/main/java/kubatech/api/eig/EIGMode.java
new file mode 100644
index 0000000000..68ad633773
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGMode.java
@@ -0,0 +1,154 @@
+package kubatech.api.eig;
+
+import static kubatech.kubatech.error;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public abstract class EIGMode {
+
+ public abstract int getUIIndex();
+
+ public abstract String getName();
+
+ public abstract int getMinVoltageTier();
+
+ public abstract int getMinGlassTier();
+
+ public abstract int getStartingSlotCount();
+
+ public abstract int getSlotPerTierMultiplier();
+
+ public abstract int getSlotCount(int machineTier);
+
+ public abstract int getSeedCapacityPerSlot();
+
+ public abstract int getWeedEXMultiplier();
+
+ public abstract int getMaxFertilizerUsagePerSeed();
+
+ public abstract double getFertilizerBoost();
+
+ public abstract GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder);
+
+ /**
+ * Used to resolve factory type to an identifier.
+ */
+ private final HashMap<String, IEIGBucketFactory> factories;
+ /**
+ * A way to have other mods submit custom buckets that can be prioritized over our default buckets
+ */
+ private final LinkedList<IEIGBucketFactory> orderedFactories;
+
+ public EIGMode() {
+ this.factories = new HashMap<>();
+ this.orderedFactories = new LinkedList<>();
+ }
+
+ /**
+ * Adds a bucket factory to the EIG mode and gives it a low priority. Factories with using existing IDs will
+ * overwrite each other.
+ *
+ * @param factory The bucket factory to add.
+ */
+ public void addLowPriorityFactory(IEIGBucketFactory factory) {
+ String factoryId = factory.getNBTIdentifier();
+ dealWithDuplicateFactoryId(factoryId);
+ // add factory as lowest priority
+ this.factories.put(factoryId, factory);
+ this.orderedFactories.addLast(factory);
+ }
+
+ /**
+ * Adds a bucket factory to the EIG mode and gives it a high priority. Factories with using existing IDs will
+ * overwrite each other.
+ *
+ * @param factory The bucket factory to add.
+ */
+ public void addHighPriorityFactory(IEIGBucketFactory factory) {
+ String factoryId = factory.getNBTIdentifier();
+ dealWithDuplicateFactoryId(factoryId);
+ // add factory as lowest priority
+ this.factories.put(factoryId, factory);
+ this.orderedFactories.addFirst(factory);
+ }
+
+ /**
+ * A standardized way to deal with duplicate factory type identifiers.
+ *
+ * @param factoryId The ID of the factory
+ */
+ private void dealWithDuplicateFactoryId(String factoryId) {
+ if (this.factories.containsKey(factoryId)) {
+ // TODO: Check with devs to see if they want a throw instead.
+ error("Duplicate EIG bucket index detected!!!: " + factoryId);
+ // remove duplicate from ordered list
+ this.orderedFactories.remove(this.factories.get(factoryId));
+ }
+ }
+
+ /**
+ * Attempts to create a new bucket from a given item. Returns if the item cannot be inserted into the EIG.
+ *
+ * @see IEIGBucketFactory#tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse, ItemStack)
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that will contain the seed.
+ * @param input The {@link ItemStack} for the input item.
+ * @param maxConsume The maximum amount of items to consume.
+ * @param simulate Whether to actually consume the seed.
+ * @return Null if no bucket could be created from the item.
+ */
+ public EIGBucket tryCreateNewBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input,
+ int maxConsume, boolean simulate) {
+ // Validate inputs
+ if (input == null) return null;
+ maxConsume = Math.min(input.stackSize, maxConsume);
+ if (maxConsume <= 0) return null;
+ for (IEIGBucketFactory factory : this.orderedFactories) {
+ EIGBucket bucket = factory.tryCreateBucket(greenhouse, input);
+ if (bucket == null || !bucket.isValid()) continue;
+ if (!simulate) input.stackSize--;
+ maxConsume--;
+ bucket.tryAddSeed(greenhouse, input, maxConsume, simulate);
+ return bucket;
+ }
+ return null;
+ }
+
+ /**
+ * Restores the buckets of an EIG for the given mode.
+ *
+ * @see IEIGBucketFactory#restore(NBTTagCompound)
+ * @param bucketNBTList The
+ */
+ public void restoreBuckets(NBTTagList bucketNBTList, List<EIGBucket> loadTo) {
+ for (int i = 0; i < bucketNBTList.tagCount(); i++) {
+ // validate nbt
+ NBTTagCompound bucketNBT = bucketNBTList.getCompoundTagAt(i);
+ if (bucketNBT.hasNoTags()) {
+ error("Empty nbt bucket found in EIG nbt.");
+ continue;
+ }
+ if (!bucketNBT.hasKey("type", 8)) {
+ error("Failed to identify bucket type in EIG nbt.");
+ continue;
+ }
+ // identify bucket type
+ String bucketType = bucketNBT.getString("type");
+ IEIGBucketFactory factory = factories.getOrDefault(bucketType, null);
+ if (factory == null) {
+ error("failed to find EIG bucket factory for type: " + bucketType);
+ continue;
+ }
+ // restore bucket
+ loadTo.add(factory.restore(bucketNBT));
+ }
+ }
+}
diff --git a/src/main/java/kubatech/api/eig/IEIGBucketFactory.java b/src/main/java/kubatech/api/eig/IEIGBucketFactory.java
new file mode 100644
index 0000000000..647e544573
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/IEIGBucketFactory.java
@@ -0,0 +1,15 @@
+package kubatech.api.eig;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public interface IEIGBucketFactory {
+
+ String getNBTIdentifier();
+
+ EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack stack);
+
+ EIGBucket restore(NBTTagCompound nbt);
+}
diff --git a/src/main/java/kubatech/api/enums/EIGModes.java b/src/main/java/kubatech/api/enums/EIGModes.java
new file mode 100644
index 0000000000..a81de2b8c5
--- /dev/null
+++ b/src/main/java/kubatech/api/enums/EIGModes.java
@@ -0,0 +1,42 @@
+package kubatech.api.enums;
+
+import java.util.HashMap;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.eigmodes.EIGIC2Mode;
+import kubatech.tileentity.gregtech.multiblock.eigmodes.EIGNormalMode;
+
+public class EIGModes {
+
+ private static final HashMap<String, EIGMode> modes = new HashMap<>();
+
+ public static final EIGMode Normal = addMode(EIGNormalMode.instance);
+ public static final EIGMode IC2 = addMode(EIGIC2Mode.instance);
+
+ // this is basically a fake enum, plz don't instantiate
+ private EIGModes() {}
+
+ private static EIGMode addMode(EIGMode mode) {
+ modes.put(mode.getName(), mode);
+ return mode;
+ }
+
+ public static EIGMode getModeFromName(String name) {
+ return modes.get(name);
+ }
+
+ public static EIGMode getNextMode(EIGMode from) {
+ int id = (from.getUIIndex() + 1) % modes.size();
+ for (EIGMode mode : modes.values()) {
+ if (mode.getUIIndex() == id) return mode;
+ }
+ return Normal;
+ }
+
+ public static void addTooltipInfo(GT_Multiblock_Tooltip_Builder tt) {
+ // maybe make this use the mods list instead
+ EIGModes.Normal.addTooltipInfo(tt);
+ EIGModes.IC2.addTooltipInfo(tt);
+ }
+}
diff --git a/src/main/java/kubatech/api/enums/EIGSetupPhase.java b/src/main/java/kubatech/api/enums/EIGSetupPhase.java
new file mode 100644
index 0000000000..95e8854347
--- /dev/null
+++ b/src/main/java/kubatech/api/enums/EIGSetupPhase.java
@@ -0,0 +1,16 @@
+package kubatech.api.enums;
+
+public enum EIGSetupPhase {
+
+ Operation(0, "Operation"),
+ Input(1, "Input"),
+ Output(2, "Output");
+
+ public final int id;
+ public final String name;
+
+ private EIGSetupPhase(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+}
diff --git a/src/main/java/kubatech/api/enums/ItemList.java b/src/main/java/kubatech/api/enums/ItemList.java
new file mode 100644
index 0000000000..02943e3b22
--- /dev/null
+++ b/src/main/java/kubatech/api/enums/ItemList.java
@@ -0,0 +1,232 @@
+package kubatech.api.enums;
+
+import static gregtech.api.enums.GT_Values.NI;
+import static gregtech.api.enums.GT_Values.W;
+
+import java.util.Locale;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import gregtech.api.interfaces.IItemContainer;
+import gregtech.api.util.GT_LanguageManager;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.api.util.GT_OreDictUnificator;
+import gregtech.api.util.GT_Utility;
+
+public enum ItemList implements IItemContainer {
+
+ ExtremeEntityCrusher,
+ ExtremeIndustrialApiary,
+ ExtremeIndustrialGreenhouse,
+ DraconicEvolutionFusionCrafter,
+ LegendaryBlackTea,
+ LegendaryButterflyTea,
+ LegendaryEarlGrayTea,
+ LegendaryGreenTea,
+ LegendaryLemonTea,
+ LegendaryMilkTea,
+ LegendaryOolongTea,
+ LegendaryPeppermintTea,
+ LegendaryPuerhTea,
+ LegendaryRedTea,
+ LegendaryWhiteTea,
+ LegendaryYellowTea,
+ LegendaryUltimateTea,
+ BlackTea,
+ EarlGrayTea,
+ GreenTea,
+ LemonTea,
+ MilkTea,
+ OolongTea,
+ PeppermintTea,
+ PuerhTea,
+ WhiteTea,
+ YellowTea,
+ BlackTeaLeaf,
+ GreenTeaLeaf,
+ OolongTeaLeaf,
+ PuerhTeaLeaf,
+ WhiteTeaLeaf,
+ YellowTeaLeaf,
+ TeaLeafDehydrated,
+ SteamedTeaLeaf,
+ RolledTeaLeaf,
+ OxidizedTeaLeaf,
+ FermentedTeaLeaf,
+ BruisedTeaLeaf,
+ PartiallyOxidizedTeaLeaf,
+ TeaAcceptorResearchNote,
+ TeaAcceptor,
+ TeaStorage,
+ Beeeeee,
+ DEFCCasingBase,
+ DEFCCasingT1,
+ DEFCCasingT2,
+ DEFCCasingT3,
+ DEFCCasingT4,
+ DEFCCasingT5,
+ DEFCDraconicSchematic,
+ DEFCWyvernSchematic,
+ DEFCAwakenedSchematic,
+ DEFCChaoticSchematic,
+
+ ;
+
+ private ItemStack mStack;
+ private boolean mHasNotBeenSet = true;
+
+ @Override
+ public IItemContainer set(Item aItem) {
+ mHasNotBeenSet = false;
+ if (aItem == null) return this;
+ ItemStack aStack = new ItemStack(aItem, 1, 0);
+ mStack = GT_Utility.copyAmount(1, aStack);
+ return this;
+ }
+
+ @Override
+ public IItemContainer set(ItemStack aStack) {
+ mHasNotBeenSet = false;
+ mStack = GT_Utility.copyAmount(1, aStack);
+ return this;
+ }
+
+ @Override
+ public IItemContainer hidden() {
+ codechicken.nei.api.API.hideItem(get(1L));
+ return this;
+ }
+
+ @Override
+ public Item getItem() {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return null;
+ return mStack.getItem();
+ }
+
+ @Override
+ public Block getBlock() {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ return GT_Utility.getBlockFromItem(getItem());
+ }
+
+ @Override
+ public final boolean hasBeenSet() {
+ return !mHasNotBeenSet;
+ }
+
+ @Override
+ public boolean isStackEqual(Object aStack) {
+ return isStackEqual(aStack, false, false);
+ }
+
+ @Override
+ public boolean isStackEqual(Object aStack, boolean aWildcard, boolean aIgnoreNBT) {
+ if (GT_Utility.isStackInvalid(aStack)) return false;
+ return GT_Utility.areUnificationsEqual((ItemStack) aStack, aWildcard ? getWildcard(1) : get(1), aIgnoreNBT);
+ }
+
+ @Override
+ public ItemStack get(long aAmount, Object... aReplacements) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return GT_Utility.copyAmount(aAmount, aReplacements);
+ return GT_Utility.copyAmount(aAmount, GT_OreDictUnificator.get(mStack));
+ }
+
+ @Override
+ public ItemStack getWildcard(long aAmount, Object... aReplacements) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return GT_Utility.copyAmount(aAmount, aReplacements);
+ return GT_Utility.copyAmountAndMetaData(aAmount, W, GT_OreDictUnificator.get(mStack));
+ }
+
+ @Override
+ public ItemStack getUndamaged(long aAmount, Object... aReplacements) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return GT_Utility.copyAmount(aAmount, aReplacements);
+ return GT_Utility.copyAmountAndMetaData(aAmount, 0, GT_OreDictUnificator.get(mStack));
+ }
+
+ @Override
+ public ItemStack getAlmostBroken(long aAmount, Object... aReplacements) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return GT_Utility.copyAmount(aAmount, aReplacements);
+ return GT_Utility.copyAmountAndMetaData(aAmount, mStack.getMaxDamage() - 1, GT_OreDictUnificator.get(mStack));
+ }
+
+ @Override
+ public ItemStack getWithName(long aAmount, String aDisplayName, Object... aReplacements) {
+ ItemStack rStack = get(1, aReplacements);
+ if (GT_Utility.isStackInvalid(rStack)) return NI;
+
+ // CamelCase alphanumeric words from aDisplayName
+ StringBuilder tCamelCasedDisplayNameBuilder = new StringBuilder();
+ final String[] tDisplayNameWords = aDisplayName.split("\\W");
+ for (String tWord : tDisplayNameWords) {
+ if (tWord.length() > 0) tCamelCasedDisplayNameBuilder.append(
+ tWord.substring(0, 1)
+ .toUpperCase(Locale.US));
+ if (tWord.length() > 1) tCamelCasedDisplayNameBuilder.append(
+ tWord.substring(1)
+ .toLowerCase(Locale.US));
+ }
+ if (tCamelCasedDisplayNameBuilder.length() == 0) {
+ // CamelCased DisplayName is empty, so use hash of aDisplayName
+ tCamelCasedDisplayNameBuilder.append(((Long) (long) aDisplayName.hashCode()));
+ }
+
+ // Construct a translation key from UnlocalizedName and CamelCased DisplayName
+ final String tKey = rStack.getUnlocalizedName() + ".with." + tCamelCasedDisplayNameBuilder + ".name";
+
+ rStack.setStackDisplayName(GT_LanguageManager.addStringLocalization(tKey, aDisplayName));
+ return GT_Utility.copyAmount(aAmount, rStack);
+ }
+
+ @Override
+ public ItemStack getWithCharge(long aAmount, int aEnergy, Object... aReplacements) {
+ ItemStack rStack = get(1, aReplacements);
+ if (GT_Utility.isStackInvalid(rStack)) return null;
+ GT_ModHandler.chargeElectricItem(rStack, aEnergy, Integer.MAX_VALUE, true, false);
+ return GT_Utility.copyAmount(aAmount, rStack);
+ }
+
+ @Override
+ public ItemStack getWithDamage(long aAmount, long aMetaValue, Object... aReplacements) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ if (GT_Utility.isStackInvalid(mStack)) return GT_Utility.copyAmount(aAmount, aReplacements);
+ return GT_Utility.copyAmountAndMetaData(aAmount, aMetaValue, GT_OreDictUnificator.get(mStack));
+ }
+
+ @Override
+ public IItemContainer registerOre(Object... aOreNames) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ for (Object tOreName : aOreNames) GT_OreDictUnificator.registerOre(tOreName, get(1));
+ return this;
+ }
+
+ @Override
+ public IItemContainer registerWildcardAsOre(Object... aOreNames) {
+ if (mHasNotBeenSet)
+ throw new IllegalAccessError("The Enum '" + name() + "' has not been set to an Item at this time!");
+ for (Object tOreName : aOreNames) GT_OreDictUnificator.registerOre(tOreName, getWildcard(1));
+ return this;
+ }
+
+ /**
+ * Returns the internal stack. This method is unsafe. It's here only for quick operations. DON'T CHANGE THE RETURNED
+ * VALUE!
+ */
+ public ItemStack getInternalStack_unsafe() {
+ return mStack;
+ }
+}
diff --git a/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java b/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java
new file mode 100644
index 0000000000..313610de07
--- /dev/null
+++ b/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java
@@ -0,0 +1,72 @@
+package kubatech.api.gui;
+
+import java.util.Collections;
+
+import com.gtnewhorizons.modularui.api.NumberFormatMUI;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.TextRenderer;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.common.internal.Theme;
+
+public class AutoScalingStackSizeText implements IDrawable {
+
+ private static final TextRenderer measuringRenderer = new TextRenderer();
+ private static final NumberFormatMUI muiNumberFormat = new NumberFormatMUI();
+ private static final TextRenderer renderer = new TextRenderer();
+ private Alignment alignment = Alignment.Center;
+ private final String text;
+ private int simWidth;
+
+ private int color;
+ private boolean shadow = false;
+
+ public AutoScalingStackSizeText(long stackSize) {
+ this.text = muiNumberFormat.formatWithSuffix(stackSize);
+ this.color = Theme.INSTANCE.getText();
+ this.measure();
+ }
+
+ public AutoScalingStackSizeText color(int color) {
+ this.color = Color.withAlpha(color, 255);
+ return this;
+ }
+
+ public AutoScalingStackSizeText shadow(boolean shadow) {
+ this.shadow = shadow;
+ return this;
+ }
+
+ public AutoScalingStackSizeText shadow() {
+ return shadow(true);
+ }
+
+ public AutoScalingStackSizeText alignment(Alignment alignment) {
+ this.alignment = alignment;
+ return this;
+ }
+
+ public AutoScalingStackSizeText measure() {
+ this.simWidth = measuringRenderer.getMaxWidth(Collections.singletonList(this.text));
+ return this;
+ }
+
+ public boolean hasColor() {
+ return Color.getAlpha(color) > 0;
+ }
+
+ @Override
+ public void applyThemeColor(int color) {
+ renderer.setColor(hasColor() ? this.color : Theme.INSTANCE.getText());
+ }
+
+ @Override
+ public void draw(float x, float y, float width, float height, float partialTicks) {
+ renderer.setPos((int) (x - 0.5), (int) (y - 0.5));
+ renderer.setShadow(this.shadow);
+ renderer.setAlignment(alignment, width, height);
+ renderer.setColor(this.color);
+ renderer.setScale(this.simWidth <= 16.0f ? 1.0f : 16.0f / this.simWidth);
+ renderer.draw(this.text);
+ }
+}
diff --git a/src/main/java/kubatech/api/helpers/GTHelper.java b/src/main/java/kubatech/api/helpers/GTHelper.java
new file mode 100644
index 0000000000..72bbe8cf77
--- /dev/null
+++ b/src/main/java/kubatech/api/helpers/GTHelper.java
@@ -0,0 +1,99 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.helpers;
+
+import static kubatech.api.Variables.ln4;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.PacketBuffer;
+
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_MultiBlockBase;
+import kubatech.api.implementations.KubaTechGTMultiBlockBase;
+
+public class GTHelper {
+
+ public static long getMaxInputEU(GT_MetaTileEntity_MultiBlockBase mte) {
+ if (mte instanceof KubaTechGTMultiBlockBase) return ((KubaTechGTMultiBlockBase<?>) mte).getMaxInputEu();
+ long rEU = 0;
+ for (GT_MetaTileEntity_Hatch_Energy tHatch : mte.mEnergyHatches)
+ if (tHatch.isValid()) rEU += tHatch.maxEUInput() * tHatch.maxAmperesIn();
+ return rEU;
+ }
+
+ public static double getVoltageTierD(long voltage) {
+ return Math.log((double) voltage / 8L) / ln4;
+ }
+
+ public static double getVoltageTierD(GT_MetaTileEntity_MultiBlockBase mte) {
+ return Math.log((double) getMaxInputEU(mte) / 8L) / ln4;
+ }
+
+ public static int getVoltageTier(long voltage) {
+ return (int) getVoltageTierD(voltage);
+ }
+
+ public static int getVoltageTier(GT_MetaTileEntity_MultiBlockBase mte) {
+ return (int) getVoltageTierD(mte);
+ }
+
+ public static class StackableItemSlot {
+
+ public StackableItemSlot(int count, ItemStack stack, ArrayList<Integer> realSlots) {
+ this.count = count;
+ this.stack = stack;
+ this.realSlots = realSlots;
+ }
+
+ public final int count;
+ public final ItemStack stack;
+ public final ArrayList<Integer> realSlots;
+
+ public void write(PacketBuffer buffer) throws IOException {
+ buffer.writeVarIntToBuffer(count);
+ buffer.writeItemStackToBuffer(stack);
+ }
+
+ public static StackableItemSlot read(PacketBuffer buffer) throws IOException {
+ return new StackableItemSlot(
+ buffer.readVarIntFromBuffer(),
+ buffer.readItemStackFromBuffer(),
+ new ArrayList<>());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof StackableItemSlot)) return false;
+ StackableItemSlot other = (StackableItemSlot) obj;
+ return count == other.count && ItemID.createNoCopy(stack, false)
+ .hashCode()
+ == ItemID.createNoCopy(other.stack, false)
+ .hashCode()
+ && realSlots.equals(other.realSlots);
+ }
+ }
+}
diff --git a/src/main/java/kubatech/api/helpers/ReflectionHelper.java b/src/main/java/kubatech/api/helpers/ReflectionHelper.java
new file mode 100644
index 0000000000..5c5bb1070e
--- /dev/null
+++ b/src/main/java/kubatech/api/helpers/ReflectionHelper.java
@@ -0,0 +1,205 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.helpers;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import net.minecraft.launchwrapper.Launch;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+
+public class ReflectionHelper {
+
+ private static class _FieldsMethods {
+
+ final HashMap<String, Field> fields = new HashMap<>();
+ final HashMap<String, Method> methods = new HashMap<>();
+ }
+
+ private static final HashMap<String, _FieldsMethods> classes = new HashMap<>();
+
+ @SuppressWarnings("unchecked")
+ public static <T> T getField(Object obj, String fieldName, T defaultvalue) {
+ Class<?> cl = obj.getClass();
+ String clName = cl.getName();
+ HashMap<String, Field> classmap = classes.computeIfAbsent(clName, s -> new _FieldsMethods()).fields;
+ try {
+ if (classmap.containsKey(fieldName)) {
+ Field f = classmap.get(fieldName);
+ if (f == null) return defaultvalue;
+ return (T) f.get(obj);
+ }
+ boolean exceptionDetected;
+ Field f = null;
+ do {
+ exceptionDetected = false;
+ try {
+ f = cl.getDeclaredField(fieldName);
+ f.setAccessible(true);
+ } catch (Exception ex) {
+ exceptionDetected = true;
+ cl = cl.getSuperclass();
+ }
+ } while (exceptionDetected && !cl.equals(Object.class));
+ classmap.put(fieldName, f);
+ if (f == null) return defaultvalue;
+ return (T) f.get(obj);
+ } catch (Exception ex) {
+ return defaultvalue;
+ }
+ }
+
+ public static <T> boolean setField(Object obj, String fieldName, T value) {
+ Class<?> cl = obj.getClass();
+ String clName = cl.getName();
+ HashMap<String, Field> classmap = classes.computeIfAbsent(clName, s -> new _FieldsMethods()).fields;
+ try {
+ if (classmap.containsKey(fieldName)) {
+ Field f = classmap.get(fieldName);
+ if (f == null) return false;
+ f.set(obj, value);
+ return true;
+ }
+ boolean exceptionDetected;
+ Field f = null;
+ do {
+ exceptionDetected = false;
+ try {
+ f = cl.getDeclaredField(fieldName);
+ f.setAccessible(true);
+ } catch (Exception ex) {
+ exceptionDetected = true;
+ cl = cl.getSuperclass();
+ }
+ } while (exceptionDetected && !cl.equals(Object.class));
+ classmap.put(fieldName, f);
+ if (f == null) return false;
+ f.set(obj, value);
+ return true;
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+
+ public static <T> T getField(Object obj, String fieldName) {
+ return getField(obj, fieldName, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T callMethod(Object obj, String methodName, T defaultValue, Object... args) {
+ Class<?> cl = obj.getClass();
+ String clName = cl.getName();
+ HashMap<String, Method> classmap = classes.computeIfAbsent(clName, s -> new _FieldsMethods()).methods;
+ StringBuilder builder = new StringBuilder(methodName);
+ Class<?>[] argsTypes = new Class<?>[args.length];
+ for (int i = 0; i < args.length; i++) {
+ Class<?> arg = args[i].getClass();
+ builder.append(";")
+ .append(arg.getSimpleName());
+ argsTypes[i] = arg;
+ }
+ String methodNameUnique = builder.toString();
+ try {
+ if (classmap.containsKey(methodNameUnique)) {
+ Method m = classmap.get(methodNameUnique);
+ if (m == null) return defaultValue;
+ return (T) m.invoke(obj, args);
+ }
+ boolean exceptionDetected;
+ Method m = null;
+ do {
+ exceptionDetected = false;
+ try {
+ m = cl.getDeclaredMethod(methodName, argsTypes);
+ m.setAccessible(true);
+ } catch (Exception ex) {
+ exceptionDetected = true;
+ cl = cl.getSuperclass();
+ }
+ } while (exceptionDetected && !cl.equals(Object.class));
+ classmap.put(methodNameUnique, m);
+ if (m == null) return defaultValue;
+ return (T) m.invoke(obj, args);
+ } catch (Exception ex) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Gets all classes in a specific package path, works only for jar files.
+ *
+ * @param packageName The package name
+ * @return The class nodes
+ */
+ public static Collection<ClassNode> getClasses(String packageName) throws IOException {
+ ClassLoader classLoader = Thread.currentThread()
+ .getContextClassLoader();
+ assert classLoader != null;
+ String packagePath = packageName.replace('.', '/');
+ URL resource = classLoader.getResource(packagePath);
+ if (resource == null) throw new FileNotFoundException();
+ if (!resource.getProtocol()
+ .equals("jar")) return Collections.emptySet();
+ try (JarFile jar = ((JarURLConnection) resource.openConnection()).getJarFile()) {
+ return jar.stream()
+ .filter(
+ j -> !j.isDirectory() && j.getName()
+ .startsWith(packagePath)
+ && j.getName()
+ .endsWith(".class"))
+ .map(j -> {
+ try {
+ String name = j.getName();
+ URL jarResource = Launch.classLoader.getResource(name);
+ if (jarResource == null) return null;
+ byte[] bytes;
+ try (InputStream is = jarResource.openStream()) {
+ bytes = new byte[(int) j.getSize()];
+ if (is.read(bytes) != bytes.length) return null;
+ if (is.available() > 0) return null;
+ }
+
+ ClassNode cn = new ClassNode();
+ ClassReader cr = new ClassReader(bytes);
+ cr.accept(cn, 0);
+
+ return cn;
+ } catch (IOException ignored) {}
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+ }
+}
diff --git a/src/main/java/kubatech/api/helpers/UUIDFinder.java b/src/main/java/kubatech/api/helpers/UUIDFinder.java
new file mode 100644
index 0000000000..922904705b
--- /dev/null
+++ b/src/main/java/kubatech/api/helpers/UUIDFinder.java
@@ -0,0 +1,43 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.helpers;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+public class UUIDFinder {
+
+ private static final HashMap<UUID, String> UUIDToUsernameMap = new HashMap<>();
+ private static final HashMap<String, UUID> UsernameToUUIDMap = new HashMap<>();
+
+ public static UUID getUUID(String username) {
+ return UsernameToUUIDMap.get(username);
+ }
+
+ public static String getUsername(UUID player) {
+ return UUIDToUsernameMap.get(player);
+ }
+
+ public static void updateMapping(String username, UUID player) {
+ UUIDToUsernameMap.put(player, username);
+ UsernameToUUIDMap.put(username, player);
+ }
+}
diff --git a/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java b/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java
new file mode 100644
index 0000000000..94b120a3e9
--- /dev/null
+++ b/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java
@@ -0,0 +1,346 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.implementations;
+
+import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY;
+import static kubatech.api.Variables.ln2;
+import static kubatech.api.Variables.ln4;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.math.MainAxisAlignment;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ITileWithModularUI;
+import com.gtnewhorizons.modularui.api.screen.ModularUIContext;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.builder.UIBuilder;
+import com.gtnewhorizons.modularui.common.builder.UIInfo;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularUIContainer;
+import com.gtnewhorizons.modularui.common.widget.Column;
+import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.metatileentity.BaseMetaTileEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_ExtendedPowerMultiBlockBase;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_OutputBus;
+import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_OutputBus_ME;
+import kubatech.Tags;
+
+public abstract class KubaTechGTMultiBlockBase<T extends GT_MetaTileEntity_ExtendedPowerMultiBlockBase<T>>
+ extends GT_MetaTileEntity_ExtendedPowerMultiBlockBase<T> {
+
+ @Deprecated
+ public final int mEUt = 0;
+
+ @SuppressWarnings("unchecked")
+ protected static <K extends KubaTechGTMultiBlockBase<?>> UIInfo<?, ?> createKTMetaTileEntityUI(
+ KTContainerConstructor<K> containerConstructor) {
+ return UIBuilder.of()
+ .container((player, world, x, y, z) -> {
+ TileEntity te = world.getTileEntity(x, y, z);
+ if (te instanceof BaseMetaTileEntity) {
+ IMetaTileEntity mte = ((BaseMetaTileEntity) te).getMetaTileEntity();
+ if (!(mte instanceof KubaTechGTMultiBlockBase)) return null;
+ final UIBuildContext buildContext = new UIBuildContext(player);
+ final ModularWindow window = ((ITileWithModularUI) te).createWindow(buildContext);
+ return containerConstructor.of(new ModularUIContext(buildContext, te::markDirty), window, (K) mte);
+ }
+ return null;
+ })
+ .gui(((player, world, x, y, z) -> {
+ if (!world.isRemote) return null;
+ TileEntity te = world.getTileEntity(x, y, z);
+ if (te instanceof BaseMetaTileEntity) {
+ IMetaTileEntity mte = ((BaseMetaTileEntity) te).getMetaTileEntity();
+ if (!(mte instanceof KubaTechGTMultiBlockBase)) return null;
+ final UIBuildContext buildContext = new UIBuildContext(player);
+ final ModularWindow window = ((ITileWithModularUI) te).createWindow(buildContext);
+ return new ModularGui(
+ containerConstructor.of(new ModularUIContext(buildContext, null), window, (K) mte));
+ }
+ return null;
+ }))
+ .build();
+ }
+
+ @FunctionalInterface
+ protected interface KTContainerConstructor<T extends KubaTechGTMultiBlockBase<?>> {
+
+ ModularUIContainer of(ModularUIContext context, ModularWindow mainWindow, T multiBlock);
+ }
+
+ protected KubaTechGTMultiBlockBase(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ }
+
+ protected KubaTechGTMultiBlockBase(String aName) {
+ super(aName);
+ }
+
+ /**
+ * Enables infinite overclocking (will give more outputs with more energy past 1 tick) Currently doesn't support
+ * recipe inputs
+ *
+ * @return If this supports infinite overclock
+ */
+ protected boolean isOverclockingInfinite() {
+ return false;
+ }
+
+ /**
+ * @return The minimum amount of ticks this multiblock can overclock to
+ */
+ protected int getOverclockTimeLimit() {
+ return 1;
+ }
+
+ @Override
+ protected void calculateOverclockedNessMultiInternal(long aEUt, int aDuration, int mAmperage, long maxInputVoltage,
+ boolean perfectOC) {
+ calculateOverclock(aEUt, aDuration, getMaxInputEu(), perfectOC);
+ }
+
+ /**
+ * @param aEUt Recipe EU/t
+ * @param aDuration Recipe duration (in ticks)
+ * @param maxInputEU The amount of energy we want to overclock to
+ * @param isPerfect Is this overclock perfect ?
+ * @return The amount of overclocks
+ */
+ protected int calculateOverclock(long aEUt, int aDuration, final long maxInputEU, final boolean isPerfect) {
+ final int minDuration = getOverclockTimeLimit();
+ int tiers = (int) (Math.log((double) maxInputEU / (double) aEUt) / ln4);
+ if (tiers <= 0) {
+ this.lEUt = aEUt;
+ this.mMaxProgresstime = aDuration;
+ return 0;
+ }
+ int durationTiers = (int) Math
+ .ceil(Math.log((double) aDuration / (double) minDuration) / (isPerfect ? ln4 : ln2));
+ if (durationTiers < 0) durationTiers = 0; // We do not support downclocks (yet)
+ if (durationTiers > tiers) durationTiers = tiers;
+ if (!isOverclockingInfinite()) {
+ tiers = durationTiers;
+ if (tiers == 0) {
+ this.lEUt = aEUt;
+ this.mMaxProgresstime = aDuration;
+ return 0;
+ }
+ this.lEUt = aEUt << (tiers << 1);
+ aDuration >>= isPerfect ? (tiers << 1) : tiers;
+ if (aDuration < minDuration) aDuration = minDuration;
+ this.mMaxProgresstime = aDuration;
+ return tiers;
+ }
+ this.lEUt = aEUt << (tiers << 1);
+ aDuration >>= isPerfect ? (durationTiers << 1) : durationTiers;
+ int dMulti = tiers - durationTiers;
+ if (dMulti > 0) {
+ dMulti = 1 << (isPerfect ? (dMulti << 1) : dMulti);
+ // TODO: Use more inputs???
+ for (ItemStack mOutputItem : this.mOutputItems) mOutputItem.stackSize *= dMulti;
+ for (FluidStack mOutputFluid : this.mOutputFluids) mOutputFluid.amount *= dMulti;
+ }
+ if (aDuration < minDuration) aDuration = minDuration;
+ this.mMaxProgresstime = aDuration;
+ return tiers;
+ }
+
+ protected int calculateOverclock(long aEUt, int aDuration, boolean isPerfect) {
+ return calculateOverclock(aEUt, aDuration, getMaxInputEu(), isPerfect);
+ }
+
+ protected int calculateOverclock(long aEUt, int aDuration) {
+ return calculateOverclock(aEUt, aDuration, false);
+ }
+
+ protected int calculatePerfectOverclock(long aEUt, int aDuration) {
+ return calculateOverclock(aEUt, aDuration, true);
+ }
+
+ public int getVoltageTier() {
+ return (int) getVoltageTierExact();
+ }
+
+ public double getVoltageTierExact() {
+ return Math.log((double) getMaxInputEu() / 8d) / ln4 + 1e-8d;
+ }
+
+ protected boolean tryOutputAll(List<ItemStack> list) {
+ return tryOutputAll(list, l -> Collections.singletonList((ItemStack) l));
+ }
+
+ protected <Y> boolean tryOutputAll(List<Y> list, Function<Y, List<ItemStack>> mappingFunction) {
+ if (list == null || list.isEmpty() || mappingFunction == null) return false;
+ int emptySlots = 0;
+ boolean ignoreEmptiness = false;
+ for (GT_MetaTileEntity_Hatch_OutputBus i : mOutputBusses) {
+ if (i instanceof GT_MetaTileEntity_Hatch_OutputBus_ME) {
+ ignoreEmptiness = true;
+ break;
+ }
+ for (int j = 0; j < i.getSizeInventory(); j++)
+ if (i.isValidSlot(j)) if (i.getStackInSlot(j) == null) emptySlots++;
+ }
+ if (emptySlots == 0 && !ignoreEmptiness) return false;
+ boolean wasSomethingRemoved = false;
+ while (!list.isEmpty()) {
+ List<ItemStack> toOutputNow = mappingFunction.apply(list.get(0));
+ if (toOutputNow == null) {
+ list.remove(0);
+ continue;
+ }
+ if (!ignoreEmptiness && emptySlots < toOutputNow.size()) break;
+ emptySlots -= toOutputNow.size();
+ list.remove(0);
+ wasSomethingRemoved = true;
+ for (ItemStack stack : toOutputNow) {
+ addOutput(stack);
+ }
+ }
+ return wasSomethingRemoved;
+ }
+
+ @Override
+ public boolean isCorrectMachinePart(ItemStack aStack) {
+ return true;
+ }
+
+ @Override
+ public int getMaxEfficiency(ItemStack aStack) {
+ return 10000;
+ }
+
+ @Override
+ public int getDamageToComponent(ItemStack aStack) {
+ return 0;
+ }
+
+ @Override
+ public boolean explodesOnComponentBreak(ItemStack aStack) {
+ return false;
+ }
+
+ // UI stuff
+
+ public static final UITexture PICTURE_KUBATECH_LOGO = UITexture.fullImage(Tags.MODID, "gui/logo_13x15_dark");
+
+ @Override
+ public void addGregTechLogo(ModularWindow.Builder builder) {
+ builder.widget(
+ new DrawableWidget().setDrawable(PICTURE_KUBATECH_LOGO)
+ .setSize(13, 15)
+ .setPos(191 - 13, 86 - 15)
+ .addTooltip(new Text(Tags.MODNAME).color(Color.GRAY.normal))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY));
+ }
+
+ protected List<SlotWidget> slotWidgets = new ArrayList<>(1);
+
+ public void createInventorySlots() {
+ final SlotWidget inventorySlot = new SlotWidget(inventoryHandler, 1);
+ inventorySlot.setBackground(GT_UITextures.SLOT_DARK_GRAY);
+ slotWidgets.add(inventorySlot);
+ }
+
+ @Override
+ public Pos2d getPowerSwitchButtonPos() {
+ return new Pos2d(174, 166 - (slotWidgets.size() * 18));
+ }
+
+ @Override
+ public Pos2d getStructureUpdateButtonPos() {
+ return new Pos2d(174, 148 - (slotWidgets.size() * 18));
+ }
+
+ @Override
+ public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ builder.widget(
+ new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
+ .setPos(4, 4)
+ .setSize(190, 85));
+
+ slotWidgets.clear();
+ createInventorySlots();
+
+ Column slotsColumn = new Column();
+ for (int i = slotWidgets.size() - 1; i >= 0; i--) {
+ slotsColumn.widget(slotWidgets.get(i));
+ }
+ builder.widget(
+ slotsColumn.setAlignment(MainAxisAlignment.END)
+ .setPos(173, 167 - 1));
+
+ final DynamicPositionedColumn screenElements = new DynamicPositionedColumn();
+ drawTexts(screenElements, slotWidgets.size() > 0 ? slotWidgets.get(0) : null);
+ builder.widget(screenElements);
+
+ builder.widget(createPowerSwitchButton(builder))
+ .widget(createVoidExcessButton(builder))
+ .widget(createInputSeparationButton(builder))
+ .widget(createBatchModeButton(builder))
+ .widget(createLockToSingleRecipeButton(builder))
+ .widget(createStructureUpdateButton(builder));
+
+ DynamicPositionedRow configurationElements = new DynamicPositionedRow();
+ addConfigurationWidgets(configurationElements, buildContext);
+
+ builder.widget(
+ configurationElements.setSpace(2)
+ .setAlignment(MainAxisAlignment.SPACE_BETWEEN)
+ .setPos(getRecipeLockingButtonPos().add(18, 0)));
+ }
+
+ protected void addConfigurationWidgets(DynamicPositionedRow configurationElements, UIBuildContext buildContext) {
+
+ }
+
+ protected static String voltageTooltipFormatted(int tier) {
+ return GT_Values.TIER_COLORS[tier] + GT_Values.VN[tier] + EnumChatFormatting.GRAY;
+ }
+
+ protected final Function<Widget, Boolean> isFixed = widget -> getIdealStatus() == getRepairStatus() && mMachine;
+ protected static final Function<Integer, IDrawable> toggleButtonTextureGetter = val -> val == 0
+ ? GT_UITextures.OVERLAY_BUTTON_CROSS
+ : GT_UITextures.OVERLAY_BUTTON_CHECKMARK;
+ protected static final Function<Integer, IDrawable[]> toggleButtonBackgroundGetter = val -> new IDrawable[] {
+ val == 0 ? GT_UITextures.BUTTON_STANDARD : GT_UITextures.BUTTON_STANDARD_PRESSED };
+}
diff --git a/src/main/java/kubatech/api/tea/TeaNetwork.java b/src/main/java/kubatech/api/tea/TeaNetwork.java
new file mode 100644
index 0000000000..68c8275bfd
--- /dev/null
+++ b/src/main/java/kubatech/api/tea/TeaNetwork.java
@@ -0,0 +1,93 @@
+package kubatech.api.tea;
+
+import java.math.BigInteger;
+import java.util.HashSet;
+import java.util.UUID;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.savedata.PlayerData;
+import kubatech.savedata.PlayerDataManager;
+import kubatech.tileentity.TeaStorageTile;
+
+public class TeaNetwork {
+
+ // TODO: Optimize later :P
+ public BigInteger teaAmount = BigInteger.ZERO;
+ public BigInteger teaLimit = BigInteger.valueOf(Long.MAX_VALUE);
+ PlayerData owner;
+ private HashSet<TeaStorageTile> teaStorageExtenders = new HashSet<>();
+
+ public static TeaNetwork getNetwork(UUID player) {
+ PlayerData p = PlayerDataManager.getPlayer(player);
+ if (p == null) return null;
+ TeaNetwork n = p.teaNetwork;
+ if (n == null) {
+ p.teaNetwork = new TeaNetwork();
+ p.teaNetwork.owner = p;
+ return p.teaNetwork;
+ }
+ n.owner = p;
+ return n;
+ }
+
+ public boolean canAfford(long price, boolean take) {
+ return canAfford(BigInteger.valueOf(price), take);
+ }
+
+ public boolean canAfford(BigInteger price, boolean take) {
+ if (teaAmount.compareTo(price) >= 0) {
+ if (take) {
+ teaAmount = teaAmount.subtract(price);
+ markDirty();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addTea(long toAdd) {
+ return addTea(BigInteger.valueOf(toAdd));
+ }
+
+ public boolean addTea(BigInteger toAdd) {
+ BigInteger newValue = teaAmount.add(toAdd);
+ if (newValue.compareTo(teaLimit) > 0) return false;
+ teaAmount = teaAmount.add(toAdd);
+ markDirty();
+ return true;
+ }
+
+ public boolean canAdd(long toAdd) {
+ return canAdd(BigInteger.valueOf(toAdd));
+ }
+
+ public boolean canAdd(BigInteger toAdd) {
+ return teaAmount.add(toAdd)
+ .compareTo(teaLimit) <= 0;
+ }
+
+ public void registerTeaStorageExtender(TeaStorageTile storageTile) {
+ if (teaStorageExtenders.add(storageTile)) teaLimit = teaLimit.add(storageTile.teaExtendAmount());
+ }
+
+ public void unregisterTeaStorageExtender(TeaStorageTile storageTile) {
+ if (teaStorageExtenders.remove(storageTile)) teaLimit = teaLimit.subtract(storageTile.teaExtendAmount());
+ }
+
+ public void markDirty() {
+ owner.markDirty();
+ }
+
+ public NBTTagCompound toNBT() {
+ NBTTagCompound nbt = new NBTTagCompound();
+ nbt.setByteArray("teaAmount", teaAmount.toByteArray());
+ return nbt;
+ }
+
+ public static TeaNetwork fromNBT(NBTTagCompound nbt) {
+ TeaNetwork teaNetwork = new TeaNetwork();
+ teaNetwork.teaAmount = new BigInteger(nbt.getByteArray("teaAmount"));
+ return teaNetwork;
+ }
+}
diff --git a/src/main/java/kubatech/api/tileentity/CustomTileEntityPacketHandler.java b/src/main/java/kubatech/api/tileentity/CustomTileEntityPacketHandler.java
new file mode 100644
index 0000000000..4fa014cfc0
--- /dev/null
+++ b/src/main/java/kubatech/api/tileentity/CustomTileEntityPacketHandler.java
@@ -0,0 +1,28 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.tileentity;
+
+import kubatech.network.CustomTileEntityPacket;
+
+public interface CustomTileEntityPacketHandler {
+
+ void HandleCustomPacket(CustomTileEntityPacket customdata);
+}
diff --git a/src/main/java/kubatech/api/utils/ItemUtils.java b/src/main/java/kubatech/api/utils/ItemUtils.java
new file mode 100644
index 0000000000..2fc34057c3
--- /dev/null
+++ b/src/main/java/kubatech/api/utils/ItemUtils.java
@@ -0,0 +1,26 @@
+package kubatech.api.utils;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+public class ItemUtils {
+
+ public static NBTTagCompound writeItemStackToNBT(ItemStack stack) {
+ NBTTagCompound compound = new NBTTagCompound();
+
+ stack.writeToNBT(compound);
+ compound.setInteger("IntCount", stack.stackSize);
+
+ return compound;
+ }
+
+ public static ItemStack readItemStackFromNBT(NBTTagCompound compound) {
+ ItemStack stack = ItemStack.loadItemStackFromNBT(compound);
+
+ if (stack == null) return null;
+
+ if (compound.hasKey("IntCount")) stack.stackSize = compound.getInteger("IntCount");
+
+ return stack;
+ }
+}
diff --git a/src/main/java/kubatech/api/utils/ModUtils.java b/src/main/java/kubatech/api/utils/ModUtils.java
new file mode 100644
index 0000000000..a055965cff
--- /dev/null
+++ b/src/main/java/kubatech/api/utils/ModUtils.java
@@ -0,0 +1,38 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.utils;
+
+import net.minecraft.launchwrapper.Launch;
+
+import cpw.mods.fml.common.FMLCommonHandler;
+
+public class ModUtils {
+
+ public static final boolean isDeobfuscatedEnvironment = (boolean) Launch.blackboard
+ .get("fml.deobfuscatedEnvironment");
+ public static boolean isClientSided = false;
+
+ public static boolean isClientThreaded() {
+ return FMLCommonHandler.instance()
+ .getEffectiveSide()
+ .isClient();
+ }
+}
diff --git a/src/main/java/kubatech/api/utils/StringUtils.java b/src/main/java/kubatech/api/utils/StringUtils.java
new file mode 100644
index 0000000000..68f6c8249f
--- /dev/null
+++ b/src/main/java/kubatech/api/utils/StringUtils.java
@@ -0,0 +1,58 @@
+/*
+ * spotless:off
+ * KubaTech - Gregtech Addon
+ * Copyright (C) 2022 - 2024 kuba6000
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <https://www.gnu.org/licenses/>.
+ * spotless:on
+ */
+
+package kubatech.api.utils;
+
+import net.minecraft.util.EnumChatFormatting;
+
+import gregtech.api.enums.GT_Values;
+
+public class StringUtils {
+
+ private static final String[] rainbow = new String[] { EnumChatFormatting.DARK_RED.toString(),
+ EnumChatFormatting.RED.toString(), EnumChatFormatting.GOLD.toString(), EnumChatFormatting.YELLOW.toString(),
+ EnumChatFormatting.DARK_GREEN.toString(), EnumChatFormatting.GREEN.toString(),
+ EnumChatFormatting.AQUA.toString(), EnumChatFormatting.DARK_AQUA.toString(),
+ EnumChatFormatting.DARK_BLUE.toString(), EnumChatFormatting.BLUE.toString(),
+ EnumChatFormatting.LIGHT_PURPLE.toString(), EnumChatFormatting.DARK_PURPLE.toString(),
+ EnumChatFormatting.WHITE.toString(), EnumChatFormatting.GRAY.toString(),
+ EnumChatFormatting.DARK_GRAY.toString(), EnumChatFormatting.BLACK.toString(), };
+
+ public static String applyRainbow(String str, int offset, String additional) {
+ StringBuilder final_string = new StringBuilder();
+ int i = offset;
+ for (char c : str.toCharArray()) final_string.append(rainbow[i++ % rainbow.length])
+ .append(additional)
+ .append(c);
+ return final_string.toString();
+ }
+
+ public static String applyRainbow(String str, int offset) {
+ return applyRainbow(str, offset, "");
+ }
+
+ public static String applyRainbow(String str) {
+ return applyRainbow(str, 0, "");
+ }
+
+ public static String voltageTooltipFormatted(int tier) {
+ return GT_Values.TIER_COLORS[tier] + GT_Values.VN[tier] + EnumChatFormatting.GRAY;
+ }
+}