aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/kubatech
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/kubatech')
-rw-r--r--src/main/java/kubatech/ClientProxy.java75
-rw-r--r--src/main/java/kubatech/CommonProxy.java96
-rw-r--r--src/main/java/kubatech/FMLEventHandler.java42
-rw-r--r--src/main/java/kubatech/Tags.java30
-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
-rw-r--r--src/main/java/kubatech/client/effect/CropRenderer.java92
-rw-r--r--src/main/java/kubatech/client/effect/EntityRenderer.java210
-rw-r--r--src/main/java/kubatech/client/effect/MegaApiaryBeesRenderer.java127
-rw-r--r--src/main/java/kubatech/commands/CommandBees.java149
-rw-r--r--src/main/java/kubatech/commands/CommandConfig.java103
-rw-r--r--src/main/java/kubatech/commands/CommandHandler.java156
-rw-r--r--src/main/java/kubatech/commands/CommandHelp.java86
-rw-r--r--src/main/java/kubatech/commands/CommandTea.java142
-rw-r--r--src/main/java/kubatech/config/Config.java102
-rw-r--r--src/main/java/kubatech/kubatech.java210
-rw-r--r--src/main/java/kubatech/loaders/BlockLoader.java51
-rw-r--r--src/main/java/kubatech/loaders/DEFCRecipes.java331
-rw-r--r--src/main/java/kubatech/loaders/EIGBucketLoader.java27
-rw-r--r--src/main/java/kubatech/loaders/ItemLoader.java149
-rw-r--r--src/main/java/kubatech/loaders/MTLoader.java44
-rw-r--r--src/main/java/kubatech/loaders/MobHandlerLoader.java242
-rw-r--r--src/main/java/kubatech/loaders/RecipeLoader.java355
-rw-r--r--src/main/java/kubatech/loaders/TCLoader.java153
-rw-r--r--src/main/java/kubatech/loaders/block/defc/DEFCCasingBlock.java63
-rw-r--r--src/main/java/kubatech/loaders/block/defc/DEFCCasingItemBlock.java36
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/BlockProxy.java111
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/IProxyTileEntityProvider.java29
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/KubaBlock.java198
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/KubaItemBlock.java73
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/blocks/TeaAcceptor.java64
-rw-r--r--src/main/java/kubatech/loaders/block/kubablock/blocks/TeaStorage.java66
-rw-r--r--src/main/java/kubatech/loaders/item/IItemProxyGUI.java31
-rw-r--r--src/main/java/kubatech/loaders/item/ItemProxy.java122
-rw-r--r--src/main/java/kubatech/loaders/item/KubaItems.java144
-rw-r--r--src/main/java/kubatech/loaders/item/items/Tea.java72
-rw-r--r--src/main/java/kubatech/loaders/item/items/TeaCollection.java209
-rw-r--r--src/main/java/kubatech/loaders/item/items/TeaIngredient.java41
-rw-r--r--src/main/java/kubatech/loaders/item/items/TeaUltimate.java216
-rw-r--r--src/main/java/kubatech/nei/IMCForNEI.java46
-rw-r--r--src/main/java/kubatech/nei/NEI_Config.java69
-rw-r--r--src/main/java/kubatech/network/CustomTileEntityPacket.java155
-rw-r--r--src/main/java/kubatech/network/LoadConfigPacket.java52
-rw-r--r--src/main/java/kubatech/savedata/PlayerData.java49
-rw-r--r--src/main/java/kubatech/savedata/PlayerDataManager.java116
-rw-r--r--src/main/java/kubatech/standalone.java16
-rw-r--r--src/main/java/kubatech/tileentity/TeaAcceptorTile.java223
-rw-r--r--src/main/java/kubatech/tileentity/TeaStorageTile.java83
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_DEFusionCrafter.java281
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeEntityCrusher.java820
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java1285
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_MegaIndustrialApiary.java1175
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java73
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java905
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java77
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java286
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java158
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java95
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java91
78 files changed, 13490 insertions, 0 deletions
diff --git a/src/main/java/kubatech/ClientProxy.java b/src/main/java/kubatech/ClientProxy.java
new file mode 100644
index 0000000000..d9b1f5cf17
--- /dev/null
+++ b/src/main/java/kubatech/ClientProxy.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import cpw.mods.fml.common.event.FMLInitializationEvent;
+import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
+import cpw.mods.fml.common.event.FMLPostInitializationEvent;
+import cpw.mods.fml.common.event.FMLPreInitializationEvent;
+import cpw.mods.fml.common.event.FMLServerAboutToStartEvent;
+import cpw.mods.fml.common.event.FMLServerStartedEvent;
+import cpw.mods.fml.common.event.FMLServerStartingEvent;
+import cpw.mods.fml.common.event.FMLServerStoppedEvent;
+import cpw.mods.fml.common.event.FMLServerStoppingEvent;
+import kubatech.api.utils.ModUtils;
+import kubatech.nei.IMCForNEI;
+
+@SuppressWarnings("unused")
+public class ClientProxy extends CommonProxy {
+
+ public void preInit(FMLPreInitializationEvent event) {
+ ModUtils.isClientSided = true;
+ super.preInit(event);
+ }
+
+ public void init(FMLInitializationEvent event) {
+ super.init(event);
+ IMCForNEI.IMCSender();
+ }
+
+ public void postInit(FMLPostInitializationEvent event) {
+ super.postInit(event);
+ }
+
+ public void serverAboutToStart(FMLServerAboutToStartEvent event) {
+ super.serverAboutToStart(event);
+ }
+
+ public void serverStarting(FMLServerStartingEvent event) {
+ super.serverStarting(event);
+ }
+
+ public void serverStarted(FMLServerStartedEvent event) {
+ super.serverStarted(event);
+ }
+
+ public void serverStopping(FMLServerStoppingEvent event) {
+ super.serverStopping(event);
+ }
+
+ public void serverStopped(FMLServerStoppedEvent event) {
+ super.serverStopped(event);
+ }
+
+ public void loadComplete(FMLLoadCompleteEvent event) {
+ super.loadComplete(event);
+ }
+}
diff --git a/src/main/java/kubatech/CommonProxy.java b/src/main/java/kubatech/CommonProxy.java
new file mode 100644
index 0000000000..51277e14d1
--- /dev/null
+++ b/src/main/java/kubatech/CommonProxy.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import static gregtech.api.enums.Mods.MineTweaker;
+import static gregtech.api.enums.Mods.Thaumcraft;
+import static kubatech.loaders.BlockLoader.registerBlocks;
+import static kubatech.loaders.ItemLoader.registerItems;
+
+import net.minecraftforge.common.MinecraftForge;
+
+import cpw.mods.fml.common.FMLCommonHandler;
+import cpw.mods.fml.common.event.FMLInitializationEvent;
+import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
+import cpw.mods.fml.common.event.FMLPostInitializationEvent;
+import cpw.mods.fml.common.event.FMLPreInitializationEvent;
+import cpw.mods.fml.common.event.FMLServerAboutToStartEvent;
+import cpw.mods.fml.common.event.FMLServerStartedEvent;
+import cpw.mods.fml.common.event.FMLServerStartingEvent;
+import cpw.mods.fml.common.event.FMLServerStoppedEvent;
+import cpw.mods.fml.common.event.FMLServerStoppingEvent;
+import gregtech.api.enums.Mods;
+import kubatech.commands.CommandHandler;
+import kubatech.config.Config;
+import kubatech.loaders.EIGBucketLoader;
+import kubatech.loaders.MTLoader;
+import kubatech.loaders.MobHandlerLoader;
+import kubatech.loaders.RecipeLoader;
+import kubatech.loaders.TCLoader;
+import kubatech.savedata.PlayerDataManager;
+
+public class CommonProxy {
+
+ public void preInit(FMLPreInitializationEvent event) {
+ kubatech.info("Initializing ! Version: " + Tags.VERSION);
+
+ Config.init(event.getModConfigurationDirectory());
+ Config.synchronizeConfiguration();
+ FMLCommonHandler.instance()
+ .bus()
+ .register(new FMLEventHandler());
+ MinecraftForge.EVENT_BUS.register(new PlayerDataManager());
+ registerItems();
+ registerBlocks();
+ if (Mods.MobsInfo.isModLoaded()) {
+ MobHandlerLoader.init();
+ }
+ }
+
+ public void init(FMLInitializationEvent event) {
+ RecipeLoader.registerMTEs(); // crashes in preinit because EIG references some blocks from other mods.
+ if (MineTweaker.isModLoaded()) {
+ MTLoader.init();
+ }
+ }
+
+ public void postInit(FMLPostInitializationEvent event) {
+ RecipeLoader.addRecipes();
+ EIGBucketLoader.LoadEIGBuckets();
+ if (Thaumcraft.isModLoaded()) TCLoader.init();
+ }
+
+ public void serverAboutToStart(FMLServerAboutToStartEvent event) {}
+
+ public void serverStarting(FMLServerStartingEvent event) {
+ RecipeLoader.addRecipesLate();
+ CommandHandler cmd = new CommandHandler();
+ event.registerServerCommand(cmd);
+ }
+
+ public void serverStarted(FMLServerStartedEvent event) {}
+
+ public void serverStopping(FMLServerStoppingEvent event) {}
+
+ public void serverStopped(FMLServerStoppedEvent event) {}
+
+ public void loadComplete(FMLLoadCompleteEvent event) {}
+}
diff --git a/src/main/java/kubatech/FMLEventHandler.java b/src/main/java/kubatech/FMLEventHandler.java
new file mode 100644
index 0000000000..02e9bb19a7
--- /dev/null
+++ b/src/main/java/kubatech/FMLEventHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import net.minecraft.entity.player.EntityPlayerMP;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import cpw.mods.fml.common.gameevent.PlayerEvent;
+import kubatech.api.helpers.UUIDFinder;
+import kubatech.network.LoadConfigPacket;
+import kubatech.savedata.PlayerDataManager;
+
+public class FMLEventHandler {
+
+ // Gets fired only server-sided
+ @SubscribeEvent
+ public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
+ if (!(event.player instanceof EntityPlayerMP)) return;
+ UUIDFinder.updateMapping(event.player.getCommandSenderName(), event.player.getPersistentID());
+ PlayerDataManager.initializePlayer((EntityPlayerMP) event.player);
+ kubatech.info("Sending config to " + event.player.getDisplayName());
+ kubatech.NETWORK.sendTo(LoadConfigPacket.instance, (EntityPlayerMP) event.player);
+ }
+}
diff --git a/src/main/java/kubatech/Tags.java b/src/main/java/kubatech/Tags.java
new file mode 100644
index 0000000000..3fcc0caee0
--- /dev/null
+++ b/src/main/java/kubatech/Tags.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import gregtech.GT_Version;
+
+public class Tags {
+
+ public static final String MODID = "kubatech";
+ public static final String MODNAME = "KubaTech";
+ public static final String VERSION = GT_Version.VERSION;
+}
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;
+ }
+}
diff --git a/src/main/java/kubatech/client/effect/CropRenderer.java b/src/main/java/kubatech/client/effect/CropRenderer.java
new file mode 100644
index 0000000000..5dbf229d6f
--- /dev/null
+++ b/src/main/java/kubatech/client/effect/CropRenderer.java
@@ -0,0 +1,92 @@
+/*
+ * 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.client.effect;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.particle.EntityFX;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.init.Blocks;
+import net.minecraft.world.World;
+
+import org.lwjgl.opengl.GL11;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+@SideOnly(Side.CLIENT)
+public class CropRenderer extends EntityFX {
+
+ int[] meta = new int[8];
+
+ public CropRenderer(World world, int x, int y, int z, int age) {
+ super(world, (double) x, ((double) y - 0.0625d), (double) z);
+ this.prevPosX = this.posX;
+ this.prevPosY = this.posY;
+ this.prevPosZ = this.posZ;
+ this.particleMaxAge = age;
+ for (int i = 0; i < 8; i++) this.meta[i] = this.rand.nextInt(8);
+ }
+
+ @Override
+ public void onUpdate() {
+ if (this.particleAge++ >= this.particleMaxAge) this.setDead();
+ }
+
+ @Override
+ public void renderParticle(Tessellator p_70539_1_, float p_70539_2_, float p_70539_3_, float p_70539_4_,
+ float p_70539_5_, float p_70539_6_, float p_70539_7_) {
+ Tessellator tessellator = Tessellator.instance;
+ Minecraft.getMinecraft()
+ .getTextureManager()
+ .bindTexture(TextureMap.locationBlocksTexture);
+ tessellator.startDrawingQuads();
+ tessellator.disableColor();
+ GL11.glColor4f(1.f, 1.f, 1.f, 1.f);
+ GL11.glEnable(GL11.GL_ALPHA_TEST);
+ GL11.glDepthMask(true);
+ tessellator.setBrightness(
+ Blocks.wheat
+ .getMixedBrightnessForBlock(this.worldObj, (int) this.posX + 1, (int) this.posY, (int) this.posZ));
+ tessellator.setColorRGBA(255, 255, 255, 255);
+ double f12 = this.posY - interpPosY;
+ int i = 0;
+ for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) {
+ if (x == 0 && z == 0) continue;
+ double f11 = (this.posX + (double) x) - interpPosX;
+ double f13 = (this.posZ + (double) z) - interpPosZ;
+ RenderBlocks.getInstance()
+ .renderBlockCropsImpl(Blocks.wheat, meta[i++], f11, f12, f13);
+ }
+ tessellator.draw();
+ }
+
+ @Override
+ public int getFXLayer() {
+ return 3;
+ }
+
+ @Override
+ public boolean shouldRenderInPass(int pass) {
+ return pass == 3;
+ }
+}
diff --git a/src/main/java/kubatech/client/effect/EntityRenderer.java b/src/main/java/kubatech/client/effect/EntityRenderer.java
new file mode 100644
index 0000000000..1a33899e15
--- /dev/null
+++ b/src/main/java/kubatech/client/effect/EntityRenderer.java
@@ -0,0 +1,210 @@
+/*
+ * 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.client.effect;
+
+import static net.minecraft.client.renderer.entity.RenderManager.instance;
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosX;
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosY;
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosZ;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.particle.EntityFX;
+import net.minecraft.client.renderer.OpenGlHelper;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.EntityLiving;
+import net.minecraft.entity.boss.BossStatus;
+import net.minecraft.world.World;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL12;
+import org.lwjgl.util.glu.GLU;
+
+import com.kuba6000.mobsinfo.api.utils.MobUtils;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import kubatech.Tags;
+import kubatech.config.Config;
+
+@SideOnly(Side.CLIENT)
+public class EntityRenderer extends EntityFX {
+
+ private static final Logger LOG = LogManager.getLogger(Tags.MODID + "[Entity Renderer]");
+ private EntityLiving entityToRender = null;
+
+ public EntityRenderer(World p_i1218_1_, double x, double y, double z, int age) {
+ super(p_i1218_1_, x + 0.5d, y, z + 0.5d);
+ this.particleMaxAge = age;
+ this.particleAge = 0;
+ }
+
+ public EntityRenderer(EntityRenderer r, int age) {
+ super(r.worldObj, r.posX, r.posY, r.posZ);
+ this.particleMaxAge = age;
+ this.particleAge = 0;
+ this.ticksExisted = r.ticksExisted;
+ this.entityToRender = r.entityToRender;
+ }
+
+ @Override
+ protected void entityInit() {}
+
+ @Override
+ public void onUpdate() {
+ if (this.entityToRender == null) return;
+ this.ticksExisted++;
+ if (ticksExisted % 20 == 0) entityToRender.hurtTime = 10;
+ else if (entityToRender.hurtTime > 0) entityToRender.hurtTime--;
+ if (this.particleAge++ == this.particleMaxAge) {
+ this.setDead();
+ }
+ }
+
+ @Override
+ public boolean shouldRenderInPass(int pass) {
+ return pass == 3;
+ }
+
+ @Override
+ public int getFXLayer() {
+ return 3;
+ }
+
+ public void setEntity(EntityLiving entity) {
+ this.entityToRender = entity;
+ }
+
+ @Override
+ public void renderParticle(Tessellator p_70539_1_, float p_70539_2_, float p_70539_3_, float p_70539_4_,
+ float p_70539_5_, float p_70539_6_, float p_70539_7_) {
+ if (entityToRender == null) return;
+
+ GL11.glEnable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_COLOR_MATERIAL);
+
+ entityToRender.worldObj = this.worldObj;
+
+ // quiver still bugged a bit, but it is on the skeleton now
+ entityToRender.setPosition(this.posX, this.posY + 1d /* for some reason quiver renders too low? */, this.posZ);
+ entityToRender.lastTickPosX = entityToRender.posX;
+ entityToRender.lastTickPosY = entityToRender.posY;
+ entityToRender.lastTickPosZ = entityToRender.posZ;
+
+ Minecraft mc = Minecraft.getMinecraft();
+
+ double rotation;
+ double headrotation;
+ {
+ double x1 = this.posX;
+ double x2 = mc.thePlayer.posX;
+ double y1 = this.posZ;
+ double y2 = mc.thePlayer.posZ;
+ double k = Math.toDegrees(Math.atan2(x2 - x1, y1 - y2));
+ if (k < 0d) k += 360d;
+ k -= 180d;
+ rotation = k;
+ }
+
+ {
+ double y1 = this.posY + entityToRender.getEyeHeight();
+ double y2 = mc.thePlayer.posY + mc.thePlayer.getEyeHeight();
+ double d = mc.thePlayer.getDistance(this.posX, y2, this.posZ);
+ double k = Math.toDegrees(Math.atan2(y1 - y2, d));
+ if (k < 0d) k += 360d;
+ headrotation = k;
+ }
+
+ entityToRender.prevRotationYawHead = entityToRender.rotationYawHead;
+ entityToRender.prevRenderYawOffset = entityToRender.renderYawOffset;
+ entityToRender.prevRotationPitch = entityToRender.rotationPitch;
+ entityToRender.renderYawOffset = (float) rotation;
+ entityToRender.rotationYawHead = (float) rotation;
+ entityToRender.rotationPitch = (float) headrotation;
+
+ float p_147936_2_ = 0.5f;
+
+ float f1 = entityToRender.prevRotationYaw
+ + (entityToRender.rotationYaw - entityToRender.prevRotationYaw) * p_147936_2_;
+ int i = entityToRender.getBrightnessForRender(p_147936_2_);
+
+ if (entityToRender.isBurning()) {
+ i = 15728880;
+ }
+
+ int j = i % 65536;
+ int k = i / 65536;
+ OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float) j, (float) k);
+ GL11.glColor4f(1f, 1f, 1f, 1F);
+ RenderHelper.enableStandardItemLighting();
+ GL11.glMatrixMode(GL11.GL_MODELVIEW);
+
+ GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
+
+ int stackdepth = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
+ GL11.glPushMatrix();
+ GL11.glTranslatef(
+ (float) (this.posX - renderPosX),
+ (float) (this.posY - renderPosY),
+ (float) (this.posZ - renderPosZ));
+ GL11.glEnable(GL12.GL_RESCALE_NORMAL);
+
+ // TODO: Use new scale calculator
+ float desiredScale = MobUtils.getDesiredScale(entityToRender, 2f);
+ if (desiredScale < 1f) GL11.glScalef(desiredScale, desiredScale, desiredScale);
+
+ float healthScale = BossStatus.healthScale;
+ int statusBarTime = BossStatus.statusBarTime;
+ String bossName = BossStatus.bossName;
+ boolean hasColorModifier = BossStatus.hasColorModifier;
+
+ try {
+ instance.renderEntityWithPosYaw(entityToRender, 0f, 0f, 0f, f1, p_147936_2_);
+ } catch (Throwable ex) {
+ Tessellator tes = Tessellator.instance;
+ try {
+ tes.draw();
+ } catch (Exception ignored) {}
+ }
+
+ BossStatus.healthScale = healthScale;
+ BossStatus.statusBarTime = statusBarTime;
+ BossStatus.bossName = bossName;
+ BossStatus.hasColorModifier = hasColorModifier;
+
+ GL11.glMatrixMode(GL11.GL_MODELVIEW_MATRIX);
+ stackdepth -= GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
+ if (stackdepth < 0) for (; stackdepth < 0; stackdepth++) GL11.glPopMatrix();
+ if (stackdepth > 0) for (; stackdepth > 0; stackdepth--) GL11.glPushMatrix();
+
+ GL11.glPopAttrib();
+
+ int err;
+ while ((err = GL11.glGetError()) != GL11.GL_NO_ERROR) if (Config.Debug.showRenderErrors) LOG.error(
+ EntityList.getEntityString(entityToRender) + " | GL ERROR: " + err + " / " + GLU.gluErrorString(err));
+
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glDisable(GL11.GL_COLOR_MATERIAL);
+ }
+}
diff --git a/src/main/java/kubatech/client/effect/MegaApiaryBeesRenderer.java b/src/main/java/kubatech/client/effect/MegaApiaryBeesRenderer.java
new file mode 100644
index 0000000000..b23a789bf5
--- /dev/null
+++ b/src/main/java/kubatech/client/effect/MegaApiaryBeesRenderer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.client.effect;
+
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosX;
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosY;
+import static net.minecraft.client.renderer.entity.RenderManager.renderPosZ;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.particle.EntityFX;
+import net.minecraft.client.renderer.OpenGlHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL12;
+
+import kubatech.api.enums.ItemList;
+
+public class MegaApiaryBeesRenderer extends EntityFX {
+
+ public MegaApiaryBeesRenderer(World world, double x, double y, double z, int age) {
+ super(world, x, y + 2, z);
+ this.particleMaxAge = age;
+ }
+
+ @Override
+ public void onUpdate() {
+ if (this.particleAge++ == this.particleMaxAge) this.setDead();
+ if (this.particleAge % 4 == 0) if (this.particleAge % 8 == 0) this.posY += 0.1;
+ else this.posY -= 0.1;
+ }
+
+ @Override
+ public boolean shouldRenderInPass(int pass) {
+ return pass == 3;
+ }
+
+ @Override
+ public int getFXLayer() {
+ return 3;
+ }
+
+ @Override
+ public void renderParticle(Tessellator p_70539_1_, float p_70539_2_, float p_70539_3_, float p_70539_4_,
+ float p_70539_5_, float p_70539_6_, float p_70539_7_) {
+
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ OpenGlHelper.glBlendFunc(770, 771, 1, 0);
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(
+ (float) (this.posX - renderPosX),
+ (float) (this.posY - renderPosY),
+ (float) (this.posZ - renderPosZ));
+ GL11.glEnable(GL12.GL_RESCALE_NORMAL);
+ GL11.glRotatef(180f, 1f, 0f, 0f);
+
+ Minecraft.getMinecraft()
+ .getTextureManager()
+ .bindTexture(TextureMap.locationItemsTexture);
+
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_ALPHA_TEST);
+ GL11.glEnable(GL11.GL_BLEND);
+
+ GL11.glColor4f(1f, 1f, 1f, 1F);
+
+ IIcon icon = ItemList.Beeeeee.get(1)
+ .getIconIndex();
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(0f, 0f, -4f);
+ GL11.glScalef(0.1f, 0.1f, 0.1f);
+ RenderItem.getInstance()
+ .renderIcon(0, 0, icon, 16, 16);
+ GL11.glPopMatrix();
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(1f, 0f, 3f);
+ GL11.glRotatef(180f, 0f, 1f, 0f);
+ GL11.glScalef(0.1f, 0.1f, 0.1f);
+ RenderItem.getInstance()
+ .renderIcon(0, 0, icon, 16, 16);
+ GL11.glPopMatrix();
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(4f, 0f, -1f);
+ GL11.glRotatef(-90f, 0f, 1f, 0f);
+ GL11.glScalef(0.1f, 0.1f, 0.1f);
+ RenderItem.getInstance()
+ .renderIcon(0, 0, icon, 16, 16);
+ GL11.glPopMatrix();
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(-3f, 0f, 1f);
+ GL11.glRotatef(90f, 0f, 1f, 0f);
+ GL11.glScalef(0.1f, 0.1f, 0.1f);
+ RenderItem.getInstance()
+ .renderIcon(0, 0, icon, 16, 16);
+ GL11.glPopMatrix();
+
+ GL11.glPopMatrix();
+ }
+}
diff --git a/src/main/java/kubatech/commands/CommandBees.java b/src/main/java/kubatech/commands/CommandBees.java
new file mode 100644
index 0000000000..2df0c294a6
--- /dev/null
+++ b/src/main/java/kubatech/commands/CommandBees.java
@@ -0,0 +1,149 @@
+/*
+ * 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.commands;
+
+import static forestry.api.apiculture.BeeManager.beeRoot;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+
+import com.google.common.io.Files;
+
+import forestry.api.apiculture.IAlleleBeeSpecies;
+import forestry.api.apiculture.IBee;
+import forestry.api.apiculture.IBeeGenome;
+import kubatech.api.utils.ModUtils;
+
+@CommandHandler.ChildCommand
+public class CommandBees extends CommandBase {
+
+ @Override
+ public String getCommandName() {
+ return "bees";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender p_71518_1_) {
+ return "bees";
+ }
+
+ @Override
+ public int getRequiredPermissionLevel() {
+ return 4;
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ @Override
+ public void processCommand(ICommandSender p_71515_1_, String[] p_71515_2_) {
+
+ if (!ModUtils.isClientSided) {
+ p_71515_1_
+ .addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "This command is single-player only!"));
+ return;
+ }
+
+ // https://docs.google.com/spreadsheets/d/1XaNGtJZ8WYv2nMnYcixTX4Jz3qUr71RadiKT5pToYFk/edit?usp=sharing
+ try {
+ File f = new File("bees.txt");
+ BufferedWriter writer = Files.newWriter(f, StandardCharsets.UTF_8);
+ String delimer = ",";
+
+ writer.write(
+ "Bee,CHANCE,OLD_0.6S_0UP,OLD_0.6S_8UP,OLD_1.7S_0UP,OLD_1.7S_8UP,NEW_0.6S_0UP_1T,NEW_0.6S_8UP_1T,NEW_1.7S_0UP_1T,NEW_1.7S_8UP_1T,NEW_1.7S_0UP_8T,NEW_1.7S_8UP_8T\n");
+
+ List<IBee> bees = beeRoot.getIndividualTemplates();
+ for (IBee bee : bees) {
+ // System.out.println("Bee: " + bee.getDisplayName());
+ StringBuilder b = new StringBuilder(bee.getDisplayName());
+ b.append(",-,-,-,-,-,-,-,-,-,-\n");
+ IBeeGenome genome = bee.getGenome();
+ IAlleleBeeSpecies primary = genome.getPrimary();
+ IAlleleBeeSpecies secondary = genome.getSecondary();
+ primary.getProductChances()
+ .forEach((k, v) -> printData("[PRIMARY]", k, v, delimer, b));
+ secondary.getProductChances()
+ .forEach((k, v) -> printData("[SECONDARY]", k, v / 2f, delimer, b));
+ primary.getSpecialtyChances()
+ .forEach((k, v) -> printData("[SPECIALITY]", k, v, delimer, b));
+ writer.write(b.toString());
+ }
+
+ writer.flush();
+ writer.close();
+ p_71515_1_.addChatMessage(new ChatComponentText(f.getAbsolutePath()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void printData(String nameOfData, ItemStack k, float v, String delimer, StringBuilder b) {
+ b.append(nameOfData);
+ b.append(k.getDisplayName());
+ b.append(delimer);
+ b.append(format(v));
+ b.append(delimer);
+ b.append(format(productChanceOld(0, 0.6d, v)));
+ b.append(delimer);
+ b.append(format(productChanceOld(8, 0.6d, v)));
+ b.append(delimer);
+ b.append(format(productChanceOld(0, 1.7d, v)));
+ b.append(delimer);
+ b.append(format(productChanceOld(8, 1.7d, v)));
+ b.append(delimer);
+ b.append(format(productChanceNew(0, 0.6d, v, 1)));
+ b.append(delimer);
+ b.append(format(productChanceNew(8, 0.6d, v, 1)));
+ b.append(delimer);
+ b.append(format(productChanceNew(0, 1.7d, v, 1)));
+ b.append(delimer);
+ b.append(format(productChanceNew(8, 1.7d, v, 1)));
+ b.append(delimer);
+ b.append(format(productChanceNew(0, 1.7d, v, 8)));
+ b.append(delimer);
+ b.append(format(productChanceNew(8, 1.7d, v, 8)));
+ b.append("\n");
+ }
+
+ private String format(double chance) {
+ return String.format("%.2f%%", chance * 100d);
+ }
+
+ private double productChanceNew(int upgradeCount, double beeSpeed, double chance, int t) {
+ chance *= 100f;
+ float productionModifier = (float) upgradeCount * 0.25f;
+ return (float) (((1f + t / 6f) * Math.sqrt(chance) * 2f * (1f + beeSpeed)
+ + Math.pow(productionModifier, Math.cbrt(chance))
+ - 3f) / 100f);
+ }
+
+ private double productChanceOld(int upgradeCount, double beeSpeed, double chance) {
+ return chance * beeSpeed * Math.pow(1.2d, upgradeCount);
+ }
+}
diff --git a/src/main/java/kubatech/commands/CommandConfig.java b/src/main/java/kubatech/commands/CommandConfig.java
new file mode 100644
index 0000000000..be759be2c9
--- /dev/null
+++ b/src/main/java/kubatech/commands/CommandConfig.java
@@ -0,0 +1,103 @@
+/*
+ * 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.commands;
+
+import static kubatech.commands.CommandConfig.Translations.INVALID_OPTION;
+import static kubatech.commands.CommandConfig.Translations.SUCCESS;
+import static kubatech.commands.CommandConfig.Translations.USAGE;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.StatCollector;
+
+import kubatech.config.Config;
+import kubatech.kubatech;
+import kubatech.network.LoadConfigPacket;
+
+@CommandHandler.ChildCommand
+public class CommandConfig extends CommandBase {
+
+ enum Translations {
+
+ INVALID_OPTION,
+ SUCCESS,
+ USAGE,;
+
+ final String key;
+
+ Translations() {
+ key = "kubatech.command.config." + this.name()
+ .toLowerCase();
+ }
+
+ public String get() {
+ return StatCollector.translateToLocal(key);
+ }
+
+ public String get(Object... args) {
+ return StatCollector.translateToLocalFormatted(key, args);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return get();
+ }
+ }
+
+ @Override
+ public String getCommandName() {
+ return "config";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender p_71518_1_) {
+ return "config " + USAGE.get();
+ }
+
+ @Override
+ public int getRequiredPermissionLevel() {
+ return 4;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void processCommand(ICommandSender p_71515_1_, String[] p_71515_2_) {
+ if (p_71515_2_.length == 0 || !p_71515_2_[0].equals("reload")) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID_OPTION.get()));
+ return;
+ }
+ Config.synchronizeConfiguration();
+ MinecraftServer.getServer()
+ .getConfigurationManager().playerEntityList.forEach(p -> {
+ if (!(p instanceof EntityPlayerMP)) return;
+ kubatech.info("Sending config to " + ((EntityPlayerMP) p).getDisplayName());
+ kubatech.NETWORK.sendTo(LoadConfigPacket.instance, (EntityPlayerMP) p);
+ });
+ p_71515_1_.addChatMessage(new ChatComponentText(SUCCESS.get()));
+ }
+}
diff --git a/src/main/java/kubatech/commands/CommandHandler.java b/src/main/java/kubatech/commands/CommandHandler.java
new file mode 100644
index 0000000000..b0957c9572
--- /dev/null
+++ b/src/main/java/kubatech/commands/CommandHandler.java
@@ -0,0 +1,156 @@
+/*
+ * 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.commands;
+
+import static kubatech.commands.CommandHandler.Translations.CANT_FIND;
+import static kubatech.commands.CommandHandler.Translations.GENERIC_HELP;
+import static kubatech.commands.CommandHandler.Translations.INVALID;
+import static kubatech.commands.CommandHandler.Translations.USAGE;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommand;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.ChatComponentTranslation;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+
+import kubatech.kubatech;
+
+public class CommandHandler extends CommandBase {
+
+ enum Translations {
+
+ INVALID,
+ CANT_FIND,
+ GENERIC_HELP,
+ USAGE,;
+
+ final String key;
+
+ Translations() {
+ key = "kubatech.commandhandler." + this.name()
+ .toLowerCase();
+ }
+
+ public String get() {
+ return StatCollector.translateToLocal(key);
+ }
+
+ public String get(Object... args) {
+ return StatCollector.translateToLocalFormatted(key, args);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return get();
+ }
+ }
+
+ private static final List<String> aliases = Collections.singletonList("kt");
+ public static final HashMap<String, ICommand> commands = new HashMap<>();
+
+ @Override
+ public String getCommandName() {
+ return "kubatech";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender p_71518_1_) {
+ return "kubatech " + USAGE.get();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public List getCommandAliases() {
+ return aliases;
+ }
+
+ @Override
+ public void processCommand(ICommandSender p_71515_1_, String[] p_71515_2_) {
+ if (p_71515_1_.getEntityWorld().isRemote) return;
+ if (p_71515_2_.length == 0) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID.get(getCommandUsage(p_71515_1_))));
+ p_71515_1_.addChatMessage(new ChatComponentText(GENERIC_HELP.get()));
+ return;
+ }
+ if (!commands.containsKey(p_71515_2_[0])) {
+ p_71515_1_.addChatMessage(new ChatComponentText(CANT_FIND.get(p_71515_2_[0])));
+ p_71515_1_.addChatMessage(new ChatComponentText(GENERIC_HELP.get()));
+ return;
+ }
+ ICommand cmd = commands.get(p_71515_2_[0]);
+ if (!cmd.canCommandSenderUseCommand(p_71515_1_)) {
+ ChatComponentTranslation chatcomponenttranslation2 = new ChatComponentTranslation(
+ "commands.generic.permission");
+ chatcomponenttranslation2.getChatStyle()
+ .setColor(EnumChatFormatting.RED);
+ p_71515_1_.addChatMessage(chatcomponenttranslation2);
+ } else cmd.processCommand(
+ p_71515_1_,
+ p_71515_2_.length > 1 ? Arrays.copyOfRange(p_71515_2_, 1, p_71515_2_.length) : new String[0]);
+ }
+
+ @Override
+ public boolean canCommandSenderUseCommand(ICommandSender p_71519_1_) {
+ return true;
+ }
+
+ public static void addCommand(ICommand command) {
+ commands.put(command.getCommandName(), command);
+ }
+
+ static {
+ String ChildCommandDesc = "L" + ChildCommand.class.getName()
+ .replace(".", "/") + ";";
+ kubatech.myClasses.stream()
+ .filter(
+ clazz -> clazz.invisibleAnnotations != null && clazz.invisibleAnnotations.stream()
+ .anyMatch(ann -> ann.desc.equals(ChildCommandDesc)))
+ .forEach(clazz -> {
+ try {
+ addCommand(
+ (ICommand) Class.forName(clazz.name.replace("/", "."))
+ .getConstructor()
+ .newInstance());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ public @interface ChildCommand {}
+}
diff --git a/src/main/java/kubatech/commands/CommandHelp.java b/src/main/java/kubatech/commands/CommandHelp.java
new file mode 100644
index 0000000000..610f330ede
--- /dev/null
+++ b/src/main/java/kubatech/commands/CommandHelp.java
@@ -0,0 +1,86 @@
+/*
+ * 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.commands;
+
+import static kubatech.commands.CommandHelp.Translations.POSSIBLE_COMMANDS;
+import static kubatech.commands.CommandHelp.Translations.USAGE;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.StatCollector;
+
+@CommandHandler.ChildCommand
+public class CommandHelp extends CommandBase {
+
+ enum Translations {
+
+ POSSIBLE_COMMANDS,
+ USAGE,;
+
+ final String key;
+
+ Translations() {
+ key = "kubatech.command.help." + this.name()
+ .toLowerCase();
+ }
+
+ public String get() {
+ return StatCollector.translateToLocal(key);
+ }
+
+ public String get(Object... args) {
+ return StatCollector.translateToLocalFormatted(key, args);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return get();
+ }
+ }
+
+ @Override
+ public String getCommandName() {
+ return "help";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender p_71518_1_) {
+ return "help " + USAGE.get();
+ }
+
+ @Override
+ public boolean canCommandSenderUseCommand(ICommandSender p_71519_1_) {
+ return true;
+ }
+
+ @Override
+ public void processCommand(ICommandSender p_71515_1_, String[] p_71515_2_) {
+ p_71515_1_.addChatMessage(new ChatComponentText(POSSIBLE_COMMANDS.get()));
+ CommandHandler.commands.values()
+ .forEach(
+ c -> p_71515_1_.addChatMessage(new ChatComponentText("/kubatech " + c.getCommandUsage(p_71515_1_))));
+ }
+}
diff --git a/src/main/java/kubatech/commands/CommandTea.java b/src/main/java/kubatech/commands/CommandTea.java
new file mode 100644
index 0000000000..057d2bca69
--- /dev/null
+++ b/src/main/java/kubatech/commands/CommandTea.java
@@ -0,0 +1,142 @@
+/*
+ * 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.commands;
+
+import static kubatech.commands.CommandTea.Translations.INVALID_OPTION;
+import static kubatech.commands.CommandTea.Translations.PLAYER_NOT_FOUND;
+import static kubatech.commands.CommandTea.Translations.SUCCESS_ADD;
+import static kubatech.commands.CommandTea.Translations.SUCCESS_GET;
+import static kubatech.commands.CommandTea.Translations.SUCCESS_SET;
+import static kubatech.commands.CommandTea.Translations.USAGE;
+
+import java.math.BigInteger;
+import java.util.UUID;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.StatCollector;
+
+import kubatech.api.helpers.UUIDFinder;
+import kubatech.api.tea.TeaNetwork;
+
+@CommandHandler.ChildCommand
+public class CommandTea extends CommandBase {
+
+ enum Translations {
+
+ INVALID_OPTION,
+ PLAYER_NOT_FOUND,
+ SUCCESS_GET,
+ SUCCESS_SET,
+ SUCCESS_ADD,
+ USAGE,;
+
+ final String key;
+
+ Translations() {
+ key = "kubatech.command.tea." + this.name()
+ .toLowerCase();
+ }
+
+ public String get() {
+ return StatCollector.translateToLocal(key);
+ }
+
+ public String get(Object... args) {
+ return StatCollector.translateToLocalFormatted(key, args);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return get();
+ }
+ }
+
+ @Override
+ public String getCommandName() {
+ return "tea";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender p_71518_1_) {
+ return "tea " + USAGE.get();
+ }
+
+ @Override
+ public int getRequiredPermissionLevel() {
+ return 4;
+ }
+
+ @Override
+ public void processCommand(ICommandSender p_71515_1_, String[] p_71515_2_) {
+ if (p_71515_2_.length < 2) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID_OPTION.get()));
+ return;
+ }
+ UUID player = UUIDFinder.getUUID(p_71515_2_[0]);
+ if (player == null) {
+ p_71515_1_.addChatMessage(new ChatComponentText(PLAYER_NOT_FOUND.get()));
+ return;
+ }
+ TeaNetwork teaNetwork = TeaNetwork.getNetwork(player);
+ if (!p_71515_2_[1].equalsIgnoreCase("get") && p_71515_2_.length < 3) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID_OPTION.get()));
+ return;
+ }
+ switch (p_71515_2_[1].toLowerCase()) {
+ case "get":
+ p_71515_1_.addChatMessage(new ChatComponentText(SUCCESS_GET.get(p_71515_2_[0], teaNetwork.teaAmount)));
+ break;
+ case "set": {
+ BigInteger tea;
+ try {
+ tea = new BigInteger(p_71515_2_[2]);
+ } catch (NumberFormatException ex) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID_OPTION.get()));
+ return;
+ }
+ teaNetwork.teaAmount = tea;
+ teaNetwork.markDirty();
+ p_71515_1_.addChatMessage(new ChatComponentText(SUCCESS_SET.get(p_71515_2_[0], teaNetwork.teaAmount)));
+ break;
+ }
+ case "add": {
+ BigInteger tea;
+ try {
+ tea = new BigInteger(p_71515_2_[2]);
+ } catch (NumberFormatException ex) {
+ p_71515_1_.addChatMessage(new ChatComponentText(INVALID_OPTION.get()));
+ return;
+ }
+ teaNetwork.addTea(tea);
+ p_71515_1_.addChatMessage(new ChatComponentText(SUCCESS_ADD.get(p_71515_2_[0], teaNetwork.teaAmount)));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
diff --git a/src/main/java/kubatech/config/Config.java b/src/main/java/kubatech/config/Config.java
new file mode 100644
index 0000000000..c2cb59dd5a
--- /dev/null
+++ b/src/main/java/kubatech/config/Config.java
@@ -0,0 +1,102 @@
+/*
+ * 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.config;
+
+import java.io.File;
+
+import net.minecraftforge.common.config.Configuration;
+
+import kubatech.Tags;
+
+public class Config {
+
+ private enum Category {
+
+ MOB_HANDLER("MobHandler"),
+ DEBUG("Debug");
+
+ final String categoryName;
+
+ Category(String s) {
+ categoryName = s;
+ }
+
+ public String get() {
+ return categoryName;
+ }
+
+ @Override
+ public String toString() {
+ return get();
+ }
+ }
+
+ public static class MobHandler {
+
+ public static double playerOnlyDropsModifier = .1d;
+
+ private static void load(Configuration configuration) {
+ Category category = Category.MOB_HANDLER;
+ playerOnlyDropsModifier = configuration
+ .get(
+ category.get(),
+ "PlayerOnlyDropsModifier",
+ .1d,
+ "Hard player only loot (such as core mod drops) will be multiplied by this number")
+ .getDouble();
+ }
+ }
+
+ public static class Debug {
+
+ public static boolean showRenderErrors = false;
+
+ private static void load(Configuration configuration) {
+ Category category = Category.DEBUG;
+ showRenderErrors = configuration.get(category.get(), "ShowRenderErrors", false)
+ .getBoolean();
+ }
+ }
+
+ public static File configFile;
+ public static File configDirectory;
+
+ public static void init(File configFile) {
+ configDirectory = new File(configFile, Tags.MODID);
+ Config.configFile = new File(configDirectory, Tags.MODID + ".cfg");
+ }
+
+ public static File getConfigFile(String file) {
+ return new File(configDirectory, file);
+ }
+
+ public static void synchronizeConfiguration() {
+ Configuration configuration = new Configuration(configFile);
+ configuration.load();
+
+ MobHandler.load(configuration);
+ Debug.load(configuration);
+
+ if (configuration.hasChanged()) {
+ configuration.save();
+ }
+ }
+}
diff --git a/src/main/java/kubatech/kubatech.java b/src/main/java/kubatech/kubatech.java
new file mode 100644
index 0000000000..e847780c61
--- /dev/null
+++ b/src/main/java/kubatech/kubatech.java
@@ -0,0 +1,210 @@
+/*
+ * 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;
+
+import static kubatech.api.enums.ItemList.LegendaryRedTea;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.objectweb.asm.tree.ClassNode;
+
+import cpw.mods.fml.common.Mod;
+import cpw.mods.fml.common.SidedProxy;
+import cpw.mods.fml.common.event.FMLInitializationEvent;
+import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
+import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
+import cpw.mods.fml.common.event.FMLPostInitializationEvent;
+import cpw.mods.fml.common.event.FMLPreInitializationEvent;
+import cpw.mods.fml.common.event.FMLServerAboutToStartEvent;
+import cpw.mods.fml.common.event.FMLServerStartedEvent;
+import cpw.mods.fml.common.event.FMLServerStartingEvent;
+import cpw.mods.fml.common.event.FMLServerStoppedEvent;
+import cpw.mods.fml.common.event.FMLServerStoppingEvent;
+import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
+import cpw.mods.fml.common.registry.GameRegistry;
+import cpw.mods.fml.relauncher.Side;
+import kubatech.api.enums.ItemList;
+import kubatech.api.helpers.ReflectionHelper;
+import kubatech.loaders.BlockLoader;
+import kubatech.network.CustomTileEntityPacket;
+import kubatech.network.LoadConfigPacket;
+
+@SuppressWarnings("unused")
+@Mod(
+ modid = Tags.MODID,
+ version = Tags.VERSION,
+ name = Tags.MODNAME,
+ acceptedMinecraftVersions = "[1.7.10]",
+ dependencies = "required-after: gregtech; " + "required-after: gtnhmixins@[2.0.1,); "
+ + "required-after: modularui; "
+ + "after: mobsinfo; "
+ + "after: EnderIO; "
+ + "after: AWWayofTime; "
+ + "after: ExtraUtilities; "
+ + "after: InfernalMobs; "
+ + "after: Thaumcraft; "
+ + "after: MineTweaker3; "
+ + "after: miscutils; "
+ + "after: harvestcraft; "
+ + "after: Forestry; "
+ + "after: DraconicEvolution; "
+ + "after: Avaritia; "
+ + "after: dreamcraft; ")
+public class kubatech {
+
+ public static kubatech instance = null;
+ public static final SimpleNetworkWrapper NETWORK = new SimpleNetworkWrapper(Tags.MODID);
+ public static final CreativeTabs KT = new CreativeTabs(Tags.MODID) {
+
+ private ItemStack iconItemStack = null;
+
+ @Override
+ public ItemStack getIconItemStack() {
+ if (iconItemStack == null) iconItemStack = LegendaryRedTea.get(1);
+ return iconItemStack;
+ }
+
+ @Override
+ public Item getTabIconItem() {
+ return null;
+ }
+
+ @Override
+ public String getTranslatedTabLabel() {
+ return Tags.MODNAME;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void displayAllReleventItems(List p_78018_1_) {
+ super.displayAllReleventItems(p_78018_1_);
+ if (ItemList.ExtremeEntityCrusher.hasBeenSet()) p_78018_1_.add(ItemList.ExtremeEntityCrusher.get(1));
+ if (ItemList.ExtremeIndustrialApiary.hasBeenSet()) p_78018_1_.add(ItemList.ExtremeIndustrialApiary.get(1));
+ if (ItemList.ExtremeIndustrialGreenhouse.hasBeenSet())
+ p_78018_1_.add(ItemList.ExtremeIndustrialGreenhouse.get(1));
+ if (ItemList.DraconicEvolutionFusionCrafter.hasBeenSet())
+ p_78018_1_.add(ItemList.DraconicEvolutionFusionCrafter.get(1));
+ }
+ };
+
+ static {
+ NETWORK.registerMessage(new LoadConfigPacket.Handler(), LoadConfigPacket.class, 0, Side.CLIENT);
+ NETWORK.registerMessage(new CustomTileEntityPacket.Handler(), CustomTileEntityPacket.class, 1, Side.CLIENT);
+ }
+
+ private static final Logger LOG = LogManager.getLogger(Tags.MODID);
+
+ @SidedProxy(clientSide = Tags.MODID + ".ClientProxy", serverSide = Tags.MODID + ".CommonProxy")
+ public static CommonProxy proxy;
+
+ public static Collection<ClassNode> myClasses;
+
+ @Mod.EventHandler
+ public void preInit(FMLPreInitializationEvent event) {
+ instance = this;
+ final long timeStart = System.currentTimeMillis();
+ try {
+ myClasses = ReflectionHelper.getClasses(
+ this.getClass()
+ .getPackage()
+ .getName());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ final long timeToLoad = System.currentTimeMillis() - timeStart;
+ info("Class discovery took " + timeToLoad + "ms ! Found " + myClasses.size() + " classes.");
+ proxy.preInit(event);
+ }
+
+ @Mod.EventHandler
+ public void init(FMLInitializationEvent event) {
+ proxy.init(event);
+ }
+
+ @Mod.EventHandler
+ public void postInit(FMLPostInitializationEvent event) {
+ proxy.postInit(event);
+ }
+
+ @Mod.EventHandler
+ public void serverAboutToStart(FMLServerAboutToStartEvent event) {
+ proxy.serverAboutToStart(event);
+ }
+
+ @Mod.EventHandler
+ public void serverStarting(FMLServerStartingEvent event) {
+ proxy.serverStarting(event);
+ }
+
+ @Mod.EventHandler
+ public void serverStarted(FMLServerStartedEvent event) {
+ proxy.serverStarted(event);
+ }
+
+ @Mod.EventHandler
+ public void serverStopping(FMLServerStoppingEvent event) {
+ proxy.serverStopping(event);
+ }
+
+ @Mod.EventHandler
+ public void serverStopped(FMLServerStoppedEvent event) {
+ proxy.serverStopped(event);
+ }
+
+ @Mod.EventHandler
+ public void loadComplete(FMLLoadCompleteEvent event) {
+ proxy.loadComplete(event);
+ }
+
+ @Mod.EventHandler
+ public void missingMappings(FMLMissingMappingsEvent event) {
+ for (FMLMissingMappingsEvent.MissingMapping missingMapping : event.getAll()) {
+ if (missingMapping.name.equals("EMT:EMT_GTBLOCK_CASEING")) {
+ if (missingMapping.type == GameRegistry.Type.BLOCK) missingMapping.remap(BlockLoader.defcCasingBlock);
+ else missingMapping.remap(Item.getItemFromBlock(BlockLoader.defcCasingBlock));
+ }
+ }
+ }
+
+ public static void debug(String message) {
+ LOG.debug(message);
+ }
+
+ public static void info(String message) {
+ LOG.info(message);
+ }
+
+ public static void warn(String message) {
+ LOG.warn(message);
+ }
+
+ public static void error(String message) {
+ LOG.error(message);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/BlockLoader.java b/src/main/java/kubatech/loaders/BlockLoader.java
new file mode 100644
index 0000000000..fcb077ff15
--- /dev/null
+++ b/src/main/java/kubatech/loaders/BlockLoader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.loaders;
+
+import net.minecraft.block.material.Material;
+import net.minecraft.item.ItemBlock;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import kubatech.api.enums.ItemList;
+import kubatech.loaders.block.defc.DEFCCasingBlock;
+import kubatech.loaders.block.kubablock.KubaBlock;
+import kubatech.loaders.block.kubablock.KubaItemBlock;
+import kubatech.loaders.block.kubablock.blocks.TeaAcceptor;
+import kubatech.loaders.block.kubablock.blocks.TeaStorage;
+import kubatech.tileentity.TeaAcceptorTile;
+import kubatech.tileentity.TeaStorageTile;
+
+public class BlockLoader {
+
+ public static final KubaBlock kubaBlock = new KubaBlock(Material.anvil);
+ public static final ItemBlock kubaItemBlock = new KubaItemBlock(kubaBlock);
+ public static final DEFCCasingBlock defcCasingBlock = new DEFCCasingBlock();
+
+ public static void registerBlocks() {
+ GameRegistry.registerTileEntity(TeaAcceptorTile.class, "KT_TeaAcceptor");
+ GameRegistry.registerTileEntity(TeaStorageTile.class, "KT_TeaStorage");
+ GameRegistry.registerBlock(kubaBlock, null, "kubablocks");
+ GameRegistry.registerItem(kubaItemBlock, "kubablocks");
+
+ ItemList.TeaAcceptor.set(kubaBlock.registerProxyBlock(new TeaAcceptor()));
+ ItemList.TeaStorage.set(kubaBlock.registerProxyBlock(new TeaStorage()));
+ }
+}
diff --git a/src/main/java/kubatech/loaders/DEFCRecipes.java b/src/main/java/kubatech/loaders/DEFCRecipes.java
new file mode 100644
index 0000000000..ebea8d751f
--- /dev/null
+++ b/src/main/java/kubatech/loaders/DEFCRecipes.java
@@ -0,0 +1,331 @@
+package kubatech.loaders;
+
+import static gregtech.api.enums.Mods.AdvancedSolarPanel;
+import static gregtech.api.enums.Mods.DraconicEvolution;
+import static gregtech.api.enums.Mods.ElectroMagicTools;
+import static gregtech.api.enums.Mods.NewHorizonsCoreMod;
+import static gregtech.api.enums.Mods.SuperSolarPanels;
+import static gregtech.api.enums.Mods.Witchery;
+import static gregtech.api.recipe.RecipeMaps.assemblerRecipes;
+import static gregtech.api.recipe.RecipeMaps.mixerRecipes;
+import static gregtech.api.util.GT_RecipeConstants.DEFC_CASING_TIER;
+
+import java.util.Arrays;
+
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+
+import cpw.mods.fml.common.Loader;
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.MaterialsUEVplus;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.TierEU;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.RecipeMapBackend;
+import gregtech.api.recipe.RecipeMapBuilder;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.api.util.GT_OreDictUnificator;
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.GT_Utility;
+import gregtech.nei.formatter.SimpleSpecialValueFormatter;
+import gtPlusPlus.xmod.forestry.bees.handler.GTPP_CombType;
+import kubatech.Tags;
+
+public class DEFCRecipes {
+
+ public static final RecipeMap<RecipeMapBackend> fusionCraftingRecipes = RecipeMapBuilder
+ .of("kubatech.defusioncrafter")
+ .maxIO(9, 1, 1, 1)
+ .minInputs(1, 0)
+ .neiSpecialInfoFormatter(new SimpleSpecialValueFormatter("kubatech.defusioncrafter.tier"))
+ .slotOverlays(
+ (index, isFluid, isOutput,
+ isSpecial) -> !isFluid && !isOutput ? UITexture.fullImage(Tags.MODID, "gui/slot/fusion_crafter") : null)
+ .build();
+
+ public static void addRecipes() {
+
+ // Dragonblood recipe for magics haters
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ new ItemStack(Blocks.dragon_egg, 1),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64L),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64L))
+ .fluidInputs(Materials.Radon.getPlasma(144))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.dust, Materials.Ash, 8L))
+ .fluidOutputs(new FluidStack(FluidRegistry.getFluid("molten.dragonblood"), 288))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(14_000)
+ .addTo(mixerRecipes);
+
+ // Casings
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.frameGt, Materials.NaquadahAlloy, 6L),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.NaquadahAlloy, 6L))
+ .fluidInputs(Materials.Void.getMolten(1152L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingBase.get(1))
+ .eut(TierEU.RECIPE_UV)
+ .duration(24000)
+ .addTo(assemblerRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_ModHandler.getModItem(NewHorizonsCoreMod.ID, "tile.BloodyIchorium", 1, 0),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.Osmiridium, 6L))
+ .fluidInputs(Materials.Void.getMolten(1152L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingT1.get(1))
+ .eut(TierEU.RECIPE_UV)
+ .duration(24000)
+ .addTo(assemblerRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ kubatech.api.enums.ItemList.DEFCCasingT1.get(1),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.Draconium, 6L),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "draconicCore", 1, 0))
+ .fluidInputs(Materials.Void.getMolten(2304L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingT2.get(1))
+ .eut(TierEU.RECIPE_UV)
+ .duration(24000)
+ .addTo(assemblerRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ kubatech.api.enums.ItemList.DEFCCasingT2.get(1),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.CosmicNeutronium, 6L),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "wyvernCore", 2, 0))
+ .fluidInputs(Materials.Void.getMolten(4608L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingT3.get(1))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(12000)
+ .addTo(assemblerRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ kubatech.api.enums.ItemList.DEFCCasingT3.get(1),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.DraconiumAwakened, 6L),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "awakenedCore", 3, 0))
+ .fluidInputs(Materials.Void.getMolten(9216L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingT4.get(1))
+ .eut(TierEU.RECIPE_UEV)
+ .duration(12000)
+ .addTo(assemblerRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ kubatech.api.enums.ItemList.DEFCCasingT4.get(1),
+ GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.Infinity, 6L),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "chaoticCore", 4, 0))
+ .fluidInputs(Materials.Void.getMolten(18432L))
+ .itemOutputs(kubatech.api.enums.ItemList.DEFCCasingT5.get(1))
+ .eut(TierEU.RECIPE_UIV)
+ .duration(12000)
+ .addTo(assemblerRecipes);
+
+ fusionRecipes();
+ conversionRecipes();
+ }
+
+ private static final Item EMTItems = GameRegistry.findItem("EMT", "EMTItems");
+
+ private static void addOldHiddenRecipe(GT_Recipe recipe) {
+ if (!ElectroMagicTools.isModLoaded()) return;
+ recipe = recipe.copy();
+ recipe.mInputs = Arrays.stream(recipe.mInputs)
+ .map(i -> {
+ if (i != null && i.getItem() == ItemLoader.kubaitems) {
+ return new ItemStack(
+ EMTItems,
+ 0,
+ 16 + (i.getItemDamage() - kubatech.api.enums.ItemList.DEFCDraconicSchematic.get(1)
+ .getItemDamage()));
+ } else return i;
+ })
+ .toArray(ItemStack[]::new);
+ recipe.mHidden = true;
+ fusionCraftingRecipes.add(recipe);
+ }
+
+ private static void conversionRecipes() {
+ if (!ElectroMagicTools.isModLoaded()) return;
+ GameRegistry.addShapelessRecipe(
+ kubatech.api.enums.ItemList.DEFCDraconicSchematic.get(1),
+ new ItemStack(EMTItems, 1, 16));
+ GameRegistry
+ .addShapelessRecipe(kubatech.api.enums.ItemList.DEFCWyvernSchematic.get(1), new ItemStack(EMTItems, 1, 17));
+ GameRegistry.addShapelessRecipe(
+ kubatech.api.enums.ItemList.DEFCAwakenedSchematic.get(1),
+ new ItemStack(EMTItems, 1, 18));
+ GameRegistry.addShapelessRecipe(
+ kubatech.api.enums.ItemList.DEFCChaoticSchematic.get(1),
+ new ItemStack(EMTItems, 1, 19));
+ }
+
+ private static void fusionRecipes() {
+ // CORES
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Osmiridium, 4),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Ichorium, 1),
+ ItemList.QuantumEye.get(1L),
+ kubatech.api.enums.ItemList.DEFCDraconicSchematic.get(0L))
+ .fluidInputs(Materials.Sunnarium.getMolten(1440))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "draconicCore", 1, 0))
+ .eut(TierEU.RECIPE_UV)
+ .duration(400)
+ .metadata(DEFC_CASING_TIER, 1)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Draconium, 8),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Neutronium, 4),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "draconicCore", 4, 0),
+ ItemList.QuantumStar.get(1L),
+ kubatech.api.enums.ItemList.DEFCWyvernSchematic.get(0L))
+ .fluidInputs(Materials.Neutronium.getMolten(1440))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "wyvernCore", 1, 0))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(800)
+ .metadata(DEFC_CASING_TIER, 2)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+
+ if (Loader.isModLoaded("supersolarpanel")) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.DraconiumAwakened, 12),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Draconium, 4),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "wyvernCore", 4, 0),
+ GT_ModHandler.getModItem(SuperSolarPanels.ID, "enderquantumcomponent", 1, 0),
+ kubatech.api.enums.ItemList.DEFCAwakenedSchematic.get(0L))
+ .fluidInputs(Materials.Infinity.getMolten(1440))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "awakenedCore", 1, 0))
+ .eut(TierEU.RECIPE_UEV)
+ .duration(1600)
+ .metadata(DEFC_CASING_TIER, 3)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+ } else {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.DraconiumAwakened, 12),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Draconium, 4),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "wyvernCore", 4, 0),
+ GT_ModHandler.getModItem(NewHorizonsCoreMod.ID, "item.ManyullynCrystal", 1, 0),
+ kubatech.api.enums.ItemList.DEFCAwakenedSchematic.get(0L))
+ .fluidInputs(Materials.Infinity.getMolten(1440))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "awakenedCore", 1, 0))
+ .eut(TierEU.RECIPE_UEV)
+ .duration(1600)
+ .metadata(DEFC_CASING_TIER, 3)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+ }
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.DraconiumAwakened, 16),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.BlackPlutonium, 4),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "awakenedCore", 4, 0),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "chaosFragment", 2, 2),
+ kubatech.api.enums.ItemList.DEFCChaoticSchematic.get(0L))
+ .fluidInputs(MaterialsUEVplus.SpaceTime.getMolten(1440))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "chaoticCore", 1, 0))
+ .eut(24_000_000)
+ .duration(3200)
+ .metadata(DEFC_CASING_TIER, 4)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+
+ // ENERGY CORES
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Draconium, 8),
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.StellarAlloy, 4),
+ GT_ModHandler.getModItem(AdvancedSolarPanel.ID, "asp_crafting_items", 4, 1),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "draconicCore", 1, 0),
+ kubatech.api.enums.ItemList.DEFCWyvernSchematic.get(0L))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "draconiumEnergyCore", 1, 0))
+ .eut(TierEU.RECIPE_UV)
+ .duration(1000)
+ .metadata(DEFC_CASING_TIER, 2)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(OrePrefixes.plate, Materials.DraconiumAwakened, 8),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "draconiumEnergyCore", 4, 0),
+ GT_ModHandler.getModItem(AdvancedSolarPanel.ID, "asp_crafting_items", 4, 4),
+ GT_ModHandler.getModItem(DraconicEvolution.ID, "wyvernCore", 1, 0),
+ kubatech.api.enums.ItemList.DEFCAwakenedSchematic.get(0L))
+ .itemOutputs(GT_ModHandler.getModItem(DraconicEvolution.ID, "draconiumEnergyCore", 1, 1))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(2000)
+ .metadata(DEFC_CASING_TIER, 3)
+ .addTo(fusionCraftingRecipes)
+ .forEach(DEFCRecipes::addOldHiddenRecipe);
+
+ // Dragon Blood
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ new ItemStack(Blocks.dragon_egg, 0),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64),
+ GT_Utility.getIntegratedCircuit(1))
+ .fluidInputs(Materials.Radon.getPlasma(144))
+ .fluidOutputs(new FluidStack(FluidRegistry.getFluid("molten.dragonblood"), 288))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(4200)
+ .metadata(DEFC_CASING_TIER, 3)
+ .noOptimize()
+ .addTo(fusionCraftingRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_ModHandler.getModItem(Witchery.ID, "infinityegg", 0),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64),
+ GT_Utility.getIntegratedCircuit(1))
+ .fluidInputs(Materials.Radon.getPlasma(72))
+ .fluidOutputs(new FluidStack(FluidRegistry.getFluid("molten.dragonblood"), 432))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(3600)
+ .metadata(DEFC_CASING_TIER, 3)
+ .noOptimize()
+ .addTo(fusionCraftingRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ new ItemStack(Blocks.dragon_egg, 0),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64),
+ GTPP_CombType.DRAGONBLOOD.getStackForType(1))
+ .fluidInputs(Materials.Radon.getPlasma(216))
+ .fluidOutputs(new FluidStack(FluidRegistry.getFluid("molten.dragonblood"), 432))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(2800)
+ .metadata(DEFC_CASING_TIER, 3)
+ .noOptimize()
+ .addTo(fusionCraftingRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_ModHandler.getModItem(Witchery.ID, "infinityegg", 0),
+ GT_OreDictUnificator.get(OrePrefixes.dust, Materials.DraconiumAwakened, 64),
+ GTPP_CombType.DRAGONBLOOD.getStackForType(1))
+ .fluidInputs(Materials.Radon.getPlasma(108))
+ .fluidOutputs(new FluidStack(FluidRegistry.getFluid("molten.dragonblood"), 648))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(2400)
+ .metadata(DEFC_CASING_TIER, 3)
+ .noOptimize()
+ .addTo(fusionCraftingRecipes);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/EIGBucketLoader.java b/src/main/java/kubatech/loaders/EIGBucketLoader.java
new file mode 100644
index 0000000000..4dbf83112b
--- /dev/null
+++ b/src/main/java/kubatech/loaders/EIGBucketLoader.java
@@ -0,0 +1,27 @@
+package kubatech.loaders;
+
+import static gregtech.api.enums.Mods.ThaumicBases;
+
+import kubatech.api.enums.EIGModes;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGFlowerBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGRainbowCactusBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGSeedBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGStemBucket;
+
+public class EIGBucketLoader {
+
+ public static void LoadEIGBuckets() {
+ // IC2 buckets
+ EIGModes.IC2.addLowPriorityFactory(EIGIC2Bucket.factory);
+
+ // Regular Mode Buckets
+ if (ThaumicBases.isModLoaded()) {
+ EIGModes.Normal.addLowPriorityFactory(EIGRainbowCactusBucket.factory);
+ }
+ EIGModes.Normal.addLowPriorityFactory(EIGFlowerBucket.factory);
+ EIGModes.Normal.addLowPriorityFactory(EIGStemBucket.factory);
+ EIGModes.Normal.addLowPriorityFactory(EIGSeedBucket.factory);
+ }
+
+}
diff --git a/src/main/java/kubatech/loaders/ItemLoader.java b/src/main/java/kubatech/loaders/ItemLoader.java
new file mode 100644
index 0000000000..d8218ea873
--- /dev/null
+++ b/src/main/java/kubatech/loaders/ItemLoader.java
@@ -0,0 +1,149 @@
+/*
+ * 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.loaders;
+
+import static kubatech.api.enums.ItemList.Beeeeee;
+import static kubatech.api.enums.ItemList.BlackTea;
+import static kubatech.api.enums.ItemList.BlackTeaLeaf;
+import static kubatech.api.enums.ItemList.BruisedTeaLeaf;
+import static kubatech.api.enums.ItemList.DEFCAwakenedSchematic;
+import static kubatech.api.enums.ItemList.DEFCChaoticSchematic;
+import static kubatech.api.enums.ItemList.DEFCDraconicSchematic;
+import static kubatech.api.enums.ItemList.DEFCWyvernSchematic;
+import static kubatech.api.enums.ItemList.EarlGrayTea;
+import static kubatech.api.enums.ItemList.FermentedTeaLeaf;
+import static kubatech.api.enums.ItemList.GreenTea;
+import static kubatech.api.enums.ItemList.GreenTeaLeaf;
+import static kubatech.api.enums.ItemList.LegendaryBlackTea;
+import static kubatech.api.enums.ItemList.LegendaryButterflyTea;
+import static kubatech.api.enums.ItemList.LegendaryEarlGrayTea;
+import static kubatech.api.enums.ItemList.LegendaryGreenTea;
+import static kubatech.api.enums.ItemList.LegendaryLemonTea;
+import static kubatech.api.enums.ItemList.LegendaryMilkTea;
+import static kubatech.api.enums.ItemList.LegendaryOolongTea;
+import static kubatech.api.enums.ItemList.LegendaryPeppermintTea;
+import static kubatech.api.enums.ItemList.LegendaryPuerhTea;
+import static kubatech.api.enums.ItemList.LegendaryRedTea;
+import static kubatech.api.enums.ItemList.LegendaryUltimateTea;
+import static kubatech.api.enums.ItemList.LegendaryWhiteTea;
+import static kubatech.api.enums.ItemList.LegendaryYellowTea;
+import static kubatech.api.enums.ItemList.LemonTea;
+import static kubatech.api.enums.ItemList.MilkTea;
+import static kubatech.api.enums.ItemList.OolongTea;
+import static kubatech.api.enums.ItemList.OolongTeaLeaf;
+import static kubatech.api.enums.ItemList.OxidizedTeaLeaf;
+import static kubatech.api.enums.ItemList.PartiallyOxidizedTeaLeaf;
+import static kubatech.api.enums.ItemList.PeppermintTea;
+import static kubatech.api.enums.ItemList.PuerhTea;
+import static kubatech.api.enums.ItemList.PuerhTeaLeaf;
+import static kubatech.api.enums.ItemList.RolledTeaLeaf;
+import static kubatech.api.enums.ItemList.SteamedTeaLeaf;
+import static kubatech.api.enums.ItemList.TeaAcceptorResearchNote;
+import static kubatech.api.enums.ItemList.TeaLeafDehydrated;
+import static kubatech.api.enums.ItemList.WhiteTea;
+import static kubatech.api.enums.ItemList.WhiteTeaLeaf;
+import static kubatech.api.enums.ItemList.YellowTea;
+import static kubatech.api.enums.ItemList.YellowTeaLeaf;
+
+import java.util.List;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import kubatech.loaders.item.ItemProxy;
+import kubatech.loaders.item.KubaItems;
+import kubatech.loaders.item.items.Tea;
+import kubatech.loaders.item.items.TeaCollection;
+import kubatech.loaders.item.items.TeaIngredient;
+import kubatech.loaders.item.items.TeaUltimate;
+
+public class ItemLoader {
+
+ public static final KubaItems kubaitems = new KubaItems();
+
+ public static void registerItems() {
+ GameRegistry.registerItem(kubaitems, "kubaitems");
+
+ // DON'T EVER CHANGE ORDER IN HERE, ADD NEW ITEMS ON BOTTOM
+
+ LegendaryBlackTea.set(kubaitems.registerProxyItem(new TeaCollection("black_tea")));
+ LegendaryButterflyTea.set(kubaitems.registerProxyItem(new TeaCollection("butterfly_tea")));
+ LegendaryEarlGrayTea.set(kubaitems.registerProxyItem(new TeaCollection("earl_gray_tea")));
+ LegendaryGreenTea.set(kubaitems.registerProxyItem(new TeaCollection("green_tea")));
+ LegendaryLemonTea.set(kubaitems.registerProxyItem(new TeaCollection("lemon_tea")));
+ LegendaryMilkTea.set(kubaitems.registerProxyItem(new TeaCollection("milk_tea")));
+ LegendaryOolongTea.set(kubaitems.registerProxyItem(new TeaCollection("oolong_tea")));
+ LegendaryPeppermintTea.set(kubaitems.registerProxyItem(new TeaCollection("peppermint_tea")));
+ LegendaryPuerhTea.set(kubaitems.registerProxyItem(new TeaCollection("pu-erh_tea")));
+ LegendaryRedTea.set(kubaitems.registerProxyItem(new TeaCollection("red_tea")));
+ LegendaryWhiteTea.set(kubaitems.registerProxyItem(new TeaCollection("white_tea")));
+ LegendaryYellowTea.set(kubaitems.registerProxyItem(new TeaCollection("yellow_tea")));
+ LegendaryUltimateTea.set(kubaitems.registerProxyItem(new TeaUltimate()));
+
+ BlackTea.set(kubaitems.registerProxyItem(new Tea("black_tea", 4, 0.3f)));
+ EarlGrayTea.set(kubaitems.registerProxyItem(new Tea("earl_gray_tea", 4, 0.3f)));
+ GreenTea.set(kubaitems.registerProxyItem(new Tea("green_tea", 4, 0.3f)));
+ LemonTea.set(kubaitems.registerProxyItem(new Tea("lemon_tea", 4, 0.3f)));
+ MilkTea.set(kubaitems.registerProxyItem(new Tea("milk_tea", 4, 0.3f)));
+ OolongTea.set(kubaitems.registerProxyItem(new Tea("oolong_tea", 4, 0.3f)));
+ PeppermintTea.set(kubaitems.registerProxyItem(new Tea("peppermint_tea", 4, 0.3f)));
+ PuerhTea.set(kubaitems.registerProxyItem(new Tea("pu-erh_tea", 4, 0.3f)));
+ WhiteTea.set(kubaitems.registerProxyItem(new Tea("white_tea", 4, 0.3f)));
+ YellowTea.set(kubaitems.registerProxyItem(new Tea("yellow_tea", 4, 0.3f)));
+
+ BlackTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("black_tea_leaf")));
+ GreenTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("green_tea_leaf")));
+ OolongTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("oolong_tea_leaf")));
+ PuerhTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("pu-erh_tea_leaf")));
+ WhiteTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("white_tea_leaf")));
+ YellowTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("yellow_tea_leaf")));
+
+ TeaLeafDehydrated.set(kubaitems.registerProxyItem(new TeaIngredient("tea_leaf_dehydrated")));
+ SteamedTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("steamed_tea_leaf")));
+ RolledTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("rolled_tea_leaf")));
+ OxidizedTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("oxidized_tea_leaf")));
+ FermentedTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("fermented_tea_leaf")));
+ BruisedTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("bruised_tea_leaf")));
+ PartiallyOxidizedTeaLeaf.set(kubaitems.registerProxyItem(new TeaIngredient("partially_oxidized_tea_leaf")));
+
+ TeaAcceptorResearchNote
+ .set(kubaitems.registerProxyItem(new ItemProxy("tea_acceptor_research_note", "research_note")));
+ Beeeeee.set(kubaitems.registerProxyItem(new ItemProxy("beeeeee")));
+
+ // DEFC stuff
+ DEFCDraconicSchematic.set(kubaitems.registerProxyItem(new ItemProxy("defc_schematic_t1") {
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList,
+ boolean showDebugInfo) {
+ tooltipList
+ .add(EnumChatFormatting.GOLD + StatCollector.translateToLocal("kubaitem.defc_schematic_t1.tip"));
+ }
+ }));
+ DEFCWyvernSchematic.set(kubaitems.registerProxyItem(new ItemProxy("defc_schematic_t2")));
+ DEFCAwakenedSchematic.set(kubaitems.registerProxyItem(new ItemProxy("defc_schematic_t3")));
+ DEFCChaoticSchematic.set(kubaitems.registerProxyItem(new ItemProxy("defc_schematic_t4")));
+
+ }
+}
diff --git a/src/main/java/kubatech/loaders/MTLoader.java b/src/main/java/kubatech/loaders/MTLoader.java
new file mode 100644
index 0000000000..dbc5bb43fe
--- /dev/null
+++ b/src/main/java/kubatech/loaders/MTLoader.java
@@ -0,0 +1,44 @@
+/*
+ * 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.loaders;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import kubatech.Tags;
+import minetweaker.MineTweakerImplementationAPI;
+
+public class MTLoader {
+
+ private static final Logger LOG = LogManager.getLogger(Tags.MODID + "[MT Loader]");
+ public static MTLoader instance = null;
+
+ public static void init() {
+ if (instance == null) {
+ instance = new MTLoader();
+ MineTweakerImplementationAPI.onPostReload(instance::MTOnPostReload);
+ }
+ }
+
+ public void MTOnPostReload(MineTweakerImplementationAPI.ReloadEvent reloadEvent) {
+ LOG.info("MT Recipes Loaded!");
+ }
+}
diff --git a/src/main/java/kubatech/loaders/MobHandlerLoader.java b/src/main/java/kubatech/loaders/MobHandlerLoader.java
new file mode 100644
index 0000000000..c7932dd00c
--- /dev/null
+++ b/src/main/java/kubatech/loaders/MobHandlerLoader.java
@@ -0,0 +1,242 @@
+/*
+ * 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.loaders;
+
+import static gregtech.api.enums.Mods.InfernalMobs;
+import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeEntityCrusher.DIAMOND_SPIKES_DAMAGE;
+import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeEntityCrusher.MOB_SPAWN_INTERVAL;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import net.minecraft.enchantment.EnchantmentHelper;
+import net.minecraft.entity.EntityLiving;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.common.MinecraftForge;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.kuba6000.mobsinfo.api.IChanceModifier;
+import com.kuba6000.mobsinfo.api.MobDrop;
+import com.kuba6000.mobsinfo.api.MobRecipe;
+import com.kuba6000.mobsinfo.api.event.MobNEIRegistrationEvent;
+import com.kuba6000.mobsinfo.api.event.PostMobRegistrationEvent;
+import com.kuba6000.mobsinfo.api.event.PreMobsRegistrationEvent;
+
+import atomicstryker.infernalmobs.common.InfernalMobsCore;
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.util.GT_Utility;
+import kubatech.Tags;
+import kubatech.config.Config;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeEntityCrusher;
+
+public class MobHandlerLoader {
+
+ private static final Logger LOG = LogManager.getLogger(Tags.MODID + "[Mob Handler Loader]");
+
+ private static MobHandlerLoader instance = null;
+
+ public static void init() {
+ instance = new MobHandlerLoader();
+ MinecraftForge.EVENT_BUS.register(instance);
+ }
+
+ public static Map<String, MobEECRecipe> recipeMap = new HashMap<>();
+
+ public static class MobEECRecipe {
+
+ public final List<MobDrop> mOutputs;
+
+ public final MobRecipe recipe;
+
+ public final int mEUt = 2000;
+ public final int mDuration;
+ public final EntityLiving entityCopy;
+
+ public MobEECRecipe(List<MobDrop> transformedDrops, MobRecipe recipe) {
+ this.mOutputs = transformedDrops;
+ this.recipe = recipe;
+ try {
+ this.entityCopy = this.recipe.createEntityCopy();
+ } catch (NoSuchMethodException | InvocationTargetException | InstantiationException
+ | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ mDuration = Math.max(MOB_SPAWN_INTERVAL, (int) ((recipe.maxEntityHealth / DIAMOND_SPIKES_DAMAGE) * 10d));
+ }
+
+ public ItemStack[] generateOutputs(Random rnd, GT_MetaTileEntity_ExtremeEntityCrusher MTE, double attackDamage,
+ int lootinglevel, boolean preferInfernalDrops, boolean voidAllDamagedAndEnchantedItems) {
+ MTE.lEUt = mEUt;
+ MTE.mMaxProgresstime = Math.max(MOB_SPAWN_INTERVAL, (int) ((recipe.maxEntityHealth / attackDamage) * 10d));
+ ArrayList<ItemStack> stacks = new ArrayList<>(this.mOutputs.size());
+ this.entityCopy.setPosition(
+ MTE.getBaseMetaTileEntity()
+ .getXCoord(),
+ MTE.getBaseMetaTileEntity()
+ .getYCoord(),
+ MTE.getBaseMetaTileEntity()
+ .getZCoord());
+ for (MobDrop o : this.mOutputs) {
+ if (voidAllDamagedAndEnchantedItems && (o.damages != null || o.enchantable != null)) continue;
+ int chance = o.chance;
+
+ double dChance = (double) chance / 100d;
+ for (IChanceModifier chanceModifier : o.chanceModifiers) {
+ dChance = chanceModifier.apply(
+ dChance,
+ MTE.getBaseMetaTileEntity()
+ .getWorld(),
+ stacks,
+ MTE.EECPlayer,
+ this.entityCopy);
+ }
+
+ chance = (int) (dChance * 100d);
+ if (chance == 0) continue;
+
+ if (o.playerOnly) {
+ chance = (int) ((double) chance * Config.MobHandler.playerOnlyDropsModifier);
+ if (chance < 1) chance = 1;
+ }
+ int amount = o.stack.stackSize;
+ if (o.lootable && lootinglevel > 0) {
+ chance += lootinglevel * 5000;
+ if (chance > 10000) {
+ int div = (int) Math.ceil(chance / 10000d);
+ amount *= div;
+ chance /= div;
+ }
+ }
+ if (chance == 10000 || rnd.nextInt(10000) < chance) {
+ ItemStack s = o.stack.copy();
+ s.stackSize = amount;
+ if (o.enchantable != null) EnchantmentHelper.addRandomEnchantment(rnd, s, o.enchantable);
+ if (o.damages != null) {
+ int rChance = rnd.nextInt(recipe.mMaxDamageChance);
+ int cChance = 0;
+ for (Map.Entry<Integer, Integer> damage : o.damages.entrySet()) {
+ cChance += damage.getValue();
+ if (rChance <= cChance) {
+ s.setItemDamage(damage.getKey());
+ break;
+ }
+ }
+ }
+ stacks.add(s);
+ }
+ }
+
+ if (InfernalMobs.isModLoaded()) {
+ InfernalMobsCore infernalMobsCore = InfernalMobsCore.instance();
+ if (recipe.infernalityAllowed && mEUt * 8 <= MTE.getMaxInputEu()
+ && !infernalMobsCore.getDimensionBlackList()
+ .contains(
+ MTE.getBaseMetaTileEntity()
+ .getWorld().provider.dimensionId)) {
+ int p = 0;
+ int mods = 0;
+ if (recipe.alwaysinfernal
+ || (preferInfernalDrops && rnd.nextInt(infernalMobsCore.getEliteRarity()) == 0)) {
+ p = 1;
+ if (rnd.nextInt(infernalMobsCore.getUltraRarity()) == 0) {
+ p = 2;
+ if (rnd.nextInt(infernalMobsCore.getInfernoRarity()) == 0) p = 3;
+ }
+ }
+ ArrayList<ItemStack> infernalstacks = null;
+ if (p > 0) if (p == 1) {
+ infernalstacks = infernalMobsCore.getDropIdListElite();
+ mods = infernalMobsCore.getMinEliteModifiers();
+ } else if (p == 2) {
+ infernalstacks = infernalMobsCore.getDropIdListUltra();
+ mods = infernalMobsCore.getMinUltraModifiers();
+ } else {
+ infernalstacks = infernalMobsCore.getDropIdListInfernal();
+ mods = infernalMobsCore.getMinInfernoModifiers();
+ }
+ if (infernalstacks != null) {
+ ItemStack infernalstack = infernalstacks.get(rnd.nextInt(infernalstacks.size()))
+ .copy();
+ // noinspection ConstantConditions
+ EnchantmentHelper.addRandomEnchantment(
+ rnd,
+ infernalstack,
+ infernalstack.getItem()
+ .getItemEnchantability());
+ stacks.add(infernalstack);
+ MTE.lEUt *= 8L;
+ MTE.mMaxProgresstime *= mods * InfernalMobsCore.instance()
+ .getMobModHealthFactor();
+ }
+ }
+ }
+
+ return stacks.toArray(new ItemStack[0]);
+ }
+
+ }
+
+ @SubscribeEvent
+ @SuppressWarnings("unused")
+ public void onPreMobsRegistration(PreMobsRegistrationEvent event) {
+ recipeMap.clear();
+ }
+
+ @SubscribeEvent
+ @SuppressWarnings("unused")
+ public void onPostMobRegistration(PostMobRegistrationEvent event) {
+ if (!event.drops.isEmpty() && event.recipe.isUsableInVial) {
+ for (MobDrop drop : event.drops) {
+ if (drop.playerOnly) {
+ drop.additionalInfo.add(
+ StatCollector.translateToLocalFormatted(
+ "kubatech.mobhandler.eec_chance",
+ (((double) drop.chance / 100d) * Config.MobHandler.playerOnlyDropsModifier)));
+ }
+ }
+ @SuppressWarnings("unchecked")
+ ArrayList<MobDrop> drops = (ArrayList<MobDrop>) event.drops.clone();
+ if (!drops.isEmpty()) {
+ recipeMap.put(event.currentMob, new MobEECRecipe(drops, event.recipe));
+ }
+ }
+ }
+
+ @SubscribeEvent
+ @SuppressWarnings("unused")
+ public void onMobNEIRegistration(MobNEIRegistrationEvent event) {
+ MobEECRecipe recipe = recipeMap.get(event.mobName);
+ if (recipe != null) {
+ event.additionalInformation.addAll(
+ Arrays.asList(
+ GT_Utility.trans("153", "Usage: ") + GT_Utility.formatNumbers(recipe.mEUt) + " EU/t",
+ GT_Utility.trans("158", "Time: ") + GT_Utility.formatNumbers(recipe.mDuration / 20d) + " secs"));
+ }
+ }
+}
diff --git a/src/main/java/kubatech/loaders/RecipeLoader.java b/src/main/java/kubatech/loaders/RecipeLoader.java
new file mode 100644
index 0000000000..995583aad4
--- /dev/null
+++ b/src/main/java/kubatech/loaders/RecipeLoader.java
@@ -0,0 +1,355 @@
+/*
+ * 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.loaders;
+
+import static gregtech.api.enums.ItemList.FluidExtractorUHV;
+import static gregtech.api.enums.MetaTileEntityIDs.DraconicEvolutionFusionCrafterController;
+import static gregtech.api.enums.MetaTileEntityIDs.ExtremeEntityCrusherController;
+import static gregtech.api.enums.MetaTileEntityIDs.ExtremeIndustrialApiaryController;
+import static gregtech.api.enums.MetaTileEntityIDs.ExtremeIndustrialGreenhouseController;
+import static gregtech.api.enums.Mods.Avaritia;
+import static gregtech.api.enums.Mods.DraconicEvolution;
+import static gregtech.api.enums.Mods.EnderIO;
+import static gregtech.api.enums.Mods.Forestry;
+import static gregtech.api.enums.Mods.NewHorizonsCoreMod;
+import static gregtech.api.enums.Mods.OpenBlocks;
+import static gregtech.api.enums.Mods.PamsHarvestCraft;
+import static gregtech.api.recipe.RecipeMaps.benderRecipes;
+import static gregtech.api.recipe.RecipeMaps.cutterRecipes;
+import static gregtech.api.recipe.RecipeMaps.mixerRecipes;
+import static gregtech.api.util.GT_RecipeBuilder.MINUTES;
+import static gregtech.api.util.GT_RecipeBuilder.SECONDS;
+import static gregtech.api.util.GT_RecipeBuilder.TICKS;
+import static gregtech.api.util.GT_RecipeConstants.AssemblyLine;
+import static gregtech.api.util.GT_RecipeConstants.RESEARCH_ITEM;
+import static gregtech.api.util.GT_RecipeConstants.RESEARCH_TIME;
+import static gregtech.api.util.GT_RecipeConstants.UniversalChemical;
+import static gtPlusPlus.api.recipe.GTPPRecipeMaps.chemicalDehydratorRecipes;
+import static kubatech.api.enums.ItemList.BlackTea;
+import static kubatech.api.enums.ItemList.BlackTeaLeaf;
+import static kubatech.api.enums.ItemList.BruisedTeaLeaf;
+import static kubatech.api.enums.ItemList.DraconicEvolutionFusionCrafter;
+import static kubatech.api.enums.ItemList.EarlGrayTea;
+import static kubatech.api.enums.ItemList.ExtremeEntityCrusher;
+import static kubatech.api.enums.ItemList.ExtremeIndustrialApiary;
+import static kubatech.api.enums.ItemList.ExtremeIndustrialGreenhouse;
+import static kubatech.api.enums.ItemList.FermentedTeaLeaf;
+import static kubatech.api.enums.ItemList.GreenTea;
+import static kubatech.api.enums.ItemList.GreenTeaLeaf;
+import static kubatech.api.enums.ItemList.LegendaryUltimateTea;
+import static kubatech.api.enums.ItemList.LemonTea;
+import static kubatech.api.enums.ItemList.MilkTea;
+import static kubatech.api.enums.ItemList.OolongTea;
+import static kubatech.api.enums.ItemList.OolongTeaLeaf;
+import static kubatech.api.enums.ItemList.OxidizedTeaLeaf;
+import static kubatech.api.enums.ItemList.PartiallyOxidizedTeaLeaf;
+import static kubatech.api.enums.ItemList.PeppermintTea;
+import static kubatech.api.enums.ItemList.PuerhTea;
+import static kubatech.api.enums.ItemList.PuerhTeaLeaf;
+import static kubatech.api.enums.ItemList.RolledTeaLeaf;
+import static kubatech.api.enums.ItemList.SteamedTeaLeaf;
+import static kubatech.api.enums.ItemList.TeaAcceptor;
+import static kubatech.api.enums.ItemList.TeaAcceptorResearchNote;
+import static kubatech.api.enums.ItemList.TeaLeafDehydrated;
+import static kubatech.api.enums.ItemList.WhiteTea;
+import static kubatech.api.enums.ItemList.WhiteTeaLeaf;
+import static kubatech.api.enums.ItemList.YellowTea;
+import static kubatech.api.enums.ItemList.YellowTeaLeaf;
+
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidRegistry;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import forestry.plugins.PluginCore;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.TierEU;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.api.util.GT_Utility;
+import ic2.core.Ic2Items;
+import kubatech.api.enums.ItemList;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_DEFusionCrafter;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeEntityCrusher;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_MegaIndustrialApiary;
+
+public class RecipeLoader {
+
+ protected static final long bitsd = GT_ModHandler.RecipeBits.NOT_REMOVABLE | GT_ModHandler.RecipeBits.REVERSIBLE
+ | GT_ModHandler.RecipeBits.BUFFERED
+ | GT_ModHandler.RecipeBits.DISMANTLEABLE;
+
+ public static void registerMTEs() {
+ if (EnderIO.isModLoaded()) {
+ ExtremeEntityCrusher.set(
+ new GT_MetaTileEntity_ExtremeEntityCrusher(
+ ExtremeEntityCrusherController.ID,
+ "multimachine.entitycrusher",
+ "Extreme Entity Crusher").getStackForm(1));
+ }
+
+ if (Forestry.isModLoaded()) {
+ ExtremeIndustrialApiary.set(
+ new GT_MetaTileEntity_MegaIndustrialApiary(
+ ExtremeIndustrialApiaryController.ID,
+ "multimachine.extremeapiary",
+ "Industrial Apicultural Acclimatiser and Drone Domestication Station").getStackForm(1));
+ }
+
+ ExtremeIndustrialGreenhouse.set(
+ new GT_MetaTileEntity_ExtremeIndustrialGreenhouse(
+ ExtremeIndustrialGreenhouseController.ID,
+ "multimachine.extremegreenhouse",
+ "Extreme Industrial Greenhouse").getStackForm(1));
+
+ if (DraconicEvolution.isModLoaded()) {
+ DraconicEvolutionFusionCrafter.set(
+ new GT_MetaTileEntity_DEFusionCrafter(
+ DraconicEvolutionFusionCrafterController.ID,
+ "multimachine.defusioncrafter",
+ "Draconic Evolution Fusion Crafter").getStackForm(1));
+ }
+ }
+
+ public static void addRecipes() {
+
+ if (EnderIO.isModLoaded()) {
+ GT_ModHandler.addCraftingRecipe(
+ ItemList.ExtremeEntityCrusher.get(1),
+ bitsd,
+ new Object[] { "RCR", "CHC", "VVV", 'R', gregtech.api.enums.ItemList.Robot_Arm_EV, 'C',
+ OrePrefixes.circuit.get(Materials.EV), 'H', gregtech.api.enums.ItemList.Hull_EV, 'V',
+ GT_ModHandler.getModItem(OpenBlocks.ID, "vacuumhopper", 1, new ItemStack(Blocks.hopper)) });
+ }
+
+ if (Forestry.isModLoaded()) {
+ GT_Values.RA.stdBuilder()
+ .metadata(RESEARCH_ITEM, gregtech.api.enums.ItemList.Machine_IndustrialApiary.get(1))
+ .metadata(RESEARCH_TIME, 8 * MINUTES + 20 * SECONDS)
+ .itemInputs(
+ gregtech.api.enums.ItemList.Machine_IndustrialApiary.get(64L),
+ gregtech.api.enums.ItemList.IndustrialApiary_Upgrade_Acceleration_8_Upgraded.get(64L),
+ gregtech.api.enums.ItemList.IndustrialApiary_Upgrade_STABILIZER.get(64L),
+ gregtech.api.enums.ItemList.Robot_Arm_UV.get(16L),
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L })
+ .fluidInputs(
+ FluidRegistry.getFluidStack("molten.indalloy140", 28800),
+ FluidRegistry.getFluidStack("for.honey", 20000))
+ .itemOutputs(ExtremeIndustrialApiary.get(1))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(5 * MINUTES)
+ .addTo(AssemblyLine);
+
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse
+ .addFertilizerItem(PluginCore.items.fertilizerCompound.getItemStack(1));
+ }
+
+ GT_ModHandler.addCraftingRecipe(
+ ExtremeIndustrialGreenhouse.get(1),
+ bitsd,
+ new Object[] { "AZA", "BRB", "AZA", 'B', gregtech.api.enums.ItemList.Casing_CleanStainlessSteel, 'R',
+ GT_ModHandler.getModItem(EnderIO.ID, "blockFarmStation", 1, new ItemStack(Items.diamond_hoe)), 'A',
+ gregtech.api.enums.ItemList.AcceleratorIV.get(1), 'Z', OrePrefixes.circuit.get(Materials.ZPM) });
+
+ // Vanilla should always be loaded
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse.addFertilizerItem(new ItemStack(Items.dye, 1, 15));
+ // IC2 should always be loaded
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse.addFertilizerItem(Ic2Items.fertilizer);
+
+ if (DraconicEvolution.isModLoaded()) {
+ // Controller recipe added in TecTech
+ DEFCRecipes.addRecipes();
+ }
+ RegisterTeaLine();
+ }
+
+ private static boolean lateRecipesInitialized = false;
+
+ public static void addRecipesLate() {
+ // Runs on server start
+ if (lateRecipesInitialized) return;
+ lateRecipesInitialized = true;
+ }
+
+ private static void RegisterTeaLine() {
+ // TEA LINE //
+ if (PamsHarvestCraft.isModLoaded()) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GameRegistry.findItemStack("harvestcraft", "tealeafItem", 1))
+ .itemOutputs(TeaLeafDehydrated.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(TeaLeafDehydrated.get(1))
+ .itemOutputs(WhiteTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(TeaLeafDehydrated.get(1))
+ .itemOutputs(SteamedTeaLeaf.get(1))
+ .fluidInputs(FluidRegistry.getFluidStack("water", 50))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(mixerRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(SteamedTeaLeaf.get(1))
+ .itemOutputs(YellowTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(TeaLeafDehydrated.get(1), GT_Utility.getIntegratedCircuit(1))
+ .itemOutputs(RolledTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(benderRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(RolledTeaLeaf.get(1))
+ .itemOutputs(GreenTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(RolledTeaLeaf.get(1), GT_Utility.getIntegratedCircuit(1))
+ .itemOutputs(OxidizedTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(UniversalChemical);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(OxidizedTeaLeaf.get(1))
+ .itemOutputs(BlackTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(RolledTeaLeaf.get(1), GT_Utility.getIntegratedCircuit(2))
+ .itemOutputs(FermentedTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(10 * SECONDS)
+ .addTo(UniversalChemical);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(FermentedTeaLeaf.get(1))
+ .itemOutputs(PuerhTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(TeaLeafDehydrated.get(1))
+ .itemOutputs(BruisedTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(cutterRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(BruisedTeaLeaf.get(1), GT_Utility.getIntegratedCircuit(1))
+ .itemOutputs(PartiallyOxidizedTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(2 * SECONDS + 10 * TICKS)
+ .addTo(UniversalChemical);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(PartiallyOxidizedTeaLeaf.get(1))
+ .itemOutputs(OolongTeaLeaf.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(chemicalDehydratorRecipes);
+
+ // Tea Assembly
+ GameRegistry.addSmelting(BlackTeaLeaf.get(1), BlackTea.get(1), 10);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(BlackTea.get(1), GameRegistry.findItemStack("harvestcraft", "limejuiceItem", 1))
+ .itemOutputs(EarlGrayTea.get(1))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(mixerRecipes);
+
+ GameRegistry.addSmelting(GreenTeaLeaf.get(1), GreenTea.get(1), 10);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(BlackTea.get(1))
+ .itemOutputs(LemonTea.get(1))
+ .fluidInputs(FluidRegistry.getFluidStack("potion.lemonjuice", 1000))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(mixerRecipes);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(BlackTea.get(1))
+ .itemOutputs(MilkTea.get(1))
+ .fluidInputs(FluidRegistry.getFluidStack("milk", 1000))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(mixerRecipes);
+
+ GameRegistry.addSmelting(OolongTeaLeaf.get(1), OolongTea.get(1), 10);
+
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GameRegistry.findItemStack("harvestcraft", "peppermintItem", 1))
+ .itemOutputs(PeppermintTea.get(1))
+ .fluidInputs(FluidRegistry.getFluidStack("water", 1000))
+ .eut(TierEU.RECIPE_LV)
+ .duration(5 * SECONDS)
+ .addTo(mixerRecipes);
+
+ GameRegistry.addSmelting(PuerhTeaLeaf.get(1), PuerhTea.get(1), 10);
+ GameRegistry.addSmelting(WhiteTeaLeaf.get(1), WhiteTea.get(1), 10);
+ GameRegistry.addSmelting(YellowTeaLeaf.get(1), YellowTea.get(1), 10);
+ }
+ if (Avaritia.isModLoaded() && NewHorizonsCoreMod.isModLoaded()) {
+ GT_Values.RA.stdBuilder()
+ .metadata(RESEARCH_ITEM, TeaAcceptorResearchNote.get(1))
+ .metadata(RESEARCH_TIME, 8 * MINUTES + 20 * SECONDS)
+ .itemInputs(
+ LegendaryUltimateTea.get(0),
+ GameRegistry.findItemStack("Avaritia", "Neutronium_Compressor", 1),
+ gregtech.api.enums.ItemList.Quantum_Tank_EV.get(1),
+ FluidExtractorUHV.get(10),
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L },
+ new Object[] { OrePrefixes.circuit.get(Materials.UV), 4L })
+ .fluidInputs(FluidRegistry.getFluidStack("molten.indalloy140", 28800))
+ .itemOutputs(TeaAcceptor.get(1))
+ .eut(TierEU.RECIPE_UHV)
+ .duration(5 * MINUTES)
+ .addTo(AssemblyLine);
+ }
+ }
+}
diff --git a/src/main/java/kubatech/loaders/TCLoader.java b/src/main/java/kubatech/loaders/TCLoader.java
new file mode 100644
index 0000000000..f6ff1cac9f
--- /dev/null
+++ b/src/main/java/kubatech/loaders/TCLoader.java
@@ -0,0 +1,153 @@
+/*
+ * 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.loaders;
+
+import static gregtech.api.enums.Mods.DraconicEvolution;
+import static gregtech.api.enums.Mods.NewHorizonsCoreMod;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import kubatech.api.enums.ItemList;
+import kubatech.loaders.item.items.TeaUltimate;
+import thaumcraft.api.ThaumcraftApi;
+import thaumcraft.api.ThaumcraftApiHelper;
+import thaumcraft.api.aspects.Aspect;
+import thaumcraft.api.aspects.AspectList;
+import thaumcraft.api.crafting.InfusionRecipe;
+import thaumcraft.api.research.ResearchCategories;
+import thaumcraft.api.research.ResearchItem;
+import thaumcraft.api.research.ResearchPage;
+
+public class TCLoader {
+
+ public static final String TCCategoryKey = "KUBATECH";
+
+ public static void init() {
+ /*
+ * ResearchCategories.registerCategory(
+ * TCCategoryKey,
+ * new ResourceLocation(Tags.MODID, "textures/gui/green_tea.png"),
+ * new ResourceLocation("thaumcraft", "textures/gui/gui_researchback.png"));
+ */
+ if (!NewHorizonsCoreMod.isModLoaded() || !DraconicEvolution.isModLoaded()) return;
+ registerRecipe();
+ registerResearch();
+ }
+
+ private static InfusionRecipe ultimateTeaRecipe = null;
+
+ @SuppressWarnings("unchecked")
+ private static void registerRecipe() {
+ if (ultimateTeaRecipe != null) return;
+ final ItemStack[] components = new ItemStack[] {
+ // ItemList.LegendaryBlackTea.get(1),
+ // ItemList.LegendaryButterflyTea.get(1),
+ ItemList.LegendaryEarlGrayTea.get(1), // MApiary
+ ItemList.LegendaryGreenTea.get(1), // EIG
+ // ItemList.LegendaryLemonTea.get(1),
+ // ItemList.LegendaryMilkTea.get(1),
+ // ItemList.LegendaryOolongTea.get(1),
+ ItemList.LegendaryPeppermintTea.get(1), // HTGR
+ ItemList.LegendaryPuerhTea.get(1), // EEC
+ // ItemList.LegendaryRedTea.get(1),
+ // ItemList.LegendaryWhiteTea.get(1),
+ ItemList.LegendaryYellowTea.get(1), // IApiary
+ ItemList.BlackTea.get(1), ItemList.EarlGrayTea.get(1), ItemList.GreenTea.get(1), ItemList.LemonTea.get(1),
+ ItemList.MilkTea.get(1), ItemList.OolongTea.get(1), ItemList.PeppermintTea.get(1), ItemList.PuerhTea.get(1),
+ ItemList.WhiteTea.get(1), ItemList.YellowTea.get(1) };
+
+ final HashSet<ItemID> componentsHashed = Arrays.stream(components)
+ .map(stack -> ItemID.createNoCopy(stack, true, false, true))
+ .collect(Collectors.toCollection(HashSet::new));
+
+ // noinspection unchecked
+ ThaumcraftApi.getCraftingRecipes()
+ .add(
+ ultimateTeaRecipe = new InfusionRecipe(
+ "KT_UltimateTea",
+ ItemList.LegendaryUltimateTea.get(1),
+ 10,
+ new AspectList().add(Aspect.MAGIC, 100)
+ .add(Aspect.HEAL, 100)
+ .add(Aspect.PLANT, 100)
+ .add(Aspect.EXCHANGE, 100),
+ GameRegistry.findItemStack("DraconicEvolution", "dezilsMarshmallow", 1),
+ components) {
+
+ @Override
+ public boolean matches(ArrayList<ItemStack> input, ItemStack central, World world,
+ EntityPlayer player) {
+ if (!central.isItemEqual(getRecipeInput())) return false;
+ if (!ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), this.research))
+ return false;
+ if (componentsHashed.size() > input.size()) return false;
+ HashSet<ItemID> hashedInputs = input.stream()
+ .map(stack -> ItemID.createNoCopy(stack, true, false, true))
+ .collect(Collectors.toCollection(HashSet::new));
+ return hashedInputs.containsAll(componentsHashed);
+ }
+ });
+ }
+
+ private static ResearchItem ultimateTeaResearch = null;
+
+ private static void registerResearch() {
+ if (ultimateTeaResearch == null) {
+ ultimateTeaResearch = new ResearchItem(
+ "KT_UltimateTea",
+ "NEWHORIZONS" /* TCCategoryKey */,
+ new AspectList().add(Aspect.MAGIC, 1)
+ .add(Aspect.HEAL, 1)
+ .add(Aspect.PLANT, 1)
+ .add(Aspect.EXCHANGE, 1),
+ -2,
+ 4,
+ 2,
+ ItemList.LegendaryUltimateTea.get(1)) {
+
+ @Override
+ public String getName() {
+ return TeaUltimate.getUltimateTeaDisplayName(super.getName());
+ }
+ };
+ ultimateTeaResearch.setPages(new ResearchPage("KT.research.ultimatetea") {
+
+ @Override
+ public String getTranslatedText() {
+ return TeaUltimate.getUltimateTeaDisplayName(super.getTranslatedText());
+ }
+ }, new ResearchPage(ultimateTeaRecipe));
+ ultimateTeaResearch.setParents("INFUSION", "DEZILSMARSHMALLOW");
+ ThaumcraftApi.addWarpToResearch("KT_UltimateTea", 20);
+ }
+ ResearchCategories.addResearch(ultimateTeaResearch);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/defc/DEFCCasingBlock.java b/src/main/java/kubatech/loaders/block/defc/DEFCCasingBlock.java
new file mode 100644
index 0000000000..5a6b95acfe
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/defc/DEFCCasingBlock.java
@@ -0,0 +1,63 @@
+package kubatech.loaders.block.defc;
+
+import static kubatech.kubatech.KT;
+
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.IIcon;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.enums.Textures;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_LanguageManager;
+import gregtech.common.blocks.GT_Block_Casings_Abstract;
+import kubatech.Tags;
+import kubatech.api.enums.ItemList;
+
+public class DEFCCasingBlock extends GT_Block_Casings_Abstract {
+
+ @SideOnly(Side.CLIENT)
+ private IIcon[] texture;
+
+ public DEFCCasingBlock() {
+ super(DEFCCasingItemBlock.class, "defc.casing", Material.anvil);
+ this.setHardness(15.0F);
+ this.setResistance(30.0F);
+ this.setCreativeTab(KT);
+
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".7.name", "Naquadah Alloy Fusion Casing");
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".8.name", "Bloody Ichorium Fusion Casing");
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".9.name", "Draconium Fusion Casing");
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".10.name", "Wyvern Fusion Casing");
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".11.name", "Awakened Draconium Fusion Casing");
+ GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".12.name", "Chaotic Fusion Casing");
+
+ ItemList.DEFCCasingBase.set(new ItemStack(this, 1, 7));
+ ItemList.DEFCCasingT1.set(new ItemStack(this, 1, 8));
+ ItemList.DEFCCasingT2.set(new ItemStack(this, 1, 9));
+ ItemList.DEFCCasingT3.set(new ItemStack(this, 1, 10));
+ ItemList.DEFCCasingT4.set(new ItemStack(this, 1, 11));
+ ItemList.DEFCCasingT5.set(new ItemStack(this, 1, 12));
+
+ // Taking one texture slot :P
+ Textures.BlockIcons.setCasingTexture((byte) 1, (byte) (15 + 48), TextureFactory.of(this, 0));
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public IIcon getIcon(int side, int meta) {
+ if (meta < 7 || meta > 12) return texture[0];
+ return texture[meta - 7];
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void registerBlockIcons(IIconRegister aIconRegister) {
+ texture = new IIcon[6];
+ for (int i = 0; i < texture.length; i++) {
+ texture[i] = aIconRegister.registerIcon(Tags.MODID + ":casing/defc_" + i);
+ }
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/defc/DEFCCasingItemBlock.java b/src/main/java/kubatech/loaders/block/defc/DEFCCasingItemBlock.java
new file mode 100644
index 0000000000..669e3a6dc8
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/defc/DEFCCasingItemBlock.java
@@ -0,0 +1,36 @@
+package kubatech.loaders.block.defc;
+
+import static kubatech.kubatech.KT;
+
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.StatCollector;
+
+import gregtech.common.blocks.GT_Item_Casings_Abstract;
+
+public class DEFCCasingItemBlock extends GT_Item_Casings_Abstract {
+
+ public DEFCCasingItemBlock(Block block) {
+ super(block);
+ this.setMaxDamage(0);
+ this.setHasSubtypes(true);
+ this.setCreativeTab(KT);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List aList, boolean aF3_H) {
+ if (getDamage(aStack) > 7)
+ aList.add(StatCollector.translateToLocalFormatted("defc.casing.tip", getDamage(aStack) - 7));
+ super.addInformation(aStack, aPlayer, aList, aF3_H);
+ }
+
+ @Override
+ public IIcon getIconFromDamage(int p_77617_1_) {
+ return this.field_150939_a.getIcon(0, p_77617_1_);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/BlockProxy.java b/src/main/java/kubatech/loaders/block/kubablock/BlockProxy.java
new file mode 100644
index 0000000000..5aef5135a3
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/BlockProxy.java
@@ -0,0 +1,111 @@
+/*
+ * 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.loaders.block.kubablock;
+
+import static kubatech.loaders.block.kubablock.KubaBlock.defaultTileEntityUI;
+
+import java.util.List;
+
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+
+import com.gtnewhorizons.modularui.api.screen.ITileWithModularUI;
+
+import kubatech.Tags;
+
+public class BlockProxy {
+
+ private final String unlocalizedName;
+ private final String sideTexturePath;
+ private final String topBottomTexturePath;
+ private IIcon sideIcon;
+ private IIcon topBottomIcon;
+
+ public BlockProxy(String unlocalizedName, String texture) {
+ this.unlocalizedName = "kubablock." + unlocalizedName;
+ sideTexturePath = topBottomTexturePath = Tags.MODID + ":" + texture;
+ }
+
+ public BlockProxy(String unlocalizedName, String sideTexture, String topBottomTexture) {
+ this.unlocalizedName = "kubablock." + unlocalizedName;
+ sideTexturePath = Tags.MODID + ":" + sideTexture;
+ topBottomTexturePath = Tags.MODID + ":" + topBottomTexture;
+ }
+
+ public void itemInit(int ID) {}
+
+ public boolean onActivated(World world, int x, int y, int z, EntityPlayer player) {
+ if (this instanceof IProxyTileEntityProvider) {
+ TileEntity te = world.getTileEntity(x, y, z);
+ if (te instanceof ITileWithModularUI) {
+ if (world.isRemote) return true;
+ if (te instanceof KubaBlock.IModularUIProvider) ((KubaBlock.IModularUIProvider) te).getUI()
+ .open(player, world, x, y, z);
+ else defaultTileEntityUI.open(player, world, x, y, z);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void onBlockPlaced(World world, int x, int y, int z, EntityLivingBase player, ItemStack stack) {}
+
+ public void registerIcon(IIconRegister iconRegister) {
+ sideIcon = iconRegister.registerIcon(sideTexturePath);
+ if (sideTexturePath.equals(topBottomTexturePath)) topBottomIcon = sideIcon;
+ else topBottomIcon = iconRegister.registerIcon(topBottomTexturePath);
+ }
+
+ public IIcon getIcon(int side) {
+ if (side <= 1) return topBottomIcon;
+ else return sideIcon;
+ }
+
+ public String getUnlocalizedName() {
+ return this.unlocalizedName;
+ }
+
+ public String getDisplayName(ItemStack stack) {
+ return StatCollector.translateToLocal(this.unlocalizedName + ".name")
+ .trim();
+ }
+
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {}
+
+ public float getHardness() {
+ return 10.f;
+ }
+
+ public Material getMaterial() {
+ return Material.anvil;
+ }
+
+ public float getResistance() {
+ return 5.f;
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/IProxyTileEntityProvider.java b/src/main/java/kubatech/loaders/block/kubablock/IProxyTileEntityProvider.java
new file mode 100644
index 0000000000..c9fb1e8da9
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/IProxyTileEntityProvider.java
@@ -0,0 +1,29 @@
+/*
+ * 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.loaders.block.kubablock;
+
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.world.World;
+
+public interface IProxyTileEntityProvider {
+
+ TileEntity createTileEntity(World world);
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/KubaBlock.java b/src/main/java/kubatech/loaders/block/kubablock/KubaBlock.java
new file mode 100644
index 0000000000..d6349d88ff
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/KubaBlock.java
@@ -0,0 +1,198 @@
+/*
+ * 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.loaders.block.kubablock;
+
+import static kubatech.kubatech.KT;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Function;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+
+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.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 kubatech.loaders.BlockLoader;
+
+public class KubaBlock extends Block {
+
+ public static final Function<IModularUIContainerCreator, UIInfo<?, ?>> TileEntityUIFactory = containerConstructor -> UIBuilder
+ .of()
+ .container((player, world, x, y, z) -> {
+ TileEntity te = world.getTileEntity(x, y, z);
+ if (te instanceof ITileWithModularUI) {
+ final UIBuildContext buildContext = new UIBuildContext(player);
+ final ModularWindow window = ((ITileWithModularUI) te).createWindow(buildContext);
+ return containerConstructor
+ .createUIContainer(new ModularUIContext(buildContext, te::markDirty), window);
+ }
+ return null;
+ })
+ .gui(((player, world, x, y, z) -> {
+ if (!world.isRemote) return null;
+ TileEntity te = world.getTileEntity(x, y, z);
+ if (te instanceof ITileWithModularUI) {
+ final UIBuildContext buildContext = new UIBuildContext(player);
+ final ModularWindow window = ((ITileWithModularUI) te).createWindow(buildContext);
+ return new ModularGui(
+ containerConstructor.createUIContainer(new ModularUIContext(buildContext, null), window));
+ }
+ return null;
+ }))
+ .build();
+
+ public static final UIInfo<?, ?> defaultTileEntityUI = TileEntityUIFactory.apply(ModularUIContainer::new);
+
+ static final HashMap<Integer, BlockProxy> blocks = new HashMap<>();
+ private static int idCounter = 0;
+
+ public KubaBlock(Material p_i45394_1_) {
+ super(p_i45394_1_);
+ setCreativeTab(KT);
+ }
+
+ public ItemStack registerProxyBlock(BlockProxy block) {
+ blocks.put(idCounter, block);
+ block.itemInit(idCounter);
+ return new ItemStack(BlockLoader.kubaItemBlock, 1, idCounter++);
+ }
+
+ private BlockProxy getBlock(int id) {
+ return blocks.get(id);
+ }
+
+ WeakReference<World> lastAccessor = null;
+ int X, Y, Z;
+
+ public void setLastBlockAccess(World accessor, int x, int y, int z) {
+ lastAccessor = new WeakReference<>(accessor);
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ @Override
+ public boolean hasTileEntity(int meta) {
+ return getBlock(meta) instanceof IProxyTileEntityProvider;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void getSubBlocks(Item p_149666_1_, CreativeTabs p_149666_2_, List p_149666_3_) {
+ for (int i = 0; i < blocks.size(); i++) p_149666_3_.add(new ItemStack(p_149666_1_, 1, i));
+ }
+
+ @Override
+ public int damageDropped(int meta) {
+ return meta;
+ }
+
+ @Override
+ public void registerBlockIcons(IIconRegister p_149651_1_) {
+ blocks.values()
+ .forEach(b -> b.registerIcon(p_149651_1_));
+ }
+
+ @Override
+ public IIcon getIcon(int p_149691_1_, int p_149691_2_) {
+ return blocks.get(p_149691_2_)
+ .getIcon(p_149691_1_);
+ }
+
+ @Override
+ public String getLocalizedName() {
+ return "KUBABLOCK";
+ }
+
+ @Override
+ public TileEntity createTileEntity(World world, int metadata) {
+ if (!hasTileEntity(metadata)) return null;
+ return ((IProxyTileEntityProvider) getBlock(metadata)).createTileEntity(world);
+ }
+
+ @Override
+ public boolean onBlockActivated(World p_149727_1_, int p_149727_2_, int p_149727_3_, int p_149727_4_,
+ EntityPlayer p_149727_5_, int p_149727_6_, float p_149727_7_, float p_149727_8_, float p_149727_9_) {
+ return getBlock(p_149727_1_.getBlockMetadata(p_149727_2_, p_149727_3_, p_149727_4_))
+ .onActivated(p_149727_1_, p_149727_2_, p_149727_3_, p_149727_4_, p_149727_5_);
+ }
+
+ @Override
+ public void onBlockPlacedBy(World p_149689_1_, int p_149689_2_, int p_149689_3_, int p_149689_4_,
+ EntityLivingBase p_149689_5_, ItemStack p_149689_6_) {
+ getBlock(p_149689_6_.getItemDamage())
+ .onBlockPlaced(p_149689_1_, p_149689_2_, p_149689_3_, p_149689_4_, p_149689_5_, p_149689_6_);
+ }
+
+ @Override
+ public float getBlockHardness(World p_149712_1_, int p_149712_2_, int p_149712_3_, int p_149712_4_) {
+ return getBlock(p_149712_1_.getBlockMetadata(p_149712_2_, p_149712_3_, p_149712_4_)).getHardness();
+ }
+
+ @Override
+ public Material getMaterial() {
+ if (lastAccessor == null) return super.getMaterial();
+ World world = lastAccessor.get();
+ if (world == null) {
+ lastAccessor = null;
+ return super.getMaterial();
+ }
+ if (world.getBlock(X, Y, Z) != this) return super.getMaterial();
+ return getBlock(world.getBlockMetadata(X, Y, Z)).getMaterial();
+ }
+
+ @Override
+ public float getExplosionResistance(Entity par1Entity, World world, int x, int y, int z, double explosionX,
+ double explosionY, double explosionZ) {
+ return getBlock(world.getBlockMetadata(x, y, z)).getResistance();
+ }
+
+ @FunctionalInterface
+ public interface IModularUIContainerCreator {
+
+ ModularUIContainer createUIContainer(ModularUIContext context, ModularWindow mainWindow);
+ }
+
+ @FunctionalInterface
+ public interface IModularUIProvider {
+
+ UIInfo<?, ?> getUI();
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/KubaItemBlock.java b/src/main/java/kubatech/loaders/block/kubablock/KubaItemBlock.java
new file mode 100644
index 0000000000..bd5c9d1119
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/KubaItemBlock.java
@@ -0,0 +1,73 @@
+/*
+ * 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.loaders.block.kubablock;
+
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+public class KubaItemBlock extends ItemBlock {
+
+ public KubaItemBlock(Block p_i45328_1_) {
+ super(p_i45328_1_);
+ setHasSubtypes(true);
+ }
+
+ @Override
+ public boolean placeBlockAt(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side,
+ float hitX, float hitY, float hitZ, int metadata) {
+ return super.placeBlockAt(stack, player, world, x, y, z, side, hitX, hitY, hitZ, metadata);
+ }
+
+ @Override
+ public void registerIcons(IIconRegister p_94581_1_) {
+ super.registerIcons(p_94581_1_);
+ }
+
+ @Override
+ public String getUnlocalizedName(ItemStack p_77667_1_) {
+ return KubaBlock.blocks.get(p_77667_1_.getItemDamage())
+ .getUnlocalizedName();
+ }
+
+ @Override
+ public String getItemStackDisplayName(ItemStack p_77653_1_) {
+ return KubaBlock.blocks.get(p_77653_1_.getItemDamage())
+ .getDisplayName(p_77653_1_);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List p_77624_3_, boolean p_77624_4_) {
+ KubaBlock.blocks.get(p_77624_1_.getItemDamage())
+ .addInformation(p_77624_1_, p_77624_2_, p_77624_3_, p_77624_4_);
+ }
+
+ @Override
+ public int getMetadata(int p_77647_1_) {
+ return p_77647_1_;
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaAcceptor.java b/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaAcceptor.java
new file mode 100644
index 0000000000..06ee3490b1
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaAcceptor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.loaders.block.kubablock.blocks;
+
+import java.util.List;
+
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.world.World;
+
+import kubatech.loaders.block.kubablock.BlockProxy;
+import kubatech.loaders.block.kubablock.IProxyTileEntityProvider;
+import kubatech.tileentity.TeaAcceptorTile;
+
+public class TeaAcceptor extends BlockProxy implements IProxyTileEntityProvider {
+
+ public TeaAcceptor() {
+ super("tea_acceptor", "tea_acceptor", "blank");
+ }
+
+ @Override
+ public TileEntity createTileEntity(World world) {
+ return new TeaAcceptorTile();
+ }
+
+ @Override
+ public void onBlockPlaced(World world, int x, int y, int z, EntityLivingBase player, ItemStack stack) {
+ if (world.isRemote) return;
+ if (!(player instanceof EntityPlayerMP)) return;
+ ((TeaAcceptorTile) world.getTileEntity(x, y, z)).setTeaOwner(player.getPersistentID());
+ }
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {
+ tooltipList.add("Accepts Tea items and adds them to your network");
+ tooltipList.add("Can accept up to 10 stacks per tick");
+ }
+
+ @Override
+ public float getResistance() {
+ return 999999999999.f;
+ }
+}
diff --git a/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaStorage.java b/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaStorage.java
new file mode 100644
index 0000000000..436af9a4e2
--- /dev/null
+++ b/src/main/java/kubatech/loaders/block/kubablock/blocks/TeaStorage.java
@@ -0,0 +1,66 @@
+/*
+ * 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.loaders.block.kubablock.blocks;
+
+import static kubatech.api.Variables.numberFormat;
+
+import java.util.List;
+
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.world.World;
+
+import kubatech.loaders.block.kubablock.BlockProxy;
+import kubatech.loaders.block.kubablock.IProxyTileEntityProvider;
+import kubatech.tileentity.TeaStorageTile;
+
+public class TeaStorage extends BlockProxy implements IProxyTileEntityProvider {
+
+ public TeaStorage() {
+ super("tea_storage", "tea_storage");
+ }
+
+ @Override
+ public TileEntity createTileEntity(World world) {
+ return new TeaStorageTile();
+ }
+
+ @Override
+ public void onBlockPlaced(World world, int x, int y, int z, EntityLivingBase player, ItemStack stack) {
+ if (world.isRemote) return;
+ if (!(player instanceof EntityPlayerMP)) return;
+ ((TeaStorageTile) world.getTileEntity(x, y, z)).setTeaOwner(player.getPersistentID());
+ }
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {
+ tooltipList.add("Extends Tea Storage by " + EnumChatFormatting.RED + numberFormat.format(Long.MAX_VALUE));
+ }
+
+ @Override
+ public float getResistance() {
+ return 999999999999.f;
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/IItemProxyGUI.java b/src/main/java/kubatech/loaders/item/IItemProxyGUI.java
new file mode 100644
index 0000000000..80462e7a3a
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/IItemProxyGUI.java
@@ -0,0 +1,31 @@
+/*
+ * 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.loaders.item;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+
+public interface IItemProxyGUI {
+
+ ModularWindow createWindow(ItemStack stack, EntityPlayer player);
+}
diff --git a/src/main/java/kubatech/loaders/item/ItemProxy.java b/src/main/java/kubatech/loaders/item/ItemProxy.java
new file mode 100644
index 0000000000..402e68fa70
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/ItemProxy.java
@@ -0,0 +1,122 @@
+/*
+ * 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.loaders.item;
+
+import java.util.List;
+
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.EnumAction;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+
+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.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 kubatech.Tags;
+
+public class ItemProxy {
+
+ private static final UIInfo<?, ?> HeldItemUIInfo = UIBuilder.of()
+ .container((player, w, x, y, z) -> {
+ ItemStack stack = player.getHeldItem();
+ ItemProxy proxy = KubaItems.getItemProxy(stack);
+ if (!(proxy instanceof IItemProxyGUI)) return null;
+ UIBuildContext context = new UIBuildContext(player);
+ ModularWindow window = ((IItemProxyGUI) proxy).createWindow(stack, player);
+ return new ModularUIContainer(
+ new ModularUIContext(context, () -> player.inventoryContainer.detectAndSendChanges()),
+ window);
+ })
+ .gui((player, w, x, y, z) -> {
+ ItemStack stack = player.getHeldItem();
+ ItemProxy proxy = KubaItems.getItemProxy(stack);
+ if (!(proxy instanceof IItemProxyGUI)) return null;
+ UIBuildContext context = new UIBuildContext(player);
+ ModularWindow window = ((IItemProxyGUI) proxy).createWindow(stack, player);
+ return new ModularGui(new ModularUIContainer(new ModularUIContext(context, null), window));
+ })
+ .build();
+ private final String unlocalizedName;
+ private final String texturepath;
+ private IIcon icon;
+
+ public ItemProxy(String unlocalizedName, String texture) {
+ this.unlocalizedName = "kubaitem." + unlocalizedName;
+ texturepath = Tags.MODID + ":" + texture;
+ }
+
+ public ItemProxy(String unlocalizedNameAndTexture) {
+ this(unlocalizedNameAndTexture, unlocalizedNameAndTexture);
+ }
+
+ public void ItemInit(int index) {}
+
+ public String getUnlocalizedName() {
+ return unlocalizedName;
+ }
+
+ public String getDisplayName(ItemStack stack) {
+ return StatCollector.translateToLocal(this.unlocalizedName + ".name")
+ .trim();
+ }
+
+ public void registerIcon(IIconRegister iconRegister) {
+ icon = iconRegister.registerIcon(texturepath);
+ }
+
+ public IIcon getIcon() {
+ return icon;
+ }
+
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {}
+
+ public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isCurrentItem) {}
+
+ public EnumAction getItemUseAction(ItemStack stack) {
+ return EnumAction.none;
+ }
+
+ public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer entity) {
+ return stack;
+ }
+
+ public ItemStack onEaten(ItemStack stack, World world, EntityPlayer entity) {
+ return stack;
+ }
+
+ public int getMaxItemUseDuration() {
+ return 0;
+ }
+
+ public static void openHeldItemGUI(EntityPlayer player) {
+ ItemStack stack = player.getHeldItem();
+ if (KubaItems.getItemProxy(stack) instanceof IItemProxyGUI) HeldItemUIInfo.open(player);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/KubaItems.java b/src/main/java/kubatech/loaders/item/KubaItems.java
new file mode 100644
index 0000000000..3dde241ff5
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/KubaItems.java
@@ -0,0 +1,144 @@
+/*
+ * 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.loaders.item;
+
+import static kubatech.kubatech.KT;
+
+import java.util.HashMap;
+import java.util.List;
+
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.EnumAction;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import kubatech.loaders.ItemLoader;
+
+public class KubaItems extends Item {
+
+ private static final HashMap<Integer, ItemProxy> items = new HashMap<>();
+ private static int idCounter = 0;
+
+ public KubaItems() {
+ this.setMaxDamage(0);
+ this.setHasSubtypes(true);
+ this.setCreativeTab(KT);
+ this.setUnlocalizedName("kubaitems");
+ }
+
+ public ItemStack registerProxyItem(ItemProxy item) {
+ items.put(idCounter, item);
+ item.ItemInit(idCounter);
+ return new ItemStack(this, 1, idCounter++);
+ }
+
+ private ItemProxy getItem(ItemStack stack) {
+ return items.get(stack.getItemDamage());
+ }
+
+ public static ItemProxy getItemProxy(ItemStack stack) {
+ if (!(stack.getItem() instanceof KubaItems)) return null;
+ return ItemLoader.kubaitems.getItem(stack);
+ }
+
+ private ItemProxy getItem(int damage) {
+ return items.get(damage);
+ }
+
+ @Override
+ public boolean onItemUse(ItemStack p_77648_1_, EntityPlayer p_77648_2_, World p_77648_3_, int p_77648_4_,
+ int p_77648_5_, int p_77648_6_, int p_77648_7_, float p_77648_8_, float p_77648_9_, float p_77648_10_) {
+ return false;
+ }
+
+ @Override
+ public EnumAction getItemUseAction(ItemStack p_77661_1_) {
+ return getItem(p_77661_1_).getItemUseAction(p_77661_1_);
+ }
+
+ @Override
+ public ItemStack onItemRightClick(ItemStack p_77659_1_, World p_77659_2_, EntityPlayer p_77659_3_) {
+ return getItem(p_77659_1_).onItemRightClick(p_77659_1_, p_77659_2_, p_77659_3_);
+ }
+
+ @Override
+ public ItemStack onEaten(ItemStack p_77654_1_, World p_77654_2_, EntityPlayer p_77654_3_) {
+ return getItem(p_77654_1_).onEaten(p_77654_1_, p_77654_2_, p_77654_3_);
+ }
+
+ @Override
+ public int getMaxItemUseDuration(ItemStack p_77626_1_) {
+ return getItem(p_77626_1_).getMaxItemUseDuration();
+ }
+
+ @Override
+ public int getMetadata(int p_77647_1_) {
+ return p_77647_1_;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List p_77624_3_, boolean p_77624_4_) {
+ getItem(p_77624_1_).addInformation(p_77624_1_, p_77624_2_, (List<String>) p_77624_3_, p_77624_4_);
+ }
+
+ @Override
+ public String getUnlocalizedName(ItemStack p_77667_1_) {
+ return getItem(p_77667_1_).getUnlocalizedName();
+ }
+
+ @Override
+ public String getItemStackDisplayName(ItemStack p_77653_1_) {
+ return getItem(p_77653_1_).getDisplayName(p_77653_1_);
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public void registerIcons(IIconRegister p_94581_1_) {
+ items.values()
+ .forEach(t -> t.registerIcon(p_94581_1_));
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public IIcon getIconFromDamage(int damage) {
+ return getItem(damage).getIcon();
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void getSubItems(Item p_150895_1_, CreativeTabs p_150895_2_, List p_150895_3_) {
+ for (int i = 0; i < items.size(); i++) p_150895_3_.add(new ItemStack(p_150895_1_, 1, i));
+ }
+
+ @Override
+ public void onUpdate(ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_,
+ boolean p_77663_5_) {
+ getItem(p_77663_1_).onUpdate(p_77663_1_, p_77663_2_, p_77663_3_, p_77663_4_, p_77663_5_);
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/items/Tea.java b/src/main/java/kubatech/loaders/item/items/Tea.java
new file mode 100644
index 0000000000..f1b3ae3d0b
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/items/Tea.java
@@ -0,0 +1,72 @@
+/*
+ * 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.loaders.item.items;
+
+import java.util.List;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.EnumAction;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+import kubatech.loaders.item.ItemProxy;
+
+public class Tea extends ItemProxy {
+
+ private final int heal;
+ private final float saturation;
+
+ public Tea(String unlocalizedName, int heal, float saturation) {
+ super("tea." + unlocalizedName, "tea/" + unlocalizedName);
+ this.heal = heal;
+ this.saturation = saturation;
+ }
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {
+ tooltipList.add("Tea");
+ }
+
+ @Override
+ public EnumAction getItemUseAction(ItemStack stack) {
+ return EnumAction.drink;
+ }
+
+ @Override
+ public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer entity) {
+ entity.setItemInUse(stack, getMaxItemUseDuration());
+ return stack;
+ }
+
+ @Override
+ public ItemStack onEaten(ItemStack stack, World world, EntityPlayer entity) {
+ if (!entity.capabilities.isCreativeMode) --stack.stackSize;
+ entity.getFoodStats()
+ .addStats(heal, saturation);
+ world.playSoundAtEntity(entity, "random.burp", 0.5F, world.rand.nextFloat() * 0.1F + 0.9F);
+ return stack;
+ }
+
+ @Override
+ public int getMaxItemUseDuration() {
+ return 32;
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/items/TeaCollection.java b/src/main/java/kubatech/loaders/item/items/TeaCollection.java
new file mode 100644
index 0000000000..efa390c517
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/items/TeaCollection.java
@@ -0,0 +1,209 @@
+/*
+ * 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.loaders.item.items;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.EnumAction;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagString;
+import net.minecraft.stats.Achievement;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+import net.minecraftforge.common.AchievementPage;
+
+import kubatech.api.utils.ModUtils;
+import kubatech.loaders.ItemLoader;
+import kubatech.loaders.item.ItemProxy;
+
+public class TeaCollection extends ItemProxy {
+
+ protected static TeaPage teapage;
+ protected static LinkedList<Achievement> achievements;
+ protected Achievement achievement;
+ private final String achievementname;
+
+ public TeaCollection(String unlocalizedName) {
+ super("teacollection." + unlocalizedName, "tea/" + unlocalizedName);
+ achievementname = "teacollection." + unlocalizedName;
+ }
+
+ private static final int[][] achievement_poses = new int[][] { { 0, 0 }, { 2, 0 }, { 3, 1 }, { 4, 2 }, { 4, 4 },
+ { 3, 5 }, { 2, 6 }, { 0, 6 }, { -1, 5 }, { -2, 4 }, { -2, 2 }, { -1, 1 }, { 1, 3 } };
+
+ boolean checkTeaOwner(ItemStack stack, UUID player) {
+ NBTTagCompound tag = stack.stackTagCompound;
+ if (tag == null || !stack.stackTagCompound.hasKey("TeaOwnerUUID")) return true;
+ return stack.stackTagCompound.getString("TeaOwnerUUID")
+ .equals(player.toString());
+ }
+
+ boolean checkTeaOwner(ItemStack stack, String player) {
+ NBTTagCompound tag = stack.stackTagCompound;
+ if (tag == null || !stack.stackTagCompound.hasKey("TeaOwner")) return true;
+ return stack.stackTagCompound.getString("TeaOwner")
+ .equals(player);
+ }
+
+ private boolean checkOrSetTeaOwner(ItemStack stack, EntityPlayer player) {
+ NBTTagCompound tag = stack.stackTagCompound;
+ if (tag == null || !stack.stackTagCompound.hasKey("TeaOwnerUUID")) {
+ stack.setTagInfo(
+ "TeaOwnerUUID",
+ new NBTTagString(
+ player.getPersistentID()
+ .toString()));
+ stack.setTagInfo("TeaOwner", new NBTTagString(player.getCommandSenderName()));
+ return true;
+ }
+ if (stack.stackTagCompound.getString("TeaOwnerUUID")
+ .equals(
+ player.getPersistentID()
+ .toString())) {
+ stack.setTagInfo("TeaOwner", new NBTTagString(player.getCommandSenderName()));
+ return true;
+ } else return false;
+ }
+
+ @Override
+ public void ItemInit(int index) {
+ super.ItemInit(index);
+ if (teapage == null) {
+ teapage = new TeaPage();
+ AchievementPage.registerAchievementPage(teapage);
+ achievements = teapage.getAchievementsOriginal();
+ }
+ achievements.add(
+ achievement = new Achievement(
+ achievementname,
+ achievementname,
+ achievement_poses[index][0],
+ achievement_poses[index][1],
+ new ItemStack(ItemLoader.kubaitems, 1, index),
+ null).registerStat());
+ }
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {
+ if (!checkTeaOwner(stack, entity.getCommandSenderName())) {
+ tooltipList.add(
+ EnumChatFormatting.GRAY + ""
+ + EnumChatFormatting.BOLD
+ + ""
+ + EnumChatFormatting.ITALIC
+ + StatCollector.translateToLocal("kubaitem.notyours"));
+ return;
+ }
+ tooltipList.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("kubaitem.fromcollection"));
+ tooltipList.add(
+ EnumChatFormatting.GRAY + ""
+ + EnumChatFormatting.BOLD
+ + ""
+ + EnumChatFormatting.ITALIC
+ + ""
+ + EnumChatFormatting.UNDERLINE
+ + StatCollector.translateToLocal("kubaitem.teacollection"));
+ }
+
+ @Override
+ public EnumAction getItemUseAction(ItemStack stack) {
+ return EnumAction.drink;
+ }
+
+ @Override
+ public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer entity) {
+ if (!checkTeaOwner(stack, entity.getCommandSenderName())) return stack;
+ entity.setItemInUse(stack, 32);
+ return stack;
+ }
+
+ @Override
+ public ItemStack onEaten(ItemStack stack, World world, EntityPlayer entity) {
+ if (world.isRemote) return stack;
+ if (!(entity instanceof EntityPlayerMP)) return stack;
+ entity.addChatComponentMessage(
+ new ChatComponentText(
+ EnumChatFormatting.GREEN + StatCollector.translateToLocal("kubaitem.teacollection.mmm")));
+ entity.triggerAchievement(achievement);
+ return stack;
+ }
+
+ @Override
+ public int getMaxItemUseDuration() {
+ return 32;
+ }
+
+ @Override
+ public String getDisplayName(ItemStack stack) {
+ if (!ModUtils.isClientSided || Minecraft.getMinecraft().thePlayer == null) {
+ return super.getDisplayName(stack);
+ }
+ // UUID is different on client if in offline mode I think
+ if (checkTeaOwner(stack, Minecraft.getMinecraft().thePlayer.getCommandSenderName())) {
+ return super.getDisplayName(stack);
+ }
+ return EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + "" + EnumChatFormatting.ITALIC + "???????";
+ }
+
+ @Override
+ public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isCurrentItem) {
+ if (world.isRemote) return;
+ if (!(entity instanceof EntityPlayerMP)) return;
+ checkOrSetTeaOwner(stack, (EntityPlayer) entity);
+ NBTTagCompound tag = stack.stackTagCompound;
+ if (tag.hasKey("display")) tag.removeTag("display");
+ }
+
+ private static class TeaPage extends AchievementPage {
+
+ public TeaPage() {
+ super("Tea");
+ }
+
+ final LinkedList<Achievement> unlockedAchievements = new LinkedList<>();
+
+ @Override
+ public List<Achievement> getAchievements() {
+ if (!ModUtils.isClientSided) return super.getAchievements();
+
+ if (new Throwable().getStackTrace()[1].getMethodName()
+ .equals("isAchievementInPages")) return super.getAchievements(); // 5HEAD FIX
+
+ unlockedAchievements.clear();
+ for (Achievement achievement : achievements) if (Minecraft.getMinecraft().thePlayer.getStatFileWriter()
+ .hasAchievementUnlocked(achievement)) unlockedAchievements.add(achievement);
+ return unlockedAchievements;
+ }
+
+ private LinkedList<Achievement> getAchievementsOriginal() {
+ return (LinkedList<Achievement>) super.getAchievements();
+ }
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/items/TeaIngredient.java b/src/main/java/kubatech/loaders/item/items/TeaIngredient.java
new file mode 100644
index 0000000000..4ff248ea37
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/items/TeaIngredient.java
@@ -0,0 +1,41 @@
+/*
+ * 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.loaders.item.items;
+
+import java.util.List;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+
+import kubatech.loaders.item.ItemProxy;
+
+public class TeaIngredient extends ItemProxy {
+
+ public TeaIngredient(String unlocalizedName) {
+ super("teaingredient." + unlocalizedName, "teaingredient/" + unlocalizedName);
+ }
+
+ @Override
+ public void addInformation(ItemStack stack, EntityPlayer entity, List<String> tooltipList, boolean showDebugInfo) {
+ tooltipList.add(EnumChatFormatting.GRAY + "This is Tea ingredient");
+ }
+}
diff --git a/src/main/java/kubatech/loaders/item/items/TeaUltimate.java b/src/main/java/kubatech/loaders/item/items/TeaUltimate.java
new file mode 100644
index 0000000000..cee7eba132
--- /dev/null
+++ b/src/main/java/kubatech/loaders/item/items/TeaUltimate.java
@@ -0,0 +1,216 @@
+/*
+ * 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.loaders.item.items;
+
+import static kubatech.api.Variables.numberFormat;
+import static kubatech.api.Variables.numberFormatScientific;
+
+import java.math.BigInteger;
+import java.text.NumberFormat;
+import java.util.concurrent.atomic.AtomicReference;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.world.World;
+
+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.math.Color;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicTextWidget;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.MultiChildWidget;
+import com.gtnewhorizons.modularui.common.widget.TabButton;
+import com.gtnewhorizons.modularui.common.widget.TabContainer;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import codechicken.nei.NEIClientUtils;
+import kubatech.api.enums.ItemList;
+import kubatech.api.tea.TeaNetwork;
+import kubatech.api.utils.ModUtils;
+import kubatech.api.utils.StringUtils;
+import kubatech.loaders.item.IItemProxyGUI;
+
+public class TeaUltimate extends TeaCollection implements IItemProxyGUI {
+
+ public TeaUltimate() {
+ super("ultimate_tea");
+ }
+
+ private static String name = "";
+ private static long timeCounter = 0;
+ private static int colorCounter = 0;
+
+ public static String getUltimateTeaDisplayName(String displayName) {
+ long current = System.currentTimeMillis();
+ if (current - timeCounter > 100) {
+ timeCounter = current;
+ name = StringUtils.applyRainbow(
+ "ULTIMATE",
+ colorCounter++,
+ EnumChatFormatting.BOLD.toString() + EnumChatFormatting.OBFUSCATED);
+ }
+ return String.format(displayName, name + EnumChatFormatting.RESET);
+ }
+
+ @Override
+ public String getDisplayName(ItemStack stack) {
+ if (!ModUtils.isClientSided || Minecraft.getMinecraft().thePlayer == null) return super.getDisplayName(stack);
+ if (checkTeaOwner(stack, Minecraft.getMinecraft().thePlayer.getCommandSenderName())) {
+ return getUltimateTeaDisplayName(super.getDisplayName(stack));
+ }
+ return EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + "" + EnumChatFormatting.ITALIC + "???????";
+ }
+
+ @Override
+ public ModularWindow createWindow(ItemStack stack, EntityPlayer player) {
+ ModularWindow.Builder builder = ModularWindow.builder(200, 150);
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ final TeaNetwork teaNetwork = TeaNetwork.getNetwork(player.getPersistentID());
+ IDrawable tab1 = new ItemDrawable(ItemList.LegendaryUltimateTea.get(1)).withFixedSize(18, 18, 4, 6);
+ IDrawable tab2 = new ItemDrawable(new ItemStack(Blocks.crafting_table)).withFixedSize(18, 18, 4, 6);
+ IDrawable tab3 = new ItemDrawable(new ItemStack(Items.golden_apple)).withFixedSize(18, 18, 4, 6);
+ AtomicReference<BigInteger> teaAmount = new AtomicReference<>(BigInteger.ZERO);
+ AtomicReference<BigInteger> teaLimit = new AtomicReference<>(BigInteger.ZERO);
+ builder.widget(
+ new TabContainer().setButtonSize(28, 32)
+ .addTabButton(
+ new TabButton(0)
+ .setBackground(false, ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f), tab1)
+ .setBackground(true, ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f), tab1)
+ .setPos(0, -28))
+ .addTabButton(
+ new TabButton(1)
+ .setBackground(false, ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), tab2)
+ .setBackground(true, ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), tab2)
+ .setPos(28, -28))
+ .addTabButton(
+ new TabButton(2)
+ .setBackground(false, ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), tab3)
+ .setBackground(true, ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), tab3)
+ .setPos(56, -28))
+ .addPage(
+ new MultiChildWidget().addChild(
+ new TextWidget(
+ new Text("STATUS").format(EnumChatFormatting.BOLD)
+ .format(EnumChatFormatting.GOLD)
+ .shadow()).setPos(10, 5))
+ .addChild(
+ new DynamicTextWidget(
+ () -> new Text(
+ "Tea: " + (NEIClientUtils.shiftKey() ? numberFormat.format(teaAmount.get())
+ : numberFormatScientific.format(teaAmount.get()))).color(Color.GREEN.dark(3)))
+ .setSynced(false)
+ .setPos(20, 20)
+ .attachSyncer(
+ new FakeSyncWidget.BigIntegerSyncer(
+ () -> teaNetwork.teaAmount,
+ teaAmount::set),
+ builder))
+ .addChild(
+ new DynamicTextWidget(
+ () -> new Text(
+ "Tea limit: " + (NEIClientUtils.shiftKey() ? numberFormat.format(teaLimit.get())
+ : numberFormatScientific.format(teaLimit.get()))).color(Color.GREEN.dark(3)))
+ .setSynced(false)
+ .setPos(20, 30)
+ .attachSyncer(
+ new FakeSyncWidget.BigIntegerSyncer(
+ () -> teaNetwork.teaLimit,
+ teaLimit::set),
+ builder)))
+ .addPage(
+ new MultiChildWidget().addChild(
+ new TextWidget(
+ new Text("EXCHANGE").format(EnumChatFormatting.BOLD)
+ .format(EnumChatFormatting.GOLD)
+ .shadow()).setPos(10, 5))
+ .addChild(new ButtonWidget().setOnClick((Widget.ClickData clickData, Widget widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!teaNetwork.canAfford(50_000, true)) return;
+ if (player.inventory.addItemStackToInventory(ItemList.TeaAcceptorResearchNote.get(1)))
+ return;
+ player.entityDropItem(ItemList.TeaAcceptorResearchNote.get(1), 0.5f);
+ })
+ .setBackground(new ItemDrawable().setItem(ItemList.TeaAcceptorResearchNote.get(1)))
+ .addTooltip("Tea Acceptor Research Note")
+ .addTooltip(
+ new Text(
+ "Cost: " + NumberFormat.getInstance()
+ .format(50_000) + " Tea").color(Color.GRAY.normal))
+ .setPos(20, 20)))
+ .addPage(
+ new MultiChildWidget().addChild(
+ new TextWidget(
+ new Text("BENEFITS").format(EnumChatFormatting.BOLD)
+ .format(EnumChatFormatting.GOLD)
+ .shadow()).setPos(10, 5))
+ /*
+ * .addChild(new ButtonWidget() .setOnClick((Widget.ClickData clickData, Widget widget) -> { if
+ * (!(player instanceof EntityPlayerMP)) return; if (playerData == null) return;
+ * playerData.autoRegen = !playerData.autoRegen; playerData.markDirty(); }) .setBackground(new
+ * ItemDrawable().setItem(new ItemStack(Items.potionitem, 1, 8193)))
+ * .addTooltip("Regeneration I") .addTooltip("For 1 minute") .addTooltip(new Text("Cost: " +
+ * NumberFormat.getInstance().format(75_000) + " Tea") .color(Color.GRAY.normal)) //
+ * .addTooltip( //Find a way to run that on server, or different approach // new
+ * Text("Autobuy: " + (playerData == null ? "ERROR" : playerData.autoRegen)) //
+ * .color(Color.GREY.normal)) .setPos(20, 20))
+ */ ));
+ return builder.build();
+ }
+
+ @Override
+ public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer entity) {
+ if (world.isRemote) return stack;
+ if (!(entity instanceof EntityPlayerMP)) return stack;
+ if (!checkTeaOwner(stack, entity.getPersistentID())) return stack;
+ openHeldItemGUI(entity);
+ return stack;
+ }
+
+ @Override
+ public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isCurrentItem) {
+ if (world.isRemote) return;
+ if (!(entity instanceof EntityPlayerMP)) return;
+ super.onUpdate(stack, world, entity, slot, isCurrentItem);
+ if (checkTeaOwner(stack, entity.getPersistentID())) {
+ TeaNetwork teaNetwork = TeaNetwork.getNetwork(entity.getPersistentID());
+ teaNetwork.addTea(1);
+
+ /*
+ * if (playerData.autoRegen && playerData.teaAmount > 75_000) { if (((EntityPlayerMP)
+ * entity).getActivePotionEffect(Potion.regeneration) == null) { ((EntityPlayerMP)
+ * entity).addPotionEffect(new PotionEffect(Potion.regeneration.id, 1200, 0, true)); playerData.teaAmount -=
+ * 75_000; } }
+ */
+ }
+ }
+}
diff --git a/src/main/java/kubatech/nei/IMCForNEI.java b/src/main/java/kubatech/nei/IMCForNEI.java
new file mode 100644
index 0000000000..fe1eb64ebb
--- /dev/null
+++ b/src/main/java/kubatech/nei/IMCForNEI.java
@@ -0,0 +1,46 @@
+/*
+ * 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.nei;
+
+import static gregtech.api.enums.Mods.EnderIO;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import cpw.mods.fml.common.event.FMLInterModComms;
+
+public class IMCForNEI {
+
+ public static void IMCSender() {
+ if (EnderIO.isModLoaded()) sendCatalyst("mobsinfo.mobhandler", "gregtech:gt.blockmachines:14201");
+ }
+
+ private static void sendCatalyst(String aName, String aStack, int aPriority) {
+ NBTTagCompound aNBT = new NBTTagCompound();
+ aNBT.setString("handlerID", aName);
+ aNBT.setString("itemName", aStack);
+ aNBT.setInteger("priority", aPriority);
+ FMLInterModComms.sendMessage("NotEnoughItems", "registerCatalystInfo", aNBT);
+ }
+
+ private static void sendCatalyst(String aName, String aStack) {
+ sendCatalyst(aName, aStack, 0);
+ }
+}
diff --git a/src/main/java/kubatech/nei/NEI_Config.java b/src/main/java/kubatech/nei/NEI_Config.java
new file mode 100644
index 0000000000..66dadbb08c
--- /dev/null
+++ b/src/main/java/kubatech/nei/NEI_Config.java
@@ -0,0 +1,69 @@
+/*
+ * 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.nei;
+
+import static kubatech.api.enums.ItemList.LegendaryBlackTea;
+import static kubatech.api.enums.ItemList.LegendaryButterflyTea;
+import static kubatech.api.enums.ItemList.LegendaryEarlGrayTea;
+import static kubatech.api.enums.ItemList.LegendaryGreenTea;
+import static kubatech.api.enums.ItemList.LegendaryLemonTea;
+import static kubatech.api.enums.ItemList.LegendaryMilkTea;
+import static kubatech.api.enums.ItemList.LegendaryOolongTea;
+import static kubatech.api.enums.ItemList.LegendaryPeppermintTea;
+import static kubatech.api.enums.ItemList.LegendaryPuerhTea;
+import static kubatech.api.enums.ItemList.LegendaryRedTea;
+import static kubatech.api.enums.ItemList.LegendaryUltimateTea;
+import static kubatech.api.enums.ItemList.LegendaryWhiteTea;
+import static kubatech.api.enums.ItemList.LegendaryYellowTea;
+
+import codechicken.nei.api.API;
+import codechicken.nei.api.IConfigureNEI;
+import kubatech.Tags;
+
+public class NEI_Config implements IConfigureNEI {
+
+ @Override
+ public void loadConfig() {
+ API.hideItem(LegendaryBlackTea.get(1));
+ API.hideItem(LegendaryButterflyTea.get(1));
+ API.hideItem(LegendaryEarlGrayTea.get(1));
+ API.hideItem(LegendaryGreenTea.get(1));
+ API.hideItem(LegendaryLemonTea.get(1));
+ API.hideItem(LegendaryMilkTea.get(1));
+ API.hideItem(LegendaryOolongTea.get(1));
+ API.hideItem(LegendaryPeppermintTea.get(1));
+ API.hideItem(LegendaryPuerhTea.get(1));
+ API.hideItem(LegendaryRedTea.get(1));
+ API.hideItem(LegendaryWhiteTea.get(1));
+ API.hideItem(LegendaryYellowTea.get(1));
+ API.hideItem(LegendaryUltimateTea.get(1));
+ }
+
+ @Override
+ public String getName() {
+ return Tags.MODNAME + " NEI Plugin";
+ }
+
+ @Override
+ public String getVersion() {
+ return Tags.VERSION;
+ }
+}
diff --git a/src/main/java/kubatech/network/CustomTileEntityPacket.java b/src/main/java/kubatech/network/CustomTileEntityPacket.java
new file mode 100644
index 0000000000..5a3ae13903
--- /dev/null
+++ b/src/main/java/kubatech/network/CustomTileEntityPacket.java
@@ -0,0 +1,155 @@
+/*
+ * 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.network;
+
+import java.nio.charset.StandardCharsets;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.world.World;
+
+import cpw.mods.fml.common.network.NetworkRegistry;
+import cpw.mods.fml.common.network.simpleimpl.IMessage;
+import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
+import cpw.mods.fml.common.network.simpleimpl.MessageContext;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import kubatech.api.tileentity.CustomTileEntityPacketHandler;
+import kubatech.api.utils.ModUtils;
+import kubatech.kubatech;
+
+public class CustomTileEntityPacket implements IMessage {
+
+ public int w, x, y, z;
+ public final ByteBuf customdata = Unpooled.buffer();
+
+ @SuppressWarnings("unused")
+ public CustomTileEntityPacket() {}
+
+ public CustomTileEntityPacket(TileEntity te, byte[] customdata) {
+ this.w = te.getWorldObj().provider.dimensionId;
+ this.x = te.xCoord;
+ this.y = te.yCoord;
+ this.z = te.zCoord;
+ if (customdata != null && customdata.length > 0) this.customdata.writeBytes(customdata);
+ }
+
+ public void sendToAllAround(int range) {
+ kubatech.NETWORK.sendToAllAround(this, new NetworkRegistry.TargetPoint(w, x, y, z, range));
+ }
+
+ // Helper methods
+
+ public void resetHelperData() {
+ customdata.clear();
+ }
+
+ public void addData(byte[] data) {
+ customdata.writeBytes(data);
+ }
+
+ public void addData(byte data) {
+ customdata.writeByte(data);
+ }
+
+ public void addData(int data) {
+ customdata.writeInt(data);
+ }
+
+ public void addData(String data) {
+ byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
+ addData(bytes.length);
+ addData(bytes);
+ }
+
+ public void addData(boolean data) {
+ customdata.writeBoolean(data);
+ }
+
+ public void getData(byte[] bytes) {
+ customdata.readBytes(bytes);
+ }
+
+ public byte[] getData(int len) {
+ byte[] bytes = new byte[len];
+ getData(bytes);
+ return bytes;
+ }
+
+ public int getDataInt() {
+ return customdata.readInt();
+ }
+
+ public String getDataString() {
+ return new String(getData(getDataInt()), StandardCharsets.UTF_8);
+ }
+
+ public boolean getDataBoolean() {
+ return customdata.readBoolean();
+ }
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ w = buf.readInt();
+ x = buf.readInt();
+ y = buf.readInt();
+ z = buf.readInt();
+ customdata.clear();
+ buf.readBytes(customdata, buf.readInt());
+ }
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ buf.writeInt(w);
+ buf.writeInt(x);
+ buf.writeInt(y);
+ buf.writeInt(z);
+ buf.writeInt(customdata.readableBytes());
+ buf.writeBytes(customdata);
+ }
+
+ public static class Handler implements IMessageHandler<CustomTileEntityPacket, IMessage> {
+
+ @Override
+ public IMessage onMessage(CustomTileEntityPacket message, MessageContext ctx) {
+ if (!ModUtils.isClientThreaded()) return null;
+ Minecraft mc = Minecraft.getMinecraft();
+ if (mc == null) return null;
+ if (mc.thePlayer == null) return null;
+ World w = mc.thePlayer.getEntityWorld();
+ if (w == null) return null;
+ if (message.w != w.provider.dimensionId) return null;
+ TileEntity e = w.getTileEntity(message.x, message.y, message.z);
+ if (e == null || e.isInvalid()) return null;
+ if (e instanceof IGregTechTileEntity && !((IGregTechTileEntity) e).isInvalidTileEntity()) {
+ IMetaTileEntity mte = ((IGregTechTileEntity) e).getMetaTileEntity();
+ if (mte == null) return null;
+ if (!(mte instanceof CustomTileEntityPacketHandler)) return null;
+ ((CustomTileEntityPacketHandler) mte).HandleCustomPacket(message);
+ return null;
+ } else if (!(e instanceof CustomTileEntityPacketHandler)) return null;
+ ((CustomTileEntityPacketHandler) e).HandleCustomPacket(message);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/kubatech/network/LoadConfigPacket.java b/src/main/java/kubatech/network/LoadConfigPacket.java
new file mode 100644
index 0000000000..d479de0da3
--- /dev/null
+++ b/src/main/java/kubatech/network/LoadConfigPacket.java
@@ -0,0 +1,52 @@
+/*
+ * 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.network;
+
+import cpw.mods.fml.common.network.simpleimpl.IMessage;
+import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
+import cpw.mods.fml.common.network.simpleimpl.MessageContext;
+import io.netty.buffer.ByteBuf;
+import kubatech.config.Config;
+import kubatech.kubatech;
+
+public class LoadConfigPacket implements IMessage {
+
+ public static final LoadConfigPacket instance = new LoadConfigPacket();
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ Config.MobHandler.playerOnlyDropsModifier = buf.readDouble();
+ }
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ buf.writeDouble(Config.MobHandler.playerOnlyDropsModifier);
+ }
+
+ public static class Handler implements IMessageHandler<LoadConfigPacket, IMessage> {
+
+ @Override
+ public IMessage onMessage(LoadConfigPacket message, MessageContext ctx) {
+ kubatech.info("Received KubaTech config, parsing");
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/kubatech/savedata/PlayerData.java b/src/main/java/kubatech/savedata/PlayerData.java
new file mode 100644
index 0000000000..40a439da95
--- /dev/null
+++ b/src/main/java/kubatech/savedata/PlayerData.java
@@ -0,0 +1,49 @@
+/*
+ * 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.savedata;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.api.tea.TeaNetwork;
+
+public class PlayerData {
+
+ public String username = "";
+ public TeaNetwork teaNetwork;
+
+ PlayerData(NBTTagCompound NBTData) {
+ username = NBTData.getString("username");
+ if (NBTData.hasKey("teaNetwork")) teaNetwork = TeaNetwork.fromNBT(NBTData.getCompoundTag("teaNetwork"));
+ }
+
+ PlayerData() {}
+
+ public NBTTagCompound toNBTData() {
+ NBTTagCompound NBTData = new NBTTagCompound();
+ NBTData.setString("username", username);
+ if (teaNetwork != null) NBTData.setTag("teaNetwork", teaNetwork.toNBT());
+ return NBTData;
+ }
+
+ public void markDirty() {
+ PlayerDataManager.Instance.markDirty();
+ }
+}
diff --git a/src/main/java/kubatech/savedata/PlayerDataManager.java b/src/main/java/kubatech/savedata/PlayerDataManager.java
new file mode 100644
index 0000000000..17357a1a2a
--- /dev/null
+++ b/src/main/java/kubatech/savedata/PlayerDataManager.java
@@ -0,0 +1,116 @@
+/*
+ * 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.savedata;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldSavedData;
+import net.minecraftforge.event.world.WorldEvent;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import kubatech.api.helpers.UUIDFinder;
+
+public class PlayerDataManager extends WorldSavedData {
+
+ private static final String playerDataName = "KubaTech_PlayerData";
+ static PlayerDataManager Instance = null;
+ private final HashMap<UUID, PlayerData> players = new HashMap<>();
+
+ public static void Initialize(World world) {
+ if (Instance != null) {
+ Instance.players.clear();
+ }
+ Instance = (PlayerDataManager) world.mapStorage.loadData(PlayerDataManager.class, playerDataName);
+ if (Instance == null) {
+ Instance = new PlayerDataManager();
+ world.mapStorage.setData(playerDataName, Instance);
+ }
+ Instance.markDirty();
+ }
+
+ @SuppressWarnings("unused")
+ public PlayerDataManager(String p_i2141_1_) {
+ super(p_i2141_1_);
+ }
+
+ public PlayerDataManager() {
+ super(playerDataName);
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound NBTData) {
+ if (!NBTData.hasKey("size")) return;
+ players.clear();
+ for (int i = 0, imax = NBTData.getInteger("size"); i < imax; i++) {
+ NBTTagCompound playerNBTData = NBTData.getCompoundTag("Player." + i);
+ if (!playerNBTData.hasKey("uuid")) continue;
+ UUID uuid = UUID.fromString(playerNBTData.getString("uuid"));
+ PlayerData pData = new PlayerData(playerNBTData.getCompoundTag("data"));
+ players.put(uuid, pData);
+ if (!pData.username.isEmpty()) UUIDFinder.updateMapping(pData.username, uuid);
+ }
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound NBTData) {
+ NBTData.setInteger("size", players.size());
+ int i = 0;
+ for (Map.Entry<UUID, PlayerData> playerDataEntry : players.entrySet()) {
+ NBTTagCompound playerNBTData = new NBTTagCompound();
+ playerNBTData.setString(
+ "uuid",
+ playerDataEntry.getKey()
+ .toString());
+ playerNBTData.setTag(
+ "data",
+ playerDataEntry.getValue()
+ .toNBTData());
+ NBTData.setTag("Player." + (i++), playerNBTData);
+ }
+ }
+
+ public static PlayerData getPlayer(UUID player) {
+ if (Instance == null) return null; // probably client side
+ return Instance.players.computeIfAbsent(player, s -> new PlayerData());
+ }
+
+ public static void initializePlayer(EntityPlayerMP player) {
+ if (Instance == null) return;
+ if (!Instance.players.containsKey(player.getPersistentID())) {
+ PlayerData pData = new PlayerData();
+ pData.username = player.getCommandSenderName();
+ Instance.players.put(player.getPersistentID(), pData);
+ Instance.markDirty();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load event) {
+ if (event.world.isRemote || event.world.provider.dimensionId != 0) return;
+ Initialize(event.world);
+ }
+}
diff --git a/src/main/java/kubatech/standalone.java b/src/main/java/kubatech/standalone.java
new file mode 100644
index 0000000000..8bba56652e
--- /dev/null
+++ b/src/main/java/kubatech/standalone.java
@@ -0,0 +1,16 @@
+package kubatech;
+
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+
+public class standalone {
+
+ public static void main(String[] args) {
+ JOptionPane.showMessageDialog(
+ null,
+ "Get some TEA",
+ "TEA",
+ JOptionPane.ERROR_MESSAGE,
+ new ImageIcon(standalone.class.getResource("/assets/kubatech/textures/gui/green_tea.png")));
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/TeaAcceptorTile.java b/src/main/java/kubatech/tileentity/TeaAcceptorTile.java
new file mode 100644
index 0000000000..a8cf9f7a50
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/TeaAcceptorTile.java
@@ -0,0 +1,223 @@
+/*
+ * 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.tileentity;
+
+import static kubatech.api.Variables.numberFormat;
+import static kubatech.api.Variables.numberFormatScientific;
+
+import java.math.BigInteger;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumChatFormatting;
+
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ITileWithModularUI;
+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.UIInfo;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularUIContainer;
+import com.gtnewhorizons.modularui.common.widget.DynamicTextWidget;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import codechicken.nei.NEIClientUtils;
+import kubatech.api.enums.ItemList;
+import kubatech.api.tea.TeaNetwork;
+import kubatech.api.utils.StringUtils;
+import kubatech.loaders.ItemLoader;
+import kubatech.loaders.block.kubablock.KubaBlock;
+
+public class TeaAcceptorTile extends TileEntity
+ implements IInventory, ITileWithModularUI, KubaBlock.IModularUIProvider {
+
+ public TeaAcceptorTile() {
+ super();
+ }
+
+ private UUID tileOwner = null;
+ private TeaNetwork teaNetwork = null;
+ private long averageInput = 0L;
+ private long inAmount = 0L;
+ private int ticker = 0;
+
+ public void setTeaOwner(UUID teaOwner) {
+ if (tileOwner == null) {
+ tileOwner = teaOwner;
+ teaNetwork = TeaNetwork.getNetwork(tileOwner);
+ markDirty();
+ }
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound NBTData) {
+ super.readFromNBT(NBTData);
+ try {
+ tileOwner = UUID.fromString(NBTData.getString("tileOwner"));
+ teaNetwork = TeaNetwork.getNetwork(tileOwner);
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound NBTData) {
+ super.writeToNBT(NBTData);
+ NBTData.setString("tileOwner", tileOwner.toString());
+ }
+
+ @Override
+ public void updateEntity() {
+ if (this.worldObj.isRemote) return;
+ if (++ticker % 100 == 0) {
+ averageInput = inAmount / 100;
+ inAmount = 0;
+ }
+ }
+
+ @Override
+ public int getSizeInventory() {
+ return 10;
+ }
+
+ @Override
+ public ItemStack getStackInSlot(int p_70301_1_) {
+ return null;
+ }
+
+ @Override
+ public ItemStack decrStackSize(int p_70298_1_, int p_70298_2_) {
+ return null;
+ }
+
+ @Override
+ public ItemStack getStackInSlotOnClosing(int p_70304_1_) {
+ return null;
+ }
+
+ @Override
+ public void setInventorySlotContents(int p_70299_1_, ItemStack p_70299_2_) {
+ if (teaNetwork != null) {
+ inAmount += p_70299_2_.stackSize;
+ teaNetwork.addTea(p_70299_2_.stackSize);
+ }
+ }
+
+ @Override
+ public String getInventoryName() {
+ return "Tea acceptor";
+ }
+
+ @Override
+ public boolean hasCustomInventoryName() {
+ return false;
+ }
+
+ @Override
+ public int getInventoryStackLimit() {
+ return 64;
+ }
+
+ @Override
+ public boolean isUseableByPlayer(EntityPlayer p_70300_1_) {
+ return p_70300_1_.getPersistentID()
+ .equals(tileOwner);
+ }
+
+ @Override
+ public void openInventory() {}
+
+ @Override
+ public void closeInventory() {}
+
+ private static final int minDamage = ItemList.BlackTea.get(1)
+ .getItemDamage();
+ private static final int maxDamage = ItemList.YellowTea.get(1)
+ .getItemDamage();
+
+ @Override
+ public boolean isItemValidForSlot(int p_94041_1_, ItemStack p_94041_2_) {
+ if (teaNetwork == null) return false;
+ if (!teaNetwork.canAdd(p_94041_2_.stackSize)) return false;
+ return p_94041_2_.getItem() == ItemLoader.kubaitems && p_94041_2_.getItemDamage() >= minDamage
+ && p_94041_2_.getItemDamage() <= maxDamage;
+ }
+
+ private static final UIInfo<?, ?> UI = KubaBlock.TileEntityUIFactory.apply(ModularUIContainer::new);
+
+ @Override
+ public UIInfo<?, ?> getUI() {
+ return UI;
+ }
+
+ private static TextWidget posCenteredHorizontally(int y, TextWidget textWidget) {
+ return (TextWidget) textWidget.setPosProvider(posCenteredHorizontallyProvider.apply(textWidget, y));
+ }
+
+ private static final BiFunction<TextWidget, Integer, Widget.PosProvider> posCenteredHorizontallyProvider = (
+ TextWidget widget, Integer y) -> (Widget.PosProvider) (screenSize, window,
+ parent) -> new Pos2d((window.getSize().width / 2) - (widget.getSize().width / 2), y);
+
+ @Override
+ public ModularWindow createWindow(UIBuildContext buildContext) {
+ ModularWindow.Builder builder = ModularWindow.builder(170, 70);
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ EntityPlayer player = buildContext.getPlayer();
+ AtomicReference<BigInteger> teaAmount = new AtomicReference<>(BigInteger.ZERO);
+ builder.widgets(
+ posCenteredHorizontally(
+ 10,
+ new TextWidget(
+ new Text("Tea Acceptor").format(EnumChatFormatting.BOLD)
+ .format(EnumChatFormatting.DARK_RED))),
+ posCenteredHorizontally(30, new DynamicTextWidget(() -> {
+ if (player.getPersistentID()
+ .equals(tileOwner)) return new Text("[Tea]").color(Color.GREEN.normal);
+ else return new Text("This is not your block").color(Color.RED.normal);
+ })),
+ posCenteredHorizontally(
+ 40,
+ (TextWidget) new DynamicTextWidget(
+ () -> new Text(
+ StringUtils.applyRainbow(
+ NEIClientUtils.shiftKey() ? numberFormat.format(teaAmount.get())
+ : numberFormatScientific.format(teaAmount.get()),
+ (int) ((teaAmount.get()
+ .longValue() / Math.max(1, averageInput * 10)) % Integer.MAX_VALUE),
+ EnumChatFormatting.BOLD.toString())).shadow()).setSynced(false)
+ .attachSyncer(
+ new FakeSyncWidget.BigIntegerSyncer(() -> teaNetwork.teaAmount, teaAmount::set),
+ builder)),
+ posCenteredHorizontally(
+ 50,
+ new DynamicTextWidget(() -> new Text("IN: " + averageInput + "/t").color(Color.BLACK.normal)))
+ .addTooltip(new Text("Average input from the last 5 seconds").color(Color.GRAY.normal)));
+ return builder.build();
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/TeaStorageTile.java b/src/main/java/kubatech/tileentity/TeaStorageTile.java
new file mode 100644
index 0000000000..46278e9853
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/TeaStorageTile.java
@@ -0,0 +1,83 @@
+/*
+ * 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.tileentity;
+
+import java.math.BigInteger;
+import java.util.UUID;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+
+import kubatech.api.tea.TeaNetwork;
+
+public class TeaStorageTile extends TileEntity {
+
+ public TeaStorageTile() {
+ super();
+ }
+
+ private UUID tileOwner = null;
+ private TeaNetwork teaNetwork = null;
+
+ public void setTeaOwner(UUID teaOwner) {
+ if (tileOwner == null) {
+ tileOwner = teaOwner;
+ teaNetwork = TeaNetwork.getNetwork(tileOwner);
+ markDirty();
+ teaNetwork.registerTeaStorageExtender(this);
+ }
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound NBTData) {
+ super.readFromNBT(NBTData);
+ try {
+ tileOwner = UUID.fromString(NBTData.getString("tileOwner"));
+ teaNetwork = TeaNetwork.getNetwork(tileOwner);
+ teaNetwork.registerTeaStorageExtender(this);
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound NBTData) {
+ super.writeToNBT(NBTData);
+ NBTData.setString("tileOwner", tileOwner.toString());
+ }
+
+ @Override
+ public boolean canUpdate() {
+ return false;
+ }
+
+ public BigInteger teaExtendAmount() {
+ return BigInteger.valueOf(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onChunkUnload() {
+ if (teaNetwork != null) teaNetwork.unregisterTeaStorageExtender(this);
+ }
+
+ @Override
+ public void invalidate() {
+ if (teaNetwork != null) teaNetwork.unregisterTeaStorageExtender(this);
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_DEFusionCrafter.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_DEFusionCrafter.java
new file mode 100644
index 0000000000..f0579bf054
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_DEFusionCrafter.java
@@ -0,0 +1,281 @@
+package kubatech.tileentity.gregtech.multiblock;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlocksTiered;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.transpose;
+import static gregtech.api.enums.GT_HatchElement.Energy;
+import static gregtech.api.enums.GT_HatchElement.InputBus;
+import static gregtech.api.enums.GT_HatchElement.InputHatch;
+import static gregtech.api.enums.GT_HatchElement.Maintenance;
+import static gregtech.api.enums.GT_HatchElement.OutputBus;
+import static gregtech.api.enums.GT_HatchElement.OutputHatch;
+import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASING_MAGIC;
+import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASING_MAGIC_ACTIVE;
+import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASING_MAGIC_ACTIVE_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASING_MAGIC_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_TELEPORTER;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_TELEPORTER_ACTIVE;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_TELEPORTER_ACTIVE_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_TELEPORTER_GLOW;
+import static gregtech.api.util.GT_StructureUtility.buildHatchAdder;
+import static kubatech.api.Variables.StructureHologram;
+import static kubatech.api.Variables.buildAuthorList;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.jetbrains.annotations.NotNull;
+
+import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
+import com.gtnewhorizon.structurelib.structure.StructureDefinition;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.logic.ProcessingLogic;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_Recipe;
+import kubatech.Tags;
+import kubatech.api.implementations.KubaTechGTMultiBlockBase;
+import kubatech.loaders.BlockLoader;
+import kubatech.loaders.DEFCRecipes;
+
+public class GT_MetaTileEntity_DEFusionCrafter extends KubaTechGTMultiBlockBase<GT_MetaTileEntity_DEFusionCrafter>
+ implements ISurvivalConstructable {
+
+ private static final int CASING_INDEX = (1 << 7) + (15 + 48);
+ private int mTierCasing = 0;
+ private int mFusionTierCasing = 0;
+ private int mCasing = 0;
+
+ @SuppressWarnings("unused")
+ public GT_MetaTileEntity_DEFusionCrafter(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ }
+
+ public GT_MetaTileEntity_DEFusionCrafter(String aName) {
+ super(aName);
+ }
+
+ @Override
+ public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) {
+ return new GT_MetaTileEntity_DEFusionCrafter(mName);
+ }
+
+ private static final String STRUCTURE_PIECE_MAIN = "main";
+ private static final List<Pair<Block, Integer>> fusionCasingTiers = Arrays
+ .asList(Pair.of(GregTech_API.sBlockCasings4, 6), Pair.of(GregTech_API.sBlockCasings4, 8));
+ private static final List<Pair<Block, Integer>> coreTiers = Arrays.asList(
+ Pair.of(BlockLoader.defcCasingBlock, 8),
+ Pair.of(BlockLoader.defcCasingBlock, 9),
+ Pair.of(BlockLoader.defcCasingBlock, 10),
+ Pair.of(BlockLoader.defcCasingBlock, 11),
+ Pair.of(BlockLoader.defcCasingBlock, 12));
+ private static final IStructureDefinition<GT_MetaTileEntity_DEFusionCrafter> STRUCTURE_DEFINITION = StructureDefinition
+ .<GT_MetaTileEntity_DEFusionCrafter>builder()
+ .addShape(
+ STRUCTURE_PIECE_MAIN,
+ transpose(
+ new String[][] { // spotless:off
+ { "nnnnn", "nnnnn", "nnnnn", "nnnnn", "nnnnn" },
+ { " ", " F ", " FfF ", " F ", " " },
+ { " ", " F ", " FfF ", " F ", " " },
+ { "RRRRR", "R F R", "RFfFR", "R F R", "RRRRR" },
+ { " ", " F ", " FfF ", " F ", " " },
+ { " ", " F ", " FfF ", " F ", " " },
+ { "RRRRR", "R F R", "RFfFR", "R F R", "RRRRR" },
+ { " ", " F ", " FfF ", " F ", " " },
+ { " ", " F ", " FfF ", " F ", " " },
+ { "NN~NN", "NNNNN", "NNNNN", "NNNNN", "NNNNN" }
+ })) // spotless:on
+ .addElement(
+ 'N',
+ buildHatchAdder(GT_MetaTileEntity_DEFusionCrafter.class)
+ .atLeast(InputBus, InputHatch, OutputBus, OutputHatch, Energy, Maintenance)
+ .casingIndex(CASING_INDEX)
+ .dot(1)
+ .buildAndChain(onElementPass(e -> e.mCasing++, ofBlock(BlockLoader.defcCasingBlock, 7))))
+ .addElement('n', onElementPass(e -> e.mCasing++, ofBlock(BlockLoader.defcCasingBlock, 7)))
+ .addElement('f', ofBlock(GregTech_API.sBlockCasings4, 7))
+ .addElement('F', ofBlocksTiered((Block b, int m) -> {
+ if (b != GregTech_API.sBlockCasings4 || (m != 6 && m != 8)) return -2;
+ return m == 6 ? 1 : 2;
+ }, fusionCasingTiers, -1, (e, i) -> e.mFusionTierCasing = i, e -> e.mFusionTierCasing))
+ .addElement('R', ofBlocksTiered((Block b, int m) -> {
+ if (b != BlockLoader.defcCasingBlock || m < 8 || m > 12) return -2;
+ return m - 7;
+ }, coreTiers, -1, (e, i) -> e.mTierCasing = i, e -> e.mTierCasing))
+ .build();
+
+ @Override
+ public IStructureDefinition<GT_MetaTileEntity_DEFusionCrafter> getStructureDefinition() {
+ return STRUCTURE_DEFINITION;
+ }
+
+ public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) {
+ mCasing = 0;
+ mTierCasing = -1;
+ mFusionTierCasing = -1;
+ if (!checkPiece(STRUCTURE_PIECE_MAIN, 2, 9, 0)) return false;
+ if (mCasing < 19) return false;
+ if (mTierCasing == -2 || mFusionTierCasing == -2) return false;
+ if (mTierCasing > 3 && mFusionTierCasing < 2) return false;
+ return mMaintenanceHatches.size() == 1;
+ }
+
+ @Override
+ protected GT_Multiblock_Tooltip_Builder createTooltip() {
+ GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder();
+ tt.addMachineType("Fusion Crafter")
+ .addInfo("Controller Block for the Draconic Evolution Fusion Crafter")
+ .addInfo(buildAuthorList("kuba6000", "Prometheus0000"))
+ .addInfo("Machine can be overclocked by using casings above the recipe tier:")
+ .addInfo("Recipe time is divided by number of tiers above the recipe")
+ .addInfo("Normal EU OC still applies !")
+ .addInfo(StructureHologram)
+ .addSeparator()
+ .beginStructureBlock(5, 10, 5, false)
+ .addController("Front bottom center")
+ .addCasingInfoMin("Naquadah Alloy Fusion Casing", 19, false)
+ .addOtherStructurePart("Fusion Coil Block", "Center pillar")
+ .addOtherStructurePart("Fusion Machine Casing", "Touching Fusion Coil Block at every side")
+ .addOtherStructurePart("Tiered Fusion Casing", "Rings (5x5 hollow) at layer 4 and 7")
+ .addStructureInfo("Bloody Ichorium for tier 1, Draconium for tier 2, etc")
+ .addStructureInfo("To use tier 3 + you have to use fusion casing MK II")
+ .addInputBus("Any bottom casing", 1)
+ .addInputHatch("Any bottom casing", 1)
+ .addOutputBus("Any bottom casing", 1)
+ .addOutputHatch("Any bottom casing", 1)
+ .addEnergyHatch("Any bottom casing", 1)
+ .addMaintenanceHatch("Any bottom casing", 1)
+ .toolTipFinisher(Tags.MODNAME);
+ return tt;
+ }
+
+ public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection facing,
+ int colorIndex, boolean aActive, boolean aRedstone) {
+ if (side == facing) {
+ if (aActive) return new ITexture[] { TextureFactory.of(MACHINE_CASING_MAGIC), TextureFactory.builder()
+ .addIcon(OVERLAY_TELEPORTER_ACTIVE)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_TELEPORTER_ACTIVE_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ return new ITexture[] { TextureFactory.of(MACHINE_CASING_MAGIC), TextureFactory.builder()
+ .addIcon(OVERLAY_TELEPORTER)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_TELEPORTER_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ }
+ if (aActive) return new ITexture[] { TextureFactory.of(MACHINE_CASING_MAGIC), TextureFactory.builder()
+ .addIcon(MACHINE_CASING_MAGIC_ACTIVE)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(MACHINE_CASING_MAGIC_ACTIVE_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ return new ITexture[] { TextureFactory.of(MACHINE_CASING_MAGIC), TextureFactory.builder()
+ .addIcon(MACHINE_CASING_MAGIC)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(MACHINE_CASING_MAGIC_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ }
+
+ @Override
+ public boolean isCorrectMachinePart(ItemStack aStack) {
+ return true;
+ }
+
+ @Override
+ public RecipeMap<?> getRecipeMap() {
+ return DEFCRecipes.fusionCraftingRecipes;
+ }
+
+ @Override
+ protected ProcessingLogic createProcessingLogic() {
+ return new ProcessingLogic() {
+
+ @NotNull
+ @Override
+ protected CheckRecipeResult validateRecipe(@NotNull GT_Recipe recipe) {
+ return recipe.mSpecialValue <= mTierCasing ? CheckRecipeResultRegistry.SUCCESSFUL
+ : CheckRecipeResultRegistry.insufficientMachineTier(recipe.mSpecialValue);
+ }
+
+ @NotNull
+ @Override
+ protected GT_OverclockCalculator createOverclockCalculator(@NotNull GT_Recipe recipe) {
+ return super.createOverclockCalculator(recipe)
+ .setSpeedBoost(1f / (mTierCasing - recipe.mSpecialValue + 1));
+ }
+ };
+ }
+
+ public int getMaxEfficiency(ItemStack aStack) {
+ return 10000;
+ }
+
+ public int getPollutionPerTick(ItemStack aStack) {
+ return 0;
+ }
+
+ public int getDamageToComponent(ItemStack aStack) {
+ return 0;
+ }
+
+ public boolean explodesOnComponentBreak(ItemStack aStack) {
+ return false;
+ }
+
+ @Override
+ public void construct(ItemStack itemStack, boolean b) {
+ buildPiece(STRUCTURE_PIECE_MAIN, itemStack, b, 2, 9, 0);
+ }
+
+ @Override
+ public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
+ return survivialBuildPiece(STRUCTURE_PIECE_MAIN, stackSize, 2, 9, 0, elementBudget, env, true, true);
+ }
+
+ @Override
+ public boolean supportsVoidProtection() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsInputSeparation() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsBatchMode() {
+ return true;
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeEntityCrusher.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeEntityCrusher.java
new file mode 100644
index 0000000000..e6d7e28b4d
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeEntityCrusher.java
@@ -0,0 +1,820 @@
+/*
+ * 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.tileentity.gregtech.multiblock;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.isAir;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.transpose;
+import static gregtech.api.enums.GT_HatchElement.Energy;
+import static gregtech.api.enums.GT_HatchElement.InputBus;
+import static gregtech.api.enums.GT_HatchElement.Maintenance;
+import static gregtech.api.enums.GT_HatchElement.OutputBus;
+import static gregtech.api.enums.GT_HatchElement.OutputHatch;
+import static gregtech.api.enums.Mods.BloodMagic;
+import static gregtech.api.enums.Mods.ExtraUtilities;
+import static gregtech.api.enums.Mods.InfernalMobs;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_GLOW;
+import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY;
+import static gregtech.api.util.GT_StructureUtility.buildHatchAdder;
+import static gregtech.api.util.GT_StructureUtility.ofFrame;
+import static kubatech.api.Variables.Author;
+import static kubatech.api.Variables.StructureHologram;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.UUID;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.enchantment.EnchantmentHelper;
+import net.minecraft.entity.EnumCreatureAttribute;
+import net.minecraft.entity.SharedMonsterAttributes;
+import net.minecraft.entity.ai.attributes.AttributeModifier;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.world.EnumDifficulty;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldProviderHell;
+import net.minecraft.world.WorldServer;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.common.util.FakePlayer;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.github.bartimaeusnek.bartworks.API.BorosilicateGlass;
+import com.google.common.collect.Multimap;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
+import com.gtnewhorizon.structurelib.structure.StructureDefinition;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.common.widget.CycleButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.kuba6000.mobsinfo.api.utils.FastRandom;
+import com.mojang.authlib.GameProfile;
+
+import WayofTime.alchemicalWizardry.api.alchemy.energy.ReagentRegistry;
+import WayofTime.alchemicalWizardry.api.event.RitualRunEvent;
+import WayofTime.alchemicalWizardry.api.rituals.Rituals;
+import WayofTime.alchemicalWizardry.api.soulNetwork.SoulNetworkHandler;
+import WayofTime.alchemicalWizardry.api.tile.IBloodAltar;
+import WayofTime.alchemicalWizardry.common.rituals.RitualEffectWellOfSuffering;
+import WayofTime.alchemicalWizardry.common.tileEntity.TEMasterStone;
+import cpw.mods.fml.common.eventhandler.EventPriority;
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import crazypants.enderio.EnderIO;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.Mods;
+import gregtech.api.enums.Textures;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.recipe.check.SimpleCheckRecipeResult;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import kubatech.Tags;
+import kubatech.api.helpers.ReflectionHelper;
+import kubatech.api.implementations.KubaTechGTMultiBlockBase;
+import kubatech.api.tileentity.CustomTileEntityPacketHandler;
+import kubatech.api.utils.ModUtils;
+import kubatech.client.effect.EntityRenderer;
+import kubatech.loaders.MobHandlerLoader;
+import kubatech.network.CustomTileEntityPacket;
+
+public class GT_MetaTileEntity_ExtremeEntityCrusher
+ extends KubaTechGTMultiBlockBase<GT_MetaTileEntity_ExtremeEntityCrusher>
+ implements CustomTileEntityPacketHandler, ISurvivalConstructable {
+
+ public static final double DIAMOND_SPIKES_DAMAGE = 9d;
+ // Powered spawner with octadic capacitor spawns ~22/min ~= 0.366/sec ~= 2.72s/spawn ~= 54.54t/spawn
+ public static final int MOB_SPAWN_INTERVAL = 55;
+ public final Random rand = new FastRandom();
+ private final WeaponCache weaponCache;
+
+ @SuppressWarnings("unused")
+ public GT_MetaTileEntity_ExtremeEntityCrusher(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ weaponCache = new WeaponCache(mInventory);
+ }
+
+ public GT_MetaTileEntity_ExtremeEntityCrusher(String aName) {
+ super(aName);
+ weaponCache = new WeaponCache(mInventory);
+ if (BloodMagic.isModLoaded()) MinecraftForge.EVENT_BUS.register(this);
+ }
+
+ @Override
+ public void onRemoval() {
+ if (BloodMagic.isModLoaded()) MinecraftForge.EVENT_BUS.unregister(this);
+ if (getBaseMetaTileEntity().isClientSide() && entityRenderer != null) {
+ entityRenderer.setDead();
+ }
+ }
+
+ @Override
+ public void onUnload() {
+ if (BloodMagic.isModLoaded()) MinecraftForge.EVENT_BUS.unregister(this);
+ }
+
+ private static final String WellOfSufferingRitualName = "AW013Suffering";
+
+ private static final Item poweredSpawnerItem = Item.getItemFromBlock(EnderIO.blockPoweredSpawner);
+ private static final int CASING_INDEX = 16;
+ private static final String STRUCTURE_PIECE_MAIN = "main";
+ private static final IStructureDefinition<GT_MetaTileEntity_ExtremeEntityCrusher> STRUCTURE_DEFINITION = StructureDefinition
+ .<GT_MetaTileEntity_ExtremeEntityCrusher>builder()
+ .addShape(
+ STRUCTURE_PIECE_MAIN,
+ transpose(
+ new String[][] { // spotless:off
+ { "ccccc", "ccccc", "ccccc", "ccccc", "ccccc" },
+ { "fgggf", "g---g", "g---g", "g---g", "fgggf" },
+ { "fgggf", "g---g", "g---g", "g---g", "fgggf" },
+ { "fgggf", "g---g", "g---g", "g---g", "fgggf" },
+ { "fgggf", "g---g", "g---g", "g---g", "fgggf" },
+ { "fgggf", "gsssg", "gsssg", "gsssg", "fgggf" },
+ { "CC~CC", "CCCCC", "CCCCC", "CCCCC", "CCCCC" },
+ })) // spotless:on
+ .addElement('c', onElementPass(t -> t.mCasing++, ofBlock(GregTech_API.sBlockCasings2, 0)))
+ .addElement(
+ 'C',
+ buildHatchAdder(GT_MetaTileEntity_ExtremeEntityCrusher.class)
+ .atLeast(InputBus, OutputBus, OutputHatch, Energy, Maintenance)
+ .casingIndex(CASING_INDEX)
+ .dot(1)
+ .buildAndChain(onElementPass(t -> t.mCasing++, ofBlock(GregTech_API.sBlockCasings2, 0))))
+ .addElement('g', BorosilicateGlass.ofBoroGlass((byte) 0, (t, v) -> t.mGlassTier = v, t -> t.mGlassTier))
+ .addElement('f', ofFrame(Materials.Steel))
+ .addElement(
+ 's',
+ ExtraUtilities.isModLoaded() ? ofBlock(Block.getBlockFromName("ExtraUtilities:spike_base_diamond"), 0)
+ : isAir())
+ .build();
+
+ private TileEntity masterStoneRitual = null;
+ private TileEntity tileAltar = null;
+ private boolean isInRitualMode = false;
+ private int mCasing = 0;
+ private byte mGlassTier = 0;
+ private boolean mAnimationEnabled = true;
+ private boolean mIsProducingInfernalDrops = true;
+ private boolean voidAllDamagedAndEnchantedItems = false;
+
+ private EntityRenderer entityRenderer = null;
+ private boolean renderEntity = false;
+ public EECFakePlayer EECPlayer = null;
+
+ @Override
+ public void saveNBTData(NBTTagCompound aNBT) {
+ super.saveNBTData(aNBT);
+ aNBT.setBoolean("isInRitualMode", isInRitualMode);
+ aNBT.setBoolean("mAnimationEnabled", mAnimationEnabled);
+ aNBT.setByte("mGlassTier", mGlassTier);
+ aNBT.setBoolean("mIsProducingInfernalDrops", mIsProducingInfernalDrops);
+ aNBT.setBoolean("voidAllDamagedAndEnchantedItems", voidAllDamagedAndEnchantedItems);
+ if (weaponCache.getStackInSlot(0) != null) aNBT.setTag(
+ "weaponCache",
+ weaponCache.getStackInSlot(0)
+ .writeToNBT(new NBTTagCompound()));
+ }
+
+ @Override
+ public void loadNBTData(NBTTagCompound aNBT) {
+ super.loadNBTData(aNBT);
+ isInRitualMode = aNBT.getBoolean("isInRitualMode");
+ mAnimationEnabled = !aNBT.hasKey("mAnimationEnabled") || aNBT.getBoolean("mAnimationEnabled");
+ mGlassTier = aNBT.getByte("mGlassTier");
+ mIsProducingInfernalDrops = !aNBT.hasKey("mIsProducingInfernalDrops")
+ || aNBT.getBoolean("mIsProducingInfernalDrops");
+ voidAllDamagedAndEnchantedItems = aNBT.getBoolean("voidAllDamagedAndEnchantedItems");
+ if (aNBT.hasKey("weaponCache"))
+ weaponCache.setStackInSlot(0, ItemStack.loadItemStackFromNBT(aNBT.getCompoundTag("weaponCache")));
+ }
+
+ @Override
+ public boolean isOverclockingInfinite() {
+ return true;
+ }
+
+ @Override
+ protected int getOverclockTimeLimit() {
+ return 20;
+ }
+
+ @Override
+ public IStructureDefinition<GT_MetaTileEntity_ExtremeEntityCrusher> getStructureDefinition() {
+ return STRUCTURE_DEFINITION;
+ }
+
+ @Override
+ protected IAlignmentLimits getInitialAlignmentLimits() {
+ return (d, r, f) -> d.offsetY == 0 && r.isNotRotated();
+ }
+
+ @Override
+ protected GT_Multiblock_Tooltip_Builder createTooltip() {
+ GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder();
+ tt.addMachineType("Powered Spawner, EEC")
+ .addInfo("Controller block for the Extreme Entity Crusher")
+ .addInfo(Author)
+ .addInfo("Spawns and kills monsters for you.")
+ .addInfo("You have to insert the powered spawner in the controller.")
+ .addInfo("Base energy usage: 2,000 EU/t")
+ .addInfo("Supports perfect OC, minimum time: 20 ticks, after that multiplies the outputs.")
+ .addInfo("Recipe time is based on mob health.")
+ .addInfo("You can additionally put a weapon inside the GUI.")
+ .addInfo("It will speed up the process and apply the looting level from the weapon (maximum 4 levels).")
+ .addInfo(EnumChatFormatting.RED + "Enchanting the spikes inside the structure does nothing!")
+ .addInfo("Also produces 120 Liquid XP per operation.")
+ .addInfo("If the mob spawns infernal, it will drain 8 times more power.")
+ .addInfo("You can prevent infernal spawns by shift clicking with a screwdriver.")
+ .addInfo("Note: If the mob has forced infernal spawn, it will do it anyway.")
+ .addInfo("You can enable ritual mode with a screwdriver.")
+ .addInfo("When in ritual mode and the Well Of Suffering ritual is built directly centered on the machine,")
+ .addInfo("the mobs will start to buffer and die very slowly by the ritual.")
+ .addInfo("You can disable mob animation with a soldering iron.")
+ .addInfo(StructureHologram)
+ .addSeparator()
+ .beginStructureBlock(5, 7, 5, true)
+ .addController("Front Bottom Center")
+ .addCasingInfoMin("Solid Steel Machine Casing", 35, false)
+ .addOtherStructurePart("Tiered (HV+) Glass", "Side walls without edges or corners")
+ .addStructureInfo("The glass tier limits the Energy Hatch tier")
+ .addOtherStructurePart("Steel Frame Box", "All vertical edges without corners")
+ .addOtherStructurePart("Diamond spikes", "Inside second layer")
+ .addOutputBus("Any bottom casing", 1)
+ .addOutputHatch("Any bottom casing", 1)
+ .addEnergyHatch("Any bottom casing", 1)
+ .addMaintenanceHatch("Any bottom casing", 1)
+ .toolTipFinisher(Tags.MODNAME);
+ return tt;
+ }
+
+ @Override
+ public void construct(ItemStack itemStack, boolean b) {
+ buildPiece(STRUCTURE_PIECE_MAIN, itemStack, b, 2, 6, 0);
+ }
+
+ @Override
+ public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
+ return survivialBuildPiece(STRUCTURE_PIECE_MAIN, stackSize, 2, 6, 0, elementBudget, env, true, true);
+ }
+
+ @Override
+ public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) {
+ return new GT_MetaTileEntity_ExtremeEntityCrusher(this.mName);
+ }
+
+ @Override
+ public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection facing,
+ int colorIndex, boolean aActive, boolean aRedstone) {
+ if (side == facing) {
+ if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX), TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ }
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX) };
+ }
+
+ @SideOnly(Side.CLIENT)
+ private void setupEntityRenderer(IGregTechTileEntity aBaseMetaTileEntity, int time) {
+ if (entityRenderer == null) {
+ ChunkCoordinates coords = this.getBaseMetaTileEntity()
+ .getCoords();
+ int[] abc = new int[] { 0, -2, 2 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ this.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += coords.posX;
+ xyz[1] += coords.posY;
+ xyz[2] += coords.posZ;
+ entityRenderer = new EntityRenderer(aBaseMetaTileEntity.getWorld(), xyz[0], xyz[1], xyz[2], time);
+ } else {
+ entityRenderer.setDead();
+ entityRenderer = new EntityRenderer(entityRenderer, time);
+ }
+ Minecraft.getMinecraft().effectRenderer.addEffect(entityRenderer);
+ }
+
+ @Override
+ public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) {
+ super.onPostTick(aBaseMetaTileEntity, aTick);
+ if (aBaseMetaTileEntity.isClientSide()) {
+ if (renderEntity && aBaseMetaTileEntity.isActive() && aTick % 40 == 0) {
+ setupEntityRenderer(aBaseMetaTileEntity, 40);
+ }
+ }
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public void HandleCustomPacket(CustomTileEntityPacket message) {
+ if (message.getDataBoolean() && Mods.MobsInfo.isModLoaded()) {
+ renderEntity = true;
+ String mobType = message.getDataString();
+ MobHandlerLoader.MobEECRecipe r = MobHandlerLoader.recipeMap.get(mobType);
+ if (r != null) {
+ if (entityRenderer == null) setupEntityRenderer(getBaseMetaTileEntity(), 40);
+ entityRenderer.setEntity(r.entityCopy);
+ } else entityRenderer.setEntity(null);
+ } else {
+ renderEntity = false;
+ if (entityRenderer != null) {
+ entityRenderer.setDead();
+ entityRenderer = null;
+ }
+ }
+ }
+
+ @Override
+ public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "Can't change mode when running !");
+ return;
+ }
+ if (aPlayer.isSneaking()) {
+ if (!InfernalMobs.isModLoaded()) return;
+ mIsProducingInfernalDrops = !mIsProducingInfernalDrops;
+ if (!mIsProducingInfernalDrops)
+ GT_Utility.sendChatToPlayer(aPlayer, "Mobs will now be prevented from spawning infernal");
+ else GT_Utility.sendChatToPlayer(aPlayer, "Mobs can spawn infernal now");
+ } else {
+ if (!BloodMagic.isModLoaded()) return;
+ isInRitualMode = !isInRitualMode;
+ if (!isInRitualMode) {
+ GT_Utility.sendChatToPlayer(aPlayer, "Ritual mode disabled");
+ } else {
+ GT_Utility.sendChatToPlayer(aPlayer, "Ritual mode enabled");
+ if (connectToRitual()) GT_Utility.sendChatToPlayer(aPlayer, "Successfully connected to the ritual");
+ else GT_Utility.sendChatToPlayer(aPlayer, "Can't connect to the ritual");
+ }
+ }
+ }
+
+ @Override
+ public boolean onSolderingToolRightClick(ForgeDirection side, ForgeDirection wrenchingSide, EntityPlayer aPlayer,
+ float aX, float aY, float aZ) {
+ if (wrenchingSide == getBaseMetaTileEntity().getFrontFacing()) {
+ mAnimationEnabled = !mAnimationEnabled;
+ GT_Utility.sendChatToPlayer(aPlayer, "Animations are " + (mAnimationEnabled ? "enabled" : "disabled"));
+ return true;
+ } else return super.onSolderingToolRightClick(side, wrenchingSide, aPlayer, aX, aY, aZ);
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onRitualPerform(RitualRunEvent event) {
+ if (!isInRitualMode) return;
+ if (masterStoneRitual == null) return;
+ if (this.mMaxProgresstime == 0) return;
+ if (event.mrs.equals(masterStoneRitual) && event.ritualKey.equals(WellOfSufferingRitualName)) {
+ Rituals ritual = Rituals.ritualMap.get(WellOfSufferingRitualName);
+ if (ritual != null && ritual.effect instanceof RitualEffectWellOfSuffering) {
+ RitualEffectWellOfSuffering effect = (RitualEffectWellOfSuffering) ritual.effect;
+ event.setCanceled(true); // we will handle that
+ String owner = event.mrs.getOwner();
+ int currentEssence = SoulNetworkHandler.getCurrentEssence(owner);
+ World world = event.mrs.getWorld();
+ int x = event.mrs.getXCoord();
+ int y = event.mrs.getYCoord();
+ int z = event.mrs.getZCoord();
+
+ if (world.getWorldTime() % RitualEffectWellOfSuffering.timeDelay != 0) return;
+
+ if (tileAltar == null || tileAltar.isInvalid()) {
+ tileAltar = null;
+ for (int i = -5; i <= 5; i++) for (int j = -5; j <= 5; j++) for (int k = -10; k <= 10; k++)
+ if (world.getTileEntity(x + i, y + k, z + j) instanceof IBloodAltar)
+ tileAltar = world.getTileEntity(x + i, y + k, z + j);
+ }
+ if (tileAltar == null) return;
+
+ if (currentEssence < effect.getCostPerRefresh() * 100) {
+ SoulNetworkHandler.causeNauseaToPlayer(owner);
+ return;
+ }
+
+ ((IBloodAltar) tileAltar).sacrificialDaggerCall(
+ 100 * RitualEffectWellOfSuffering.amount
+ * (effect.canDrainReagent(
+ event.mrs,
+ ReagentRegistry.offensaReagent,
+ ReflectionHelper.getField(effect, "offensaDrain", 3),
+ true) ? 2 : 1)
+ * (effect.canDrainReagent(
+ event.mrs,
+ ReagentRegistry.tenebraeReagent,
+ ReflectionHelper.getField(effect, "tennebraeDrain", 5),
+ true) ? 2 : 1),
+ true);
+
+ SoulNetworkHandler.syphonFromNetwork(owner, effect.getCostPerRefresh() * 100);
+ }
+ }
+ }
+
+ private CustomTileEntityPacket mobPacket = null;
+
+ private static class WeaponCache extends ItemStackHandler {
+
+ boolean isValid = false;
+ int looting = 0;
+ double attackDamage = 0;
+
+ public WeaponCache(ItemStack[] inventory) {
+ super(inventory);
+ }
+
+ @Override
+ protected void onContentsChanged(int slot) {
+ if (slot != 0) return;
+ if (ModUtils.isClientThreaded()) return;
+ ItemStack stack = getStackInSlot(0);
+ if (stack == null) {
+ isValid = false;
+ return;
+ }
+ // noinspection unchecked
+ attackDamage = ((Multimap<String, AttributeModifier>) stack.getAttributeModifiers())
+ .get(SharedMonsterAttributes.attackDamage.getAttributeUnlocalizedName())
+ .stream()
+ .mapToDouble(
+ attr -> attr.getAmount()
+ + (double) EnchantmentHelper.func_152377_a(stack, EnumCreatureAttribute.UNDEFINED))
+ .sum();
+ looting = Math.min(4, EnchantmentHelper.getEnchantmentLevel(Enchantment.looting.effectId, stack));
+ isValid = true;
+ }
+
+ @Override
+ public boolean isItemValid(int slot, ItemStack stack) {
+ return Enchantment.looting.canApply(stack);
+ }
+ }
+
+ @Override
+ public boolean isValidSlot(int aIndex) {
+ return aIndex >= 0;
+ }
+
+ @SuppressWarnings("unlikely-arg-type")
+ @Override
+ @NotNull
+ public CheckRecipeResult checkProcessing() {
+ if (getBaseMetaTileEntity().isClientSide()) return CheckRecipeResultRegistry.NO_RECIPE;
+ ItemStack aStack = mInventory[1];
+ if (aStack == null) return SimpleCheckRecipeResult.ofFailure("EEC_nospawner");
+
+ if (aStack.getItem() != poweredSpawnerItem) return SimpleCheckRecipeResult.ofFailure("EEC_nospawner");
+
+ if (aStack.getTagCompound() == null) return SimpleCheckRecipeResult.ofFailure("EEC_invalidspawner");
+ String mobType = aStack.getTagCompound()
+ .getString("mobType");
+ if (mobType.isEmpty()) return SimpleCheckRecipeResult.ofFailure("EEC_invalidspawner");
+
+ if (mobType.equals("Skeleton") && getBaseMetaTileEntity().getWorld().provider instanceof WorldProviderHell
+ && rand.nextInt(5) > 0) mobType = "witherSkeleton";
+
+ MobHandlerLoader.MobEECRecipe recipe = MobHandlerLoader.recipeMap.get(mobType);
+
+ if (recipe == null) return CheckRecipeResultRegistry.NO_RECIPE;
+ if (!recipe.recipe.isPeacefulAllowed && this.getBaseMetaTileEntity()
+ .getWorld().difficultySetting == EnumDifficulty.PEACEFUL)
+ return SimpleCheckRecipeResult.ofFailure("EEC_peaceful");
+
+ if (isInRitualMode && isRitualValid()) {
+ if (getMaxInputEu() < recipe.mEUt / 4) return CheckRecipeResultRegistry.insufficientPower(recipe.mEUt / 4);
+ this.mOutputFluids = new FluidStack[] { FluidRegistry.getFluidStack("xpjuice", 5000) };
+ this.mOutputItems = recipe
+ .generateOutputs(rand, this, 3, 0, mIsProducingInfernalDrops, voidAllDamagedAndEnchantedItems);
+ this.lEUt /= 4L;
+ this.mMaxProgresstime = 400;
+ } else {
+ if (getMaxInputEu() < recipe.mEUt) return CheckRecipeResultRegistry.insufficientPower(recipe.mEUt);
+ if (recipe.recipe.alwaysinfernal && getMaxInputEu() < recipe.mEUt * 8)
+ return CheckRecipeResultRegistry.insufficientPower(recipe.mEUt * 8);
+
+ double attackDamage = DIAMOND_SPIKES_DAMAGE; // damage from spikes
+ weaponCheck: {
+ GT_MetaTileEntity_Hatch_InputBus inputbus = this.mInputBusses.size() == 0 ? null
+ : this.mInputBusses.get(0);
+ if (inputbus != null && !inputbus.isValid()) inputbus = null;
+ ItemStack lootingHolder = inputbus == null ? null : inputbus.getStackInSlot(0);
+ if (lootingHolder == null) break weaponCheck;
+ if (weaponCache.getStackInSlot(0) != null) break weaponCheck;
+ if (weaponCache.isItemValid(0, lootingHolder)) {
+ weaponCache.setStackInSlot(0, lootingHolder);
+ inputbus.setInventorySlotContents(0, null);
+ updateSlots();
+ }
+ }
+ if (weaponCache.isValid) attackDamage += weaponCache.attackDamage;
+
+ if (EECPlayer == null) EECPlayer = new EECFakePlayer(this);
+ EECPlayer.currentWeapon = weaponCache.getStackInSlot(0);
+
+ this.mOutputItems = recipe.generateOutputs(
+ rand,
+ this,
+ attackDamage,
+ weaponCache.isValid ? weaponCache.looting : 0,
+ mIsProducingInfernalDrops,
+ voidAllDamagedAndEnchantedItems);
+
+ EECPlayer.currentWeapon = null;
+
+ this.mOutputFluids = new FluidStack[] { FluidRegistry.getFluidStack("xpjuice", 120) };
+ ItemStack weapon = weaponCache.getStackInSlot(0);
+ int times = this.calculatePerfectOverclock(this.lEUt, this.mMaxProgresstime);
+ if (weaponCache.isValid && weapon.isItemStackDamageable()) {
+ EECPlayer.currentWeapon = weapon;
+ Item lootingHolderItem = weapon.getItem();
+ for (int i = 0; i < times + 1; i++) {
+ // noinspection ConstantConditions
+ if (!lootingHolderItem.hitEntity(weapon, recipe.recipe.entity, EECPlayer)) break;
+ if (weapon.stackSize == 0) {
+ weaponCache.setStackInSlot(0, null);
+ break;
+ }
+ }
+ EECPlayer.currentWeapon = null;
+ }
+ }
+ if (this.lEUt > 0) this.lEUt = -this.lEUt;
+ this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ this.mEfficiencyIncrease = 10000;
+
+ if (mobPacket == null) mobPacket = new CustomTileEntityPacket((TileEntity) this.getBaseMetaTileEntity(), null);
+ mobPacket.resetHelperData();
+ mobPacket.addData(mAnimationEnabled);
+ if (mAnimationEnabled) mobPacket.addData(mobType);
+ mobPacket.sendToAllAround(16);
+
+ this.updateSlots();
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+
+ private boolean isRitualValid() {
+ if (!isInRitualMode) return false;
+ if (masterStoneRitual == null) return false;
+ if (masterStoneRitual.isInvalid() || !(((TEMasterStone) masterStoneRitual).getCurrentRitual()
+ .equals(WellOfSufferingRitualName))) {
+ masterStoneRitual = null;
+ return false;
+ }
+ return true;
+ }
+
+ private boolean connectToRitual() {
+ if (!BloodMagic.isModLoaded()) return false;
+ ChunkCoordinates coords = this.getBaseMetaTileEntity()
+ .getCoords();
+ int[] abc = new int[] { 0, -8, 2 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ this.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += coords.posX;
+ xyz[1] += coords.posY;
+ xyz[2] += coords.posZ;
+ TileEntity te = this.getBaseMetaTileEntity()
+ .getTileEntity(xyz[0], xyz[1], xyz[2]);
+ if (te instanceof TEMasterStone) {
+ if (((TEMasterStone) te).getCurrentRitual()
+ .equals(WellOfSufferingRitualName)) {
+ masterStoneRitual = te;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) {
+ mGlassTier = 0;
+ mCasing = 0;
+ if (!checkPiece(STRUCTURE_PIECE_MAIN, 2, 6, 0)) return false;
+ if (mCasing < 35 || mMaintenanceHatches.size() != 1
+ || mEnergyHatches.size() == 0
+ || !(mInputBusses.size() == 0 || (mInputBusses.size() == 1 && mInputBusses.get(0).mTier == 0)))
+ return false;
+ if (mGlassTier < 8)
+ for (GT_MetaTileEntity_Hatch_Energy hatch : mEnergyHatches) if (hatch.mTier > mGlassTier) return false;
+ if (isInRitualMode) connectToRitual();
+ return true;
+ }
+
+ @Override
+ public String[] getInfoData() {
+ ArrayList<String> info = new ArrayList<>(Arrays.asList(super.getInfoData()));
+ info.add("Animations: " + EnumChatFormatting.YELLOW + (mAnimationEnabled ? "Enabled" : "Disabled"));
+ info.add(
+ "Is allowed to produce infernal drops: " + EnumChatFormatting.YELLOW
+ + (mIsProducingInfernalDrops ? "Yes" : "No"));
+ info.add(
+ "Void all damaged and enchanted items: " + EnumChatFormatting.YELLOW
+ + (voidAllDamagedAndEnchantedItems ? "Yes" : "No"));
+ info.add("Is in ritual mode: " + EnumChatFormatting.YELLOW + (isInRitualMode ? "Yes" : "No"));
+ if (isInRitualMode) info.add(
+ "Is connected to ritual: "
+ + (isRitualValid() ? EnumChatFormatting.GREEN + "Yes" : EnumChatFormatting.RED + "No"));
+ else {
+ info.add("Inserted weapon: " + EnumChatFormatting.YELLOW + (weaponCache.isValid ? "Yes" : "No"));
+ if (weaponCache.isValid) {
+ info.add("Weapon attack damage: " + EnumChatFormatting.YELLOW + weaponCache.attackDamage);
+ info.add("Weapon looting level: " + EnumChatFormatting.YELLOW + weaponCache.looting);
+ info.add(
+ "Total attack damage: " + EnumChatFormatting.YELLOW
+ + (DIAMOND_SPIKES_DAMAGE + weaponCache.attackDamage));
+ } else info.add("Total attack damage: " + EnumChatFormatting.YELLOW + DIAMOND_SPIKES_DAMAGE);
+ }
+ return info.toArray(new String[0]);
+ }
+
+ @Override
+ protected void addConfigurationWidgets(DynamicPositionedRow configurationElements, UIBuildContext buildContext) {
+ configurationElements.setSynced(true);
+ configurationElements.widget(new CycleButtonWidget().setToggle(() -> isInRitualMode, v -> {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Can't change mode when running !");
+ return;
+ }
+
+ isInRitualMode = v;
+
+ if (!(buildContext.getPlayer() instanceof EntityPlayerMP)) return;
+ if (!isInRitualMode) {
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Ritual mode disabled");
+ } else {
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Ritual mode enabled");
+ if (connectToRitual())
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Successfully connected to the ritual");
+ else GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Can't connect to the ritual");
+ }
+ })
+ .setTextureGetter(toggleButtonTextureGetter)
+ .setVariableBackgroundGetter(toggleButtonBackgroundGetter)
+ .setSize(16, 16)
+ .addTooltip("Ritual mode")
+ .setTooltipShowUpDelay(TOOLTIP_DELAY));
+ configurationElements.widget(new CycleButtonWidget().setToggle(() -> mIsProducingInfernalDrops, v -> {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Can't change mode when running !");
+ return;
+ }
+
+ mIsProducingInfernalDrops = v;
+
+ if (!(buildContext.getPlayer() instanceof EntityPlayerMP)) return;
+ if (!mIsProducingInfernalDrops) GT_Utility
+ .sendChatToPlayer(buildContext.getPlayer(), "Mobs will now be prevented from spawning infernal");
+ else GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Mobs can spawn infernal now");
+ })
+ .setTextureGetter(toggleButtonTextureGetter)
+ .setVariableBackgroundGetter(toggleButtonBackgroundGetter)
+ .setSize(16, 16)
+ .addTooltip("Is allowed to spawn infernal mobs")
+ .addTooltip(new Text("Does not affect mobs that are always infernal !").color(Color.GRAY.normal))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY));
+ configurationElements.widget(new CycleButtonWidget().setToggle(() -> voidAllDamagedAndEnchantedItems, v -> {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Can't change mode when running !");
+ return;
+ }
+
+ voidAllDamagedAndEnchantedItems = v;
+
+ if (!(buildContext.getPlayer() instanceof EntityPlayerMP)) return;
+ if (!voidAllDamagedAndEnchantedItems) GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Void nothing");
+ else GT_Utility.sendChatToPlayer(buildContext.getPlayer(), "Void all damaged and enchanted items");
+ })
+ .setTextureGetter(toggleButtonTextureGetter)
+ .setVariableBackgroundGetter(toggleButtonBackgroundGetter)
+ .setSize(16, 16)
+ .addTooltip("Void all damaged and enchanted items")
+ .addTooltip(
+ new Text("Does not affect infernal drops and some special drops like Sticky Sword!")
+ .color(Color.GRAY.normal))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY));
+ }
+
+ @Override
+ public void createInventorySlots() {
+ final SlotWidget spawnerSlot = new SlotWidget(inventoryHandler, 1);
+ spawnerSlot.setBackground(
+ GT_UITextures.SLOT_DARK_GRAY,
+ UITexture.fullImage(Tags.MODID, "gui/slot/gray_spawner")
+ .withFixedSize(16, 16)
+ .withOffset(1, 1));
+ spawnerSlot.setFilter(stack -> stack.getItem() == poweredSpawnerItem);
+ slotWidgets.add(spawnerSlot);
+ final SlotWidget weaponSlot = new SlotWidget(weaponCache, 0);
+ weaponSlot.setBackground(
+ GT_UITextures.SLOT_DARK_GRAY,
+ UITexture.fullImage(Tags.MODID, "gui/slot/gray_sword")
+ .withFixedSize(16, 16)
+ .withOffset(1, 1));
+ slotWidgets.add(weaponSlot);
+ }
+
+ private static class EECFakePlayer extends FakePlayer {
+
+ GT_MetaTileEntity_ExtremeEntityCrusher mte;
+ ItemStack currentWeapon;
+
+ public EECFakePlayer(GT_MetaTileEntity_ExtremeEntityCrusher mte) {
+ super(
+ (WorldServer) mte.getBaseMetaTileEntity()
+ .getWorld(),
+ new GameProfile(
+ UUID.nameUUIDFromBytes("[EEC Fake Player]".getBytes(StandardCharsets.UTF_8)),
+ "[EEC Fake Player]"));
+ this.mte = mte;
+ }
+
+ @Override
+ public void renderBrokenItemStack(ItemStack p_70669_1_) {}
+
+ @Override
+ public Random getRNG() {
+ return mte.rand;
+ }
+
+ @Override
+ public void destroyCurrentEquippedItem() {}
+
+ @Override
+ public ItemStack getCurrentEquippedItem() {
+ return currentWeapon;
+ }
+
+ @Override
+ public ItemStack getHeldItem() {
+ return currentWeapon;
+ }
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java
new file mode 100644
index 0000000000..4df5a995b5
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java
@@ -0,0 +1,1285 @@
+/*
+ * 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.tileentity.gregtech.multiblock;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.transpose;
+import static gregtech.api.enums.Mods.BartWorks;
+import static gregtech.api.enums.Mods.ProjectRedIllumination;
+import static gregtech.api.enums.Mods.RandomThings;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_GLOW;
+import static gregtech.api.util.GT_StructureUtility.ofHatchAdder;
+import static gregtech.api.util.GT_Utility.filterValidMTEs;
+import static kubatech.api.Variables.Author;
+import static kubatech.api.Variables.StructureHologram;
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.item.EntityItem;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Blocks;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.github.bartimaeusnek.bartworks.API.BorosilicateGlass;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.StructureDefinition;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.math.MainAxisAlignment;
+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.common.builder.UIInfo;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularUIContainer;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.Column;
+import com.gtnewhorizons.modularui.common.widget.CycleButtonWidget;
+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.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GTVoltageIndex;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.Textures;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_OutputBus;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.recipe.check.SimpleCheckRecipeResult;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.VoidProtectionHelper;
+import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_OutputBus_ME;
+import ic2.core.init.BlocksItems;
+import ic2.core.init.InternalName;
+import kubatech.Tags;
+import kubatech.api.EIGDynamicInventory;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.EIGMode;
+import kubatech.api.enums.EIGModes;
+import kubatech.api.implementations.KubaTechGTMultiBlockBase;
+import kubatech.client.effect.CropRenderer;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket;
+
+@SuppressWarnings("unused")
+public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
+ extends KubaTechGTMultiBlockBase<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> {
+
+ /***
+ * BALANCE OF THE IC2 MODE:
+ * (let T = EIG_BALANCE_IC2_ACCELERATOR_TIER)
+ * All IC2 crops are simulated and all drops are generated based on the real crop drops.
+ * T is a tick accelerator tier for the IC2 crops,
+ * Each crop in the EIG is accelerated using T tier accelerator
+ * (Accelerators in the game are defined as 2^T acceleration, 8*(4^T) voltage, 6 amps)
+ * IC2 mode is unlocked at T+1 tier (glass and power)
+ * And each amp of T gives one crop slot, EIG only consumes 1 AMP of a tier that it is at
+ * (EIG starts at 4 crops (T+1 tier) and each tier quadruples the amount of slots)
+ * Each crop is accelerated 2^T times
+ * Summary:
+ * Accelerators in EIG are a bit cheaper than on the crop field (4 amps instead of 6 amps)
+ * There are 4 crops touching the accelerator (1 AMP for 1 accelerated crop)
+ *
+ * Changing T one number down will buff the EIG twice, as well as changing it up will nerf the EIG twice
+ * (That is because accelerators are imperfectly scaled in game LV = 2x, MV = 4x, ...)
+ */
+ public static final int EIG_BALANCE_IC2_ACCELERATOR_TIER = GTVoltageIndex.IV;
+ public static final int EIG_BALANCE_REGULAR_MODE_MIN_TIER = GTVoltageIndex.EV;
+ public static final int EIG_BALANCE_IC2_MODE_MIN_TIER = EIG_BALANCE_IC2_ACCELERATOR_TIER + 1;
+ public static final double EIG_BALANCE_MAX_FERTILIZER_BOOST = 4.0d;
+ public static final int EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT = 1000;
+ public static final int EIG_BALANCE_WATER_USAGE_PER_SEED = 1000;
+
+ private static final Fluid WEEDEX_FLUID = Materials.WeedEX9000.mFluid;
+ private static final LinkedList<ItemStack> FERTILIZER_ITEM_LIST = new LinkedList<>();
+
+ public static void addFertilizerItem(ItemStack fertilizer) {
+ FERTILIZER_ITEM_LIST.addLast(fertilizer);
+ }
+
+ private static final boolean debug = false;
+
+ /***
+ * Changing this variable will cause ALL EIGs in the world to regenerate their drop tables.
+ */
+ private static final int NBT_REVISION = 1;
+ private static final int CONFIGURATION_WINDOW_ID = 999;
+
+ public final List<EIGBucket> buckets = new LinkedList<>();
+ public final EIGDropTable dropTracker = new EIGDropTable();
+ public Collection<EIGMigrationHolder> toMigrate;
+ public EIGDropTable guiDropTracker = new EIGDropTable();
+ private HashMap<ItemStack, Double> synchedGUIDropTracker = new HashMap<>();
+ private int maxSeedTypes = 0;
+ private int maxSeedCount = 0;
+ /**
+ * The setup phase of the EIG. 0 operation. 1 input. 2 output.
+ */
+ private int setupPhase = 1;
+ /**
+ * The amount of water used per cycle.
+ */
+ private int waterUsage = 0;
+ /**
+ * The tier of the glass on the EIG.
+ */
+ private byte glassTier = 0;
+ /**
+ * The Amount of Weed-EX used per cycle.
+ */
+ private int weedEXUsage = 0;
+ /**
+ * The mode that the EIG is in.
+ */
+ private EIGMode mode = EIGModes.Normal;
+ /**
+ * Determines whether new IC2 buckets will use no humidity for their growth speed calculation.
+ */
+ private boolean useNoHumidity = false;
+
+ public boolean isInNoHumidityMode() {
+ return this.useNoHumidity;
+ }
+
+ // region structure stuff
+
+ private int mCasing = 0;
+ private static final int CASING_INDEX = 49;
+ private static final String STRUCTURE_PIECE_MAIN = "main";
+ private static final IStructureDefinition<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> STRUCTURE_DEFINITION = StructureDefinition
+ .<GT_MetaTileEntity_ExtremeIndustrialGreenhouse>builder()
+ .addShape(
+ STRUCTURE_PIECE_MAIN,
+ transpose(
+ new String[][] { // spotless:off
+ { "ccccc", "ccccc", "ccccc", "ccccc", "ccccc" },
+ { "ccccc", "clllc", "clllc", "clllc", "ccccc" },
+ { "ggggg", "g---g", "g---g", "g---g", "ggggg" },
+ { "ggggg", "g---g", "g---g", "g---g", "ggggg" },
+ { "ccccc", "cdddc", "cdwdc", "cdddc", "ccccc" },
+ { "cc~cc", "cCCCc", "cCCCc", "cCCCc", "ccccc" },
+ })) // spotless:on
+ .addElement(
+ 'c',
+ ofChain(
+ onElementPass(t -> t.mCasing++, ofBlock(GregTech_API.sBlockCasings4, 1)),
+ ofHatchAdder(
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse::addEnergyInputToMachineList,
+ CASING_INDEX,
+ 1),
+ ofHatchAdder(
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse::addMaintenanceToMachineList,
+ CASING_INDEX,
+ 1),
+ ofHatchAdder(GT_MetaTileEntity_ExtremeIndustrialGreenhouse::addInputToMachineList, CASING_INDEX, 1),
+ ofHatchAdder(GT_MetaTileEntity_ExtremeIndustrialGreenhouse::addOutputToMachineList, CASING_INDEX, 1)))
+ .addElement('C', onElementPass(t -> t.mCasing++, ofBlock(GregTech_API.sBlockCasings4, 1)))
+ .addElement(
+ 'l',
+ ProjectRedIllumination.isModLoaded()
+ ? ofChain(
+ ofBlock(Block.getBlockFromName("ProjRed|Illumination:projectred.illumination.lamp"), 10),
+ ofBlock(Block.getBlockFromName("ProjRed|Illumination:projectred.illumination.lamp"), 26))
+ : ofChain(ofBlock(Blocks.redstone_lamp, 0), ofBlock(Blocks.lit_redstone_lamp, 0)))
+ .addElement(
+ 'g',
+ BartWorks.isModLoaded()
+ ? BorosilicateGlass
+ .ofBoroGlass((byte) 0, (byte) 1, Byte.MAX_VALUE, (te, t) -> te.glassTier = t, te -> te.glassTier)
+ : onElementPass(t -> t.glassTier = 100, ofBlock(Blocks.glass, 0)))
+ .addElement(
+ 'd',
+ ofBlock(
+ RandomThings.isModLoaded() ? Block.getBlockFromName("RandomThings:fertilizedDirt_tilled")
+ : Blocks.farmland,
+ 0))
+ .addElement(
+ 'w',
+ ofChain(ofBlock(Blocks.water, 0), ofBlock(BlocksItems.getFluidBlock(InternalName.fluidDistilledWater), 0)))
+ .build();
+
+ @Override
+ public IStructureDefinition<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> getStructureDefinition() {
+ return STRUCTURE_DEFINITION;
+ }
+
+ @Override
+ public boolean checkMachine(IGregTechTileEntity iGregTechTileEntity, ItemStack itemStack) {
+ mCasing = 0;
+ glassTier = 0;
+ if (debug) glassTier = 8;
+
+ if (!checkPiece(STRUCTURE_PIECE_MAIN, 2, 5, 0)) return false;
+
+ if (this.glassTier < 8 && !this.mEnergyHatches.isEmpty())
+ for (GT_MetaTileEntity_Hatch_Energy hatchEnergy : this.mEnergyHatches)
+ if (this.glassTier < hatchEnergy.mTier) return false;
+
+ boolean valid = this.mMaintenanceHatches.size() == 1 && !this.mEnergyHatches.isEmpty() && this.mCasing >= 70;
+
+ if (valid) this.updateSeedLimits();
+
+ return valid;
+ }
+
+ @Override
+ public void construct(ItemStack itemStack, boolean b) {
+ buildPiece(STRUCTURE_PIECE_MAIN, itemStack, b, 2, 5, 0);
+ }
+
+ @Override
+ protected IAlignmentLimits getInitialAlignmentLimits() {
+ return (d, r, f) -> d.offsetY == 0 && r.isNotRotated() && f.isNotFlipped();
+ }
+
+ // endregion structure stuff
+
+ // region tooltip
+
+ @Override
+ protected GT_Multiblock_Tooltip_Builder createTooltip() {
+ GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder();
+ String fertilizerBoostMax = String.format("%.0f", EIG_BALANCE_MAX_FERTILIZER_BOOST * 100);
+ tt.addMachineType("Crop Farm")
+ .addInfo("Controller block for the Extreme Industrial Greenhouse")
+ .addInfo(Author)
+ .addInfo("Grow your crops like a chad!")
+ .addInfo("Use screwdriver to enable/change/disable setup mode")
+ .addInfo("Use screwdriver while sneaking to enable/disable IC2 mode")
+ .addInfo("Use wire cutters to give incoming IC2 seeds 0 humidity")
+ .addInfo("Uses " + EIG_BALANCE_WATER_USAGE_PER_SEED + "L of water per seed per operation")
+ .addInfo(
+ "Uses 1L of " + new FluidStack(WEEDEX_FLUID, 1).getLocalizedName()
+ + " per operation per seed if it contains more than "
+ + EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT
+ + " seeds")
+ .addInfo("Otherwise, around 1% of seeds will be voided each operation")
+ .addInfo("You can insert fertilizer each operation to get more drops (max + " + fertilizerBoostMax + ")")
+ .addInfo("--------------------- SETUP MODE ---------------------")
+ .addInfo("Does not take power")
+ .addInfo("There are two modes: input / output")
+ .addInfo("Input mode: machine will take seeds from input bus and plant them")
+ .addInfo("[IC2] You need to also input block that is required under the crop")
+ .addInfo("Output mode: machine will take planted seeds and output them");
+ EIGModes.addTooltipInfo(tt);
+ tt.addInfo(StructureHologram)
+ .addSeparator()
+ .beginStructureBlock(5, 6, 5, false)
+ .addController("Front bottom center")
+ .addCasingInfoMin("Clean Stainless Steel Casings", 70, false)
+ .addOtherStructurePart("Borosilicate Glass", "Hollow two middle layers")
+ .addStructureInfo("The glass tier limits the Energy Input tier")
+ .addStructureInfo("The dirt is from RandomThings, must be tilled")
+ .addStructureInfo("Regular water and IC2 Distilled Water are accepted")
+ .addStructureInfo("Purple lamps are from ProjectRedIllumination. They can be powered and/or inverted")
+ .addMaintenanceHatch("Any casing (Except inner bottom ones)", 1)
+ .addInputBus("Any casing (Except inner bottom ones)", 1)
+ .addOutputBus("Any casing (Except inner bottom ones)", 1)
+ .addInputHatch("Any casing (Except inner bottom ones)", 1)
+ .addEnergyHatch("Any casing (Except inner bottom ones)", 1)
+ .toolTipFinisher(Tags.MODNAME);
+ return tt;
+ }
+
+ @Override
+ public String[] getStructureDescription(ItemStack stackSize) {
+ List<String> info = new ArrayList<>(Arrays.asList(super.getStructureDescription(stackSize)));
+ info.add("The dirt is from RandomThings, must be tilled");
+ info.add("Purple lamps are from ProjectRedIllumination. They can be powered and/or inverted");
+ return info.toArray(new String[] {});
+ }
+
+ // endregion tooltip
+
+ // region (de)constructor
+
+ public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ }
+
+ public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(String aName) {
+ super(aName);
+ }
+
+ @Override
+ public IMetaTileEntity newMetaEntity(IGregTechTileEntity iGregTechTileEntity) {
+ return new GT_MetaTileEntity_ExtremeIndustrialGreenhouse(this.mName);
+ }
+
+ @Override
+ public void onFirstTick(IGregTechTileEntity aBaseMetaTileEntity) {
+ super.onFirstTick(aBaseMetaTileEntity);
+ if (this.toMigrate != null) {
+ // Create the new buckets respectively.
+ if (this.mode == EIGModes.IC2) {
+ for (EIGMigrationHolder holder : toMigrate) {
+ // We will have to revalidate the seeds on the next cycle.
+ this.buckets
+ .add(new EIGIC2Bucket(holder.seed, holder.count, holder.supportBlock, holder.useNoHumidity));
+ }
+ } else {
+ this.mode = EIGModes.Normal;
+ for (EIGMigrationHolder holder : toMigrate) {
+ holder.seed.stackSize = holder.count;
+ EIGBucket bucket = this.mode.tryCreateNewBucket(this, holder.seed, Integer.MAX_VALUE, false);
+ if (bucket == null) {
+ // if we somehow can't grow the seed, try ejecting it at least.
+ holder.seed.stackSize = holder.count;
+ this.addOutput(holder.seed);
+ continue;
+ }
+ this.buckets.add(bucket);
+ }
+ }
+ }
+ }
+
+ /**
+ * Ejects all the seeds when the controller is broken.
+ */
+ @Override
+ public void onRemoval() {
+ super.onRemoval();
+
+ // attempt to empty all buckets
+ buckets.removeIf(this::tryEmptyBucket);
+ if (buckets.isEmpty()) return;
+
+ // attempt to drop non outputted items into the world.
+ IGregTechTileEntity mte = this.getBaseMetaTileEntity();
+ for (EIGBucket bucket : this.buckets) {
+ for (ItemStack stack : bucket.tryRemoveSeed(bucket.getSeedCount(), false)) {
+ EntityItem entityitem = new EntityItem(
+ mte.getWorld(),
+ mte.getXCoord(),
+ mte.getYCoord(),
+ mte.getZCoord(),
+ stack);
+ entityitem.delayBeforeCanPickup = 10;
+ mte.getWorld()
+ .spawnEntityInWorld(entityitem);
+ }
+ }
+ }
+
+ // endregion
+
+ // region tool interactions
+
+ /**
+ * Right click = change setup phase
+ * Shift+Right Click = change EIG Mode
+ */
+ @Override
+ public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ,
+ ItemStack aTool) {
+ if (aPlayer.isSneaking()) {
+ tryChangeMode(aPlayer);
+ } else {
+ tryChangeSetupPhase(aPlayer);
+ }
+ }
+
+ /**
+ * Right-Clicking with wire cutters toggle no hydration mode.
+ */
+ @Override
+ public boolean onWireCutterRightClick(ForgeDirection side, ForgeDirection wrenchingSide, EntityPlayer aPlayer,
+ float aX, float aY, float aZ, ItemStack aTool) {
+ this.tryChangeHumidityMode(aPlayer);
+ return true;
+ }
+
+ // endregion tool interactions
+
+ // region mode change standardisation
+
+ /**
+ * Attempts to change the setup phase of the EIG to the next mode
+ *
+ * @param aPlayer The player to notify for success and errors
+ */
+ private void tryChangeSetupPhase(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the setup phase change messages.
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't enable/disable setup if the machine is working!");
+ return;
+ }
+ this.setupPhase++;
+ if (this.setupPhase == 3) this.setupPhase = 0;
+ String phaseChangeMessage = "EIG is now running in ";
+ switch (this.setupPhase) {
+ case 0:
+ phaseChangeMessage += "operational mode.";
+ break;
+ case 1:
+ phaseChangeMessage += "seed input mode.";
+ break;
+ case 2:
+ phaseChangeMessage += "seed output mode.";
+ break;
+ default:
+ phaseChangeMessage += "an invalid mode please send us a ticket!";
+ break;
+ }
+ this.updateSeedLimits();
+ GT_Utility.sendChatToPlayer(aPlayer, phaseChangeMessage);
+ }
+
+ /**
+ * Attempts to change the mode of the EIG to the next mode.
+ *
+ * @param aPlayer The player to notify of success and errors
+ */
+ private void tryChangeMode(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the mode change messages.
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't change mode if the machine is working!");
+ return;
+ }
+ if (!this.buckets.isEmpty()) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't change mode if there are seeds inside!");
+ return;
+ }
+ this.mode = EIGModes.getNextMode(this.mode);
+ this.updateSeedLimits();
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed mode to: " + this.mode.getName());
+ }
+
+ /**
+ * Attempts to toggle the hydration mode of the EIG.
+ *
+ * @param aPlayer The player to notify for success and errors
+ */
+ private void tryChangeHumidityMode(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the humidity status interactions.
+ this.useNoHumidity = !this.useNoHumidity;
+ if (this.useNoHumidity) {
+ GT_Utility.sendChatToPlayer(aPlayer, "No Humidity mode enabled.");
+ } else {
+ GT_Utility.sendChatToPlayer(aPlayer, "No Humidity mode disabled.");
+ }
+ }
+
+ // endregion mode change standardisation
+
+ // region (de)serialisations
+
+ @Override
+ public void saveNBTData(NBTTagCompound aNBT) {
+ super.saveNBTData(aNBT);
+ aNBT.setInteger("version", NBT_REVISION);
+ aNBT.setByte("glassTier", this.glassTier);
+ aNBT.setInteger("setupPhase", this.setupPhase);
+ aNBT.setString("mode", this.mode.getName());
+ aNBT.setBoolean("isNoHumidity", this.useNoHumidity);
+ NBTTagList bucketListNBT = new NBTTagList();
+ for (EIGBucket b : this.buckets) {
+ bucketListNBT.appendTag(b.save());
+ }
+ aNBT.setTag(
+ "progress",
+ this.dropTracker.intersect(this.guiDropTracker)
+ .save());
+ aNBT.setTag("buckets", bucketListNBT);
+ }
+
+ private static class EIGMigrationHolder {
+
+ public final ItemStack seed;
+ public final ItemStack supportBlock;
+ public final boolean useNoHumidity;
+ public int count;
+ public boolean isValid = false;
+
+ public EIGMigrationHolder(NBTTagCompound nbt) {
+ this.seed = readItemStackFromNBT(nbt.getCompoundTag("input"));
+ this.count = this.seed.stackSize;
+ this.seed.stackSize = 1;
+ this.supportBlock = nbt.hasKey("undercrop", 10) ? readItemStackFromNBT(nbt.getCompoundTag("undercrop"))
+ : null;
+ this.useNoHumidity = nbt.getBoolean("noHumidity");
+ this.isValid = true;
+ }
+
+ public String getKey() {
+ if (this.supportBlock == null) return seed.toString();
+ return "(" + this.seed.toString() + "," + this.supportBlock + ")";
+ }
+
+ }
+
+ @Override
+ public void loadNBTData(NBTTagCompound aNBT) {
+ super.loadNBTData(aNBT);
+ int revision = aNBT.hasKey("version", 3) ? aNBT.getInteger("version") : 0;
+ if (revision <= 0) {
+ // migrate old EIG with greenhouse slots to new Bucker mode and fix variable names
+ this.glassTier = aNBT.getByte("glasTier");
+ this.setupPhase = aNBT.getInteger("setupphase");
+ this.mode = aNBT.getBoolean("isIC2Mode") ? EIGModes.IC2 : EIGModes.Normal;
+ this.useNoHumidity = aNBT.getBoolean("isNoHumidity");
+ // aggregate all seed types
+ HashMap<String, EIGMigrationHolder> toMigrate = new HashMap<>();
+ for (int i = 0; i < aNBT.getInteger("mStorageSize"); i++) {
+ EIGMigrationHolder holder = new EIGMigrationHolder(aNBT.getCompoundTag("mStorage." + i));
+ if (toMigrate.containsKey(holder.getKey())) {
+ toMigrate.get(holder.getKey()).count += holder.count;
+ } else {
+ toMigrate.put(holder.getKey(), holder);
+ }
+ }
+
+ this.toMigrate = toMigrate.values();
+ } else {
+ this.glassTier = aNBT.getByte("glassTier");
+ this.setupPhase = aNBT.getInteger("setupPhase");
+ this.mode = EIGModes.getModeFromName(aNBT.getString("mode"));
+ this.useNoHumidity = aNBT.getBoolean("isNoHumidity");
+ this.mode.restoreBuckets(aNBT.getTagList("buckets", 10), this.buckets);
+ new EIGDropTable(aNBT.getTagList("progress", 10)).addTo(this.dropTracker);
+ }
+ }
+
+ // endregion
+
+ // region crop visuals rendering
+
+ @SideOnly(Side.CLIENT)
+ public void spawnVisualCrops(World world, int x, int y, int z, int age) {
+ CropRenderer crop = new CropRenderer(world, x, y, z, age);
+ Minecraft.getMinecraft().effectRenderer.addEffect(crop);
+ }
+
+ @Override
+ public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) {
+ super.onPostTick(aBaseMetaTileEntity, aTick);
+ if (aBaseMetaTileEntity.isClientSide()) {
+ if (aBaseMetaTileEntity.isActive() && aTick % 40 == 0) {
+ int[] abc = new int[] { 0, -2, 2 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ this.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += aBaseMetaTileEntity.getXCoord();
+ xyz[1] += aBaseMetaTileEntity.getYCoord();
+ xyz[2] += aBaseMetaTileEntity.getZCoord();
+ spawnVisualCrops(aBaseMetaTileEntity.getWorld(), xyz[0], xyz[1], xyz[2], 40);
+ }
+ }
+ }
+
+ // endregion crop visuals rendering
+
+ /**
+ * Calculates the total amount of seeds in the EIG
+ *
+ * @return The number of seeds in the EIG.
+ */
+ private int getTotalSeedCount() {
+ // null check is to prevent a occasional weird NPE from MUI
+ return this.buckets.parallelStream()
+ .reduce(0, (b, t) -> b + t.getSeedCount(), Integer::sum);
+ }
+
+ /**
+ * Updates the max seed counts of the machine
+ */
+ private void updateSeedLimits() {
+ this.maxSeedTypes = this.mode.getSlotCount(getVoltageTier());
+ this.maxSeedCount = this.maxSeedTypes * this.mode.getSeedCapacityPerSlot();
+ }
+
+ /**
+ * Attempts to drain the multi of a given fluid, will only return true if all fluid is consumed.
+ *
+ * @param toConsume A fluid stack of the fluid to consume.
+ * @param drainPartial True to allow partial consumption.
+ * @return True when all the fluid has been consumed.
+ */
+ private boolean tryDrain(FluidStack toConsume, boolean drainPartial) {
+ // Nothing to consume = success I guess?
+ if (toConsume == null || toConsume.amount <= 0) return true;
+ // TODO: improve fluid draining logic.
+ List<FluidStack> fluids = this.getStoredFluids();
+ List<FluidStack> fluidsToUse = new ArrayList<>(fluids.size());
+ int remaining = toConsume.amount;
+ for (FluidStack fluid : fluids) {
+ if (fluid.isFluidEqual(toConsume)) {
+ remaining -= fluid.amount;
+ fluidsToUse.add(fluid);
+ if (remaining <= 0) break;
+ }
+ }
+ if (!drainPartial && remaining > 0 && !debug) return false;
+ boolean success = remaining <= 0;
+ remaining = toConsume.amount - Math.max(0, remaining);
+ for (FluidStack fluid : fluidsToUse) {
+ int used = Math.min(remaining, fluid.amount);
+ fluid.amount -= used;
+ remaining -= used;
+ }
+ return success;
+ }
+
+ /**
+ * Checks if a stack contains an item that can be used as fertilizer
+ *
+ * @param item A stack of item to validate
+ * @return True if the item can be consumed as fertilizer.
+ */
+ public static boolean isFertilizer(ItemStack item) {
+ if (item == null || item.stackSize <= 0) return false;
+ for (ItemStack fert : FERTILIZER_ITEM_LIST) {
+ if (GT_Utility.areStacksEqual(item, fert)) return true;
+ }
+ return false;
+ }
+
+ private boolean tryEmptyBucket(EIGBucket bucket) {
+ // check if it's already empty
+ if (bucket.getSeedCount() <= 0) return true;
+
+ // check if we have an ME output bus to output to.
+ for (GT_MetaTileEntity_Hatch_OutputBus tHatch : filterValidMTEs(mOutputBusses)) {
+ if (!(tHatch instanceof GT_MetaTileEntity_Hatch_OutputBus_ME)) continue;
+ for (ItemStack stack : bucket.tryRemoveSeed(bucket.getSeedCount(), false)) {
+ ((GT_MetaTileEntity_Hatch_OutputBus_ME) tHatch).store(stack);
+ }
+ return true;
+ }
+
+ // Else attempt to empty the bucket while not voiding anything.
+ ItemStack[] simulated = bucket.tryRemoveSeed(1, true);
+ VoidProtectionHelper helper = new VoidProtectionHelper().setMachine(this, true, false)
+ .setItemOutputs(simulated)
+ .setMaxParallel(bucket.getSeedCount())
+ .build();
+ if (helper.getMaxParallel() > 0) {
+ for (ItemStack toOutput : bucket.tryRemoveSeed(helper.getMaxParallel(), false)) {
+ for (GT_MetaTileEntity_Hatch_OutputBus tHatch : filterValidMTEs(mOutputBusses)) {
+ if (tHatch.storeAll(toOutput)) break;
+ }
+ }
+ }
+ return bucket.getSeedCount() <= 0;
+ }
+
+ @Override
+ @NotNull
+ public CheckRecipeResult checkProcessing() {
+ int tier = getVoltageTier();
+ updateSeedLimits();
+
+ if (setupPhase > 0) {
+ if ((buckets.size() >= maxSeedTypes && setupPhase == 1) || (buckets.isEmpty() && setupPhase == 2))
+ return CheckRecipeResultRegistry.NO_RECIPE;
+
+ if (setupPhase == 1) {
+ List<ItemStack> inputs = getStoredInputs();
+ for (ItemStack input : inputs) {
+ addCrop(input);
+ if (buckets.size() >= maxSeedTypes) break;
+ }
+ } else if (setupPhase == 2) {
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ if (tryEmptyBucket(bucket)) {
+ iterator.remove();
+ } else {
+ this.mMaxProgresstime = 20;
+ this.lEUt = 0;
+ return CheckRecipeResultRegistry.ITEM_OUTPUT_FULL;
+ }
+ }
+ }
+
+ this.updateSlots();
+ this.mMaxProgresstime = 5;
+ this.lEUt = 0;
+ this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ this.mEfficiencyIncrease = 10000;
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+ if (this.maxSeedTypes < this.buckets.size()) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_slotoverflow");
+ }
+ int seedCount = this.getTotalSeedCount();
+ if (this.maxSeedCount < seedCount) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_seedOverflow");
+ }
+
+ // Kick out bad buckets.
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ if (bucket.isValid() || bucket.revalidate(this)) continue;
+ // attempt to empty the bucket
+ tryEmptyBucket(bucket);
+ // remove empty bucket and attempt to revalidate invalid buckets
+ if (bucket.getSeedCount() <= 0) {
+ iterator.remove();
+ }
+ }
+
+ if (this.buckets.isEmpty()) return CheckRecipeResultRegistry.NO_RECIPE;
+
+ // Compute the Weed-EX and water requirements,
+ // TODO: We only really need to update water usage and WeedEX usage when adding seeds or when loading NBT.
+ this.waterUsage = seedCount * 1000;
+ this.weedEXUsage = (seedCount >= EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT ? seedCount : 0)
+ * this.mode.getWeedEXMultiplier();
+
+ // Consume water, fail if we don't have enough
+ if (!this.tryDrain(new FluidStack(FluidRegistry.WATER, this.waterUsage), false)) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_missingwater");
+ }
+
+ // Consume weed ex, if there isn't enough we consume what's there but don't fail
+ if (weedEXUsage > 0 && !this.tryDrain(new FluidStack(WEEDEX_FLUID, this.weedEXUsage), true)) {
+ IGregTechTileEntity baseMTE = this.getBaseMetaTileEntity();
+ // Cap seed murder to the Weed EX limit, no more senseless murder of bystanders
+ int killLimit = (seedCount - EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT + 1);
+ int toKill = Math.min(killLimit, baseMTE.getRandomNumber((int) ((double) seedCount * 0.02d) + 1));
+ if (toKill > 0) {
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ ItemStack[] removed = bucket.tryRemoveSeed(toKill, false);
+ if (removed == null || removed[0].stackSize <= 0) continue;
+ toKill -= removed[0].stackSize;
+ // if bucket is empty, yeet it out.
+ if (bucket.getSeedCount() <= 0) iterator.remove();
+ // if we are out of crops to kill we can just leave
+ if (toKill <= 0) break;
+ }
+ }
+ }
+
+ // OVERCLOCK
+ // FERTILIZER IDEA:
+ // IC2 +10% per fertilizer per crop per operation
+ // NORMAL +200% per fertilizer per crop per operation
+
+ int consumedFertilizer = 0;
+ int maxFertilizerToConsume = 0;
+ for (EIGBucket bucket : this.buckets)
+ maxFertilizerToConsume += bucket.getSeedCount() * this.mode.getMaxFertilizerUsagePerSeed();
+
+ ArrayList<ItemStack> inputs = getStoredInputs();
+ for (ItemStack i : inputs) {
+ if (isFertilizer(i)) {
+ int used = Math.min(i.stackSize, maxFertilizerToConsume - consumedFertilizer);
+ i.stackSize -= used;
+ consumedFertilizer += used;
+ }
+ if (consumedFertilizer == maxFertilizerToConsume) break;
+ }
+ double multiplier = 1.d
+ + (((double) consumedFertilizer / (double) maxFertilizerToConsume) * EIG_BALANCE_MAX_FERTILIZER_BOOST);
+
+ // compute drops based on the drop tracker
+ this.guiDropTracker = new EIGDropTable();
+ if (this.mode == EIGModes.IC2) {
+ if (glassTier < (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1))
+ return SimpleCheckRecipeResult.ofFailure("EIG_ic2glass");
+ this.mMaxProgresstime = 100;
+ // determine the amount of time we are simulating on the seed.
+ double timeElapsed = ((double) this.mMaxProgresstime * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER));
+ // Add drops to the drop tracker for each seed bucket.
+ for (EIGBucket bucket : this.buckets) {
+ bucket.addProgress(timeElapsed * multiplier, this.guiDropTracker);
+ }
+ } else if (this.mode == EIGModes.Normal) {
+ this.mMaxProgresstime = Math.max(20, 100 / (tier - 3)); // Min 1 s
+ for (EIGBucket bucket : this.buckets) {
+ bucket.addProgress(multiplier, this.guiDropTracker);
+ }
+ }
+
+ this.guiDropTracker.addTo(this.dropTracker, multiplier);
+ this.mOutputItems = this.dropTracker.getDrops();
+
+ // consume power
+ this.lEUt = -(long) ((double) GT_Values.V[tier] * 0.99d);
+ this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ this.mEfficiencyIncrease = 10000;
+ this.updateSlots();
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+
+ private ItemStack addCrop(ItemStack input) {
+ return addCrop(input, false) ? input : null;
+ }
+
+ /**
+ * Adds a seed to the EIG
+ *
+ * @param input The item to add to the EIG.
+ * @param simulate Set to true to not actually consume any input.
+ * @return True if all items were consumed
+ */
+ private boolean addCrop(ItemStack input, boolean simulate) {
+ // Nothing to add = success since technically nothing should have changed?
+ if (input == null || input.stackSize <= 0) return true;
+
+ // For safety's sake copy the input if we are simulating to make sure we aren't modifying it
+ if (simulate) input = input.copy();
+
+ // Cap input count to current seed max
+ int addCap = Math.min(input.stackSize, this.maxSeedCount - this.getTotalSeedCount());
+ if (addCap <= 0) return false;
+
+ // Attempt to find a compatible bucket that already exists
+ for (EIGBucket bucket : this.buckets) {
+ int consumed = bucket.tryAddSeed(this, input, addCap, simulate);
+ if (consumed <= 0) continue;
+ return input.stackSize <= 0;
+ }
+
+ // Check if we have space for a new bucket
+ if (this.maxSeedTypes <= this.buckets.size()) {
+ return false;
+ }
+
+ // try creating a new bucket, this only returns valid buckets.
+ EIGBucket bucket = this.mode.tryCreateNewBucket(this, input, addCap, simulate);
+ if (bucket == null) return false;
+ this.buckets.add(bucket);
+ return input.stackSize <= 0;
+ }
+
+ // region ui
+
+ private static final UIInfo<?, ?> GreenhouseUI = createKTMetaTileEntityUI(
+ KT_ModulaUIContainer_ExtremeIndustrialGreenhouse::new);
+
+ @Override
+ public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer) {
+ if (aBaseMetaTileEntity.isClientSide()) return true;
+ GreenhouseUI.open(
+ aPlayer,
+ aBaseMetaTileEntity.getWorld(),
+ aBaseMetaTileEntity.getXCoord(),
+ aBaseMetaTileEntity.getYCoord(),
+ aBaseMetaTileEntity.getZCoord());
+ return true;
+ }
+
+ private static class KT_ModulaUIContainer_ExtremeIndustrialGreenhouse extends ModularUIContainer {
+
+ final WeakReference<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> parent;
+
+ public KT_ModulaUIContainer_ExtremeIndustrialGreenhouse(ModularUIContext context, ModularWindow mainWindow,
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse mte) {
+ super(context, mainWindow);
+ parent = new WeakReference<>(mte);
+ }
+
+ @Override
+ public ItemStack transferStackInSlot(EntityPlayer aPlayer, int aSlotIndex) {
+ if (!(aPlayer instanceof EntityPlayerMP)) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ final Slot s = getSlot(aSlotIndex);
+ if (s == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ if (aSlotIndex >= 36) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ final ItemStack aStack = s.getStack();
+ if (aStack == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse mte = parent.get();
+ if (mte == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ // if (mte.buckets.size() >= mte.maxSeedTypes) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ if (mte.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, EnumChatFormatting.RED + "Can't insert while running !");
+ return super.transferStackInSlot(aPlayer, aSlotIndex);
+ }
+
+ mte.addCrop(aStack);
+ if (aStack.stackSize <= 0) s.putStack(null);
+ else s.putStack(aStack);
+ detectAndSendChanges();
+ return null;
+ }
+ }
+
+ @Override
+ protected void addConfigurationWidgets(DynamicPositionedRow configurationElements, UIBuildContext buildContext) {
+ buildContext.addSyncedWindow(CONFIGURATION_WINDOW_ID, this::createConfigurationWindow);
+ configurationElements.setSynced(false);
+ configurationElements.widget(
+ new ButtonWidget().setOnClick(
+ (clickData, widget) -> {
+ if (!widget.isClient()) widget.getContext()
+ .openSyncedWindow(CONFIGURATION_WINDOW_ID);
+ })
+ .setBackground(GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_BUTTON_CYCLIC)
+ .addTooltip("Configuration")
+ .setSize(16, 16));
+ }
+
+ EIGDynamicInventory<EIGBucket> dynamicInventory = new EIGDynamicInventory<>(
+ 128,
+ 60,
+ () -> this.maxSeedTypes,
+ () -> this.maxSeedCount,
+ this.buckets::size,
+ this::getTotalSeedCount,
+ this.buckets,
+ EIGBucket::getSeedStack).allowInventoryInjection(this::addCrop)
+ .allowInventoryExtraction((bucket, player) -> {
+ if (bucket == null) return null;
+ int maxRemove = bucket.getSeedStack()
+ .getMaxStackSize();
+ ItemStack[] outputs = bucket.tryRemoveSeed(maxRemove, false);
+ if (outputs == null || outputs.length <= 0) return null;
+ ItemStack ret = outputs[0];
+ for (int i = 1; i < outputs.length; i++) {
+ ItemStack suppertItem = outputs[i];
+ if (!player.inventory.addItemStackToInventory(suppertItem)) {
+ player.entityDropItem(suppertItem, 0.f);
+ } ;
+ }
+ if (bucket.getSeedCount() <= 0) this.buckets.remove(bucket);
+ return ret;
+ })
+ // TODO: re-add allow inventory replace?
+ .setEnabled(() -> this.mMaxProgresstime == 0);
+
+ @Override
+ public void createInventorySlots() {
+
+ }
+
+ private boolean isInInventory = true;
+
+ @Override
+ public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ isInInventory = !getBaseMetaTileEntity().isActive();
+ builder.widget(
+ new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
+ .setPos(4, 4)
+ .setSize(190, 85)
+ .setEnabled(w -> !isInInventory));
+ builder.widget(
+ dynamicInventory.asWidget(builder, buildContext)
+ .setPos(10, 16)
+ .setEnabled(w -> isInInventory));
+
+ builder.widget(
+ new CycleButtonWidget().setToggle(() -> isInInventory, i -> isInInventory = i)
+ .setTextureGetter(i -> i == 0 ? new Text("Inventory") : new Text("Status"))
+ .setBackground(GT_UITextures.BUTTON_STANDARD)
+ .setPos(140, 4)
+ .setSize(55, 16));
+
+ final DynamicPositionedColumn screenElements = new DynamicPositionedColumn();
+ drawTexts(screenElements, null);
+ builder.widget(screenElements.setEnabled(w -> !isInInventory));
+
+ 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 ModularWindow createConfigurationWindow(final EntityPlayer player) {
+ ModularWindow.Builder builder = ModularWindow.builder(200, 100);
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ builder.widget(
+ new DrawableWidget().setDrawable(GT_UITextures.OVERLAY_BUTTON_CYCLIC)
+ .setPos(5, 5)
+ .setSize(16, 16))
+ .widget(new TextWidget("Configuration").setPos(25, 9))
+ .widget(
+ ButtonWidget.closeWindowButton(true)
+ .setPos(185, 3))
+ .widget(
+ new Column().widget(
+ new CycleButtonWidget().setLength(3)
+ .setGetter(() -> this.setupPhase)
+ .setSetter(val -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ tryChangeSetupPhase(player);
+ })
+ .addTooltip(0, new Text("Operating").color(Color.GREEN.dark(3)))
+ .addTooltip(1, new Text("Input").color(Color.YELLOW.dark(3)))
+ .addTooltip(2, new Text("Output").color(Color.YELLOW.dark(3)))
+ .setTextureGetter(
+ i -> i == 0 ? new Text("Operating").color(Color.GREEN.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : i == 1 ? new Text("Input").color(Color.YELLOW.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : new Text("Output").color(Color.YELLOW.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0))
+ .setBackground(
+ ModularUITextures.VANILLA_BACKGROUND,
+ GT_UITextures.OVERLAY_BUTTON_CYCLIC.withFixedSize(18, 18))
+ .setSize(70, 18)
+ .addTooltip("Setup mode"))
+ .widget(
+ new CycleButtonWidget().setLength(2)
+ .setGetter(() -> this.mode.getUIIndex())
+ .setSetter(val -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ tryChangeMode(player);
+ })
+ .addTooltip(0, new Text("Disabled").color(Color.RED.dark(3)))
+ .addTooltip(1, new Text("Enabled").color(Color.GREEN.dark(3)))
+ .setTextureGetter(
+ i -> i == 0 ? new Text("Disabled").color(Color.RED.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : new Text("Enabled").color(Color.GREEN.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0))
+ .setBackground(
+ ModularUITextures.VANILLA_BACKGROUND,
+ GT_UITextures.OVERLAY_BUTTON_CYCLIC.withFixedSize(18, 18))
+ .setSize(70, 18)
+ .addTooltip("IC2 mode"))
+ .widget(
+ new CycleButtonWidget().setLength(2)
+ .setGetter(() -> useNoHumidity ? 1 : 0)
+ .setSetter(val -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ this.tryChangeHumidityMode(player);
+ })
+ .addTooltip(0, new Text("Disabled").color(Color.RED.dark(3)))
+ .addTooltip(1, new Text("Enabled").color(Color.GREEN.dark(3)))
+ .setTextureGetter(
+ i -> i == 0 ? new Text("Disabled").color(Color.RED.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : new Text("Enabled").color(Color.GREEN.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0))
+ .setBackground(
+ ModularUITextures.VANILLA_BACKGROUND,
+ GT_UITextures.OVERLAY_BUTTON_CYCLIC.withFixedSize(18, 18))
+ .setSize(70, 18)
+ .addTooltip("No Humidity mode"))
+ .setEnabled(widget -> !getBaseMetaTileEntity().isActive())
+ .setPos(10, 30))
+ .widget(
+ new Column().widget(new TextWidget("Setup mode").setSize(100, 18))
+ .widget(new TextWidget("IC2 mode").setSize(100, 18))
+ .widget(new TextWidget("No Humidity mode").setSize(100, 18))
+ .setEnabled(widget -> !getBaseMetaTileEntity().isActive())
+ .setPos(80, 30))
+ .widget(
+ new DrawableWidget().setDrawable(GT_UITextures.OVERLAY_BUTTON_CROSS)
+ .setSize(18, 18)
+ .setPos(10, 30)
+ .addTooltip(new Text("Can't change configuration when running !").color(Color.RED.dark(3)))
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive()));
+ return builder.build();
+ }
+
+ @Override
+ protected String generateCurrentRecipeInfoString() {
+ StringBuilder ret = new StringBuilder(EnumChatFormatting.WHITE + "Progress: ")
+ .append(String.format("%,.2f", (double) this.mProgresstime / 20))
+ .append("s / ")
+ .append(String.format("%,.2f", (double) this.mMaxProgresstime / 20))
+ .append("s (")
+ .append(String.format("%,.1f", (double) this.mProgresstime / this.mMaxProgresstime * 100))
+ .append("%)\n");
+
+ for (Map.Entry<ItemStack, Double> drop : this.synchedGUIDropTracker.entrySet()
+ .stream()
+ .sorted(
+ Comparator.comparing(
+ a -> a.getKey()
+ .toString()
+ .toLowerCase()))
+ .collect(Collectors.toList())) {
+ int outputSize = Arrays.stream(this.mOutputItems)
+ .filter(s -> s.isItemEqual(drop.getKey()))
+ .mapToInt(i -> i.stackSize)
+ .sum();
+ ret.append(EnumChatFormatting.AQUA)
+ .append(
+ drop.getKey()
+ .getDisplayName())
+ .append(EnumChatFormatting.WHITE)
+ .append(": ");
+ if (outputSize == 0) {
+ ret.append(String.format("%.2f", drop.getValue() * 100))
+ .append("%\n");
+ } else {
+ ret.append(EnumChatFormatting.GOLD)
+ .append(
+ String.format(
+ "x%d %s(+%.2f/sec)\n",
+ outputSize,
+ EnumChatFormatting.WHITE,
+ (double) outputSize / (mMaxProgresstime / 20)));
+ }
+ }
+ return ret.toString();
+ }
+
+ @Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+ screenElements.widget(
+ new FakeSyncWidget.BooleanSyncer(
+ () -> this.mode == EIGModes.IC2,
+ b -> this.mode = b ? EIGModes.IC2 : EIGModes.Normal));
+ screenElements.widget(new FakeSyncWidget<>(() -> {
+ HashMap<ItemStack, Double> ret = new HashMap<>();
+
+ for (Map.Entry<ItemStack, Double> drop : this.guiDropTracker.entrySet()) {
+ ret.merge(drop.getKey(), drop.getValue(), Double::sum);
+ }
+
+ return ret;
+ }, h -> this.synchedGUIDropTracker = h, (buffer, h) -> {
+ buffer.writeVarIntToBuffer(h.size());
+ for (Map.Entry<ItemStack, Double> itemStackDoubleEntry : h.entrySet()) {
+ try {
+ buffer.writeItemStackToBuffer(itemStackDoubleEntry.getKey());
+ buffer.writeDouble(itemStackDoubleEntry.getValue());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, buffer -> {
+ int len = buffer.readVarIntFromBuffer();
+ HashMap<ItemStack, Double> ret = new HashMap<>(len);
+ for (int i = 0; i < len; i++) {
+ try {
+ ret.put(buffer.readItemStackFromBuffer(), buffer.readDouble());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return ret;
+ }));
+ super.drawTexts(screenElements, inventorySlot);
+ }
+
+ @Override
+ public String[] getInfoData() {
+ List<String> info = new ArrayList<>(
+ Arrays.asList(
+ "Running in mode: " + EnumChatFormatting.GREEN
+ + (this.setupPhase == 0 ? this.mode.getName()
+ : ("Setup mode " + (this.setupPhase == 1 ? "(input)" : "(output)")))
+ + EnumChatFormatting.RESET,
+ "Uses " + waterUsage + "L/operation of water",
+ "Uses " + weedEXUsage + "L/second of Weed-EX 9000",
+ "Max slots: " + EnumChatFormatting.GREEN + this.maxSeedTypes + EnumChatFormatting.RESET,
+ "Used slots: "
+ + ((this.buckets.size() > maxSeedTypes) ? EnumChatFormatting.RED : EnumChatFormatting.GREEN)
+ + this.buckets.size()
+ + EnumChatFormatting.RESET));
+ for (EIGBucket bucket : buckets) {
+ info.add(bucket.getInfoData());
+ }
+ if (this.buckets.size() > this.maxSeedTypes) {
+ info.add(
+ EnumChatFormatting.DARK_RED + "There are too many seed types inside to run!"
+ + EnumChatFormatting.RESET);
+ }
+ if (this.getTotalSeedCount() > this.maxSeedCount) {
+ info.add(
+ EnumChatFormatting.DARK_RED + "There are too many seeds inside to run!" + EnumChatFormatting.RESET);
+ }
+ info.addAll(Arrays.asList(super.getInfoData()));
+ return info.toArray(new String[0]);
+ }
+
+ @Override
+ public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection facing,
+ int colorIndex, boolean aActive, boolean aRedstone) {
+ if (side == facing) {
+ if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX), TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ }
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX) };
+ }
+
+ // endregion ui
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_MegaIndustrialApiary.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_MegaIndustrialApiary.java
new file mode 100644
index 0000000000..aecebe8da9
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_MegaIndustrialApiary.java
@@ -0,0 +1,1175 @@
+/*
+ * 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.tileentity.gregtech.multiblock;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockAnyMeta;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlocksMap;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.transpose;
+import static forestry.api.apiculture.BeeManager.beeRoot;
+import static gregtech.api.enums.GT_HatchElement.Energy;
+import static gregtech.api.enums.GT_HatchElement.InputBus;
+import static gregtech.api.enums.GT_HatchElement.Maintenance;
+import static gregtech.api.enums.GT_HatchElement.OutputBus;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_GLOW;
+import static gregtech.api.util.GT_StructureUtility.buildHatchAdder;
+import static kubatech.api.Variables.StructureHologram;
+import static kubatech.api.Variables.buildAuthorList;
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Blocks;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.github.bartimaeusnek.bartworks.API.BorosilicateGlass;
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.IStructureElementNoPlacement;
+import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
+import com.gtnewhorizon.structurelib.structure.StructureDefinition;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.drawable.shapes.Rectangle;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.math.MainAxisAlignment;
+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.common.builder.UIInfo;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularUIContainer;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.Column;
+import com.gtnewhorizons.modularui.common.widget.CycleButtonWidget;
+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.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import forestry.api.apiculture.EnumBeeType;
+import forestry.api.apiculture.FlowerManager;
+import forestry.api.apiculture.IAlleleBeeSpecies;
+import forestry.api.apiculture.IBee;
+import forestry.api.apiculture.IBeeGenome;
+import forestry.api.apiculture.IBeeModifier;
+import forestry.api.apiculture.IBeekeepingMode;
+import forestry.apiculture.blocks.BlockAlveary;
+import forestry.apiculture.blocks.BlockApicultureType;
+import forestry.apiculture.genetics.Bee;
+import forestry.plugins.PluginApiculture;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.Textures;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.recipe.check.SimpleCheckRecipeResult;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import ic2.core.init.BlocksItems;
+import ic2.core.init.InternalName;
+import kubatech.Tags;
+import kubatech.api.DynamicInventory;
+import kubatech.api.implementations.KubaTechGTMultiBlockBase;
+import kubatech.client.effect.MegaApiaryBeesRenderer;
+
+public class GT_MetaTileEntity_MegaIndustrialApiary
+ extends KubaTechGTMultiBlockBase<GT_MetaTileEntity_MegaIndustrialApiary> implements ISurvivalConstructable {
+
+ private byte mGlassTier = 0;
+ private int mCasing = 0;
+ private int mMaxSlots = 0;
+ private int mPrimaryMode = 0;
+ private int mSecondaryMode = 0;
+ private final ArrayList<BeeSimulator> mStorage = new ArrayList<>();
+
+ private static final ItemStack royalJelly = PluginApiculture.items.royalJelly.getItemStack(1);
+ private static final int CASING_INDEX = 10;
+ private static final String STRUCTURE_PIECE_MAIN = "main";
+ private static final String STRUCTURE_PIECE_MAIN_SURVIVAL = "mainsurvival";
+ private static final int CONFIGURATION_WINDOW_ID = 999;
+ private static final int MEGA_APIARY_STORAGE_VERSION = 2;
+
+ private static final String[][] struct = transpose(
+ new String[][] { // spotless:off
+ {" "," "," "," HHH "," HHAAAHH "," HAPLPAH "," HAPAAAPAH "," HALAAALAH "," HAPAAAPAH "," HAPLPAH "," HHAAAHH "," HHH "," "," "," "},
+ {" "," "," GGG "," GGG GG "," G G "," G G "," G G "," G G "," G G "," G G "," G G "," GG GG "," GGG "," "," "},
+ {" "," HHH "," HHH HHH "," H GH "," H H "," H H "," H H "," H H "," H H "," H H "," H H "," HG GH "," HHH HHH "," HHH "," "},
+ {" GGG "," GGG GGG "," G G "," G G "," G G "," G G ","G G","G G","G G"," G G "," G G "," G G "," G G "," GGG GGG "," GGG "},
+ {" AAA "," OLA ALO "," P P "," O O "," L L "," A A ","A A","A A","A A"," A A "," L L "," O O "," P P "," OLA ALO "," AAA "},
+ {" AAAAA "," NA AO "," P P "," N O "," A A ","A A","A III A","A III A","A III A","A A"," A A "," N N "," P P "," NA AN "," AAAAA "},
+ {" AAAAA "," NA FFF AO "," PFF FFP "," NF FFO "," AF FA ","A A","AF JJJ FA","AF JKJ FA","AF JJJ FA","A A"," AF FA "," NFF FFN "," PFF FFP "," NA FFF AN "," AAAAA "},
+ {" AAA "," OLAFFFALO "," PFFFFFFFFFP "," OFFFF FFFFO "," LFF FFL "," AFF FFFFF FA ","AFF FKKKFF FFA","AFF FFKKKFF FFA","AFF FFKKKF FFA"," AF FFFFF FA "," LFF FF FFL "," OFFFF FFFO "," PFFFFFFFFFP "," OLAFFFALO "," AAA "},
+ {" G~G "," GGGBBBGGG "," GBBFFFFFBBG "," GBFFF FFBBG "," GBF FBG "," GFF FFFFF FG ","GBF FKKKFF FBG","GBF FFKJKFF FBG","GBF FFKKKF FBG"," GF FFFFF FG "," GBF FF FBG "," GBBFF FBBG "," GBBFFFFFBBG "," GGGBBBGGG "," GGG "},
+ {" HHH "," HHBBBHH "," HHBBBBBBBHH "," HBBBWWWBBBH "," HBBWWWWWWWBBH "," HBBWBBBBBWWBH ","HBBWWBBBBBBWBBH","HBBWBBBBBBBWBBH","HBBWBBBBBBWWBBH"," HBWWBBBBBWWBH "," HBBWWWBBWWBBH "," HBBBWWWWBBH "," HHBBBBBBBHH "," HHBBBHH "," HHH "},
+ {" "," GGGGG "," GGGBBBBGG "," GBBBBBBBBBG "," GBBBBBBBBBG "," GBBBBBBBBBBBG "," GBBBBBBBBBBBG "," GBBBBBBBBBBBG "," GBBBBBBBBBBBG "," GBBBBBBBBBBBG "," GBBBBBBBBBG "," GBBBBBBBBBG "," GGBBBBBGG "," GGGGG "," "},
+ {" "," HHH "," HHBBBHH "," HBBBBBBBH "," HBBBBBBBBBH "," HBBBBBBBBBH "," HBBBBBBBBBBBH "," HBBBBBBBBBBBH "," HBBBBBBBBBBBH "," HBBBBBBBBBH "," HBBBBBBBBBH "," HBBBBBBBH "," HHBBBHH "," HHH "," "},
+ {" "," "," GGG "," GGBBBGG "," GBBBBBBBG "," GBBBBBBBG "," GBBBBBBBBBG "," GBBBBBBBBBG "," GBBBBBBBBBG "," GBBBBBBBG "," GBBBBBBBG "," GGBBBGG "," GGG "," "," "},
+ {" "," "," H "," HHBHH "," HBBBBBH "," HBBBBBBBH "," HBBBBBBBH "," HBBBBBBBBBH "," HBBBBBBBH "," HBBBBBBBH "," HBBBBBH "," HHBHH "," H "," "," "},
+ {" "," "," "," G "," GGBGG "," GBBBBBG "," GBBBBBG "," GBBBBBBBG "," GBBBBBG "," GBBBBBG "," GGBGG "," G "," "," "," "},
+ {" "," "," "," "," HHH "," HHHHH "," HHBBBHH "," HHBBBHH "," HHBBBHH "," HHBHH "," HHH "," "," "," "," "},
+ {" "," "," "," "," "," "," GGG "," GHG "," GGG "," "," "," "," "," "," "}
+ }); // spotless:on
+
+ private static final IStructureDefinition<GT_MetaTileEntity_MegaIndustrialApiary> STRUCTURE_DEFINITION = StructureDefinition
+ .<GT_MetaTileEntity_MegaIndustrialApiary>builder()
+ .addShape(STRUCTURE_PIECE_MAIN, struct)
+ .addShape(
+ STRUCTURE_PIECE_MAIN_SURVIVAL,
+ Arrays.stream(struct)
+ .map(
+ sa -> Arrays.stream(sa)
+ .map(
+ s -> s.replaceAll("W", " ")
+ .replaceAll("F", " "))
+ .toArray(String[]::new))
+ .toArray(String[][]::new))
+ .addElement('A', BorosilicateGlass.ofBoroGlass((byte) 0, (t, v) -> t.mGlassTier = v, t -> t.mGlassTier))
+ .addElement('B', ofChain(ofBlockAnyMeta(Blocks.dirt, 0), ofBlock(Blocks.grass, 0)))
+ .addElement(
+ 'G',
+ buildHatchAdder(GT_MetaTileEntity_MegaIndustrialApiary.class)
+ .atLeast(InputBus, OutputBus, Energy, Maintenance)
+ .casingIndex(CASING_INDEX)
+ .dot(1)
+ .buildAndChain(onElementPass(t -> t.mCasing++, ofBlock(GregTech_API.sBlockCasings1, 10))))
+ .addElement(
+ 'H',
+ ofBlocksMap(
+ Collections.singletonMap(
+ Blocks.planks,
+ IntStream.rangeClosed(0, 5)
+ .boxed()
+ .collect(Collectors.toList())),
+ Blocks.planks,
+ 5))
+ .addElement(
+ 'I',
+ ofBlocksMap(
+ Collections.singletonMap(
+ Blocks.wooden_slab,
+ IntStream.rangeClosed(0, 5)
+ .boxed()
+ .collect(Collectors.toList())),
+ Blocks.wooden_slab,
+ 5))
+ .addElement('J', ofBlock(PluginApiculture.blocks.apiculture, BlockApicultureType.APIARY.getMeta()))
+ .addElement('K', ofBlock(PluginApiculture.blocks.alveary, BlockAlveary.Type.PLAIN.ordinal()))
+ .addElement('L', ofBlock(PluginApiculture.blocks.alveary, BlockAlveary.Type.HYGRO.ordinal()))
+ .addElement('N', ofBlock(PluginApiculture.blocks.alveary, BlockAlveary.Type.STABILIZER.ordinal()))
+ .addElement('O', ofBlock(PluginApiculture.blocks.alveary, BlockAlveary.Type.HEATER.ordinal()))
+ .addElement('P', ofBlock(PluginApiculture.blocks.alveary, BlockAlveary.Type.FAN.ordinal()))
+ .addElement(
+ 'W',
+ ofChain(ofBlock(Blocks.water, 0), ofBlock(BlocksItems.getFluidBlock(InternalName.fluidDistilledWater), 0)))
+ .addElement('F', new IStructureElementNoPlacement<GT_MetaTileEntity_MegaIndustrialApiary>() {
+
+ @Override
+ public boolean check(GT_MetaTileEntity_MegaIndustrialApiary mte, World world, int x, int y, int z) {
+ mte.flowerCheck(world, x, y, z);
+ return true;
+ }
+
+ @Override
+ public boolean spawnHint(GT_MetaTileEntity_MegaIndustrialApiary mte, World world, int x, int y, int z,
+ ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 2 - 1);
+ return true;
+ }
+ })
+ .build();
+
+ @SuppressWarnings("unused")
+ public GT_MetaTileEntity_MegaIndustrialApiary(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ }
+
+ public GT_MetaTileEntity_MegaIndustrialApiary(String aName) {
+ super(aName);
+ }
+
+ @Override
+ public void onRemoval() {
+ super.onRemoval();
+ if (getBaseMetaTileEntity().isServerSide())
+ tryOutputAll(mStorage, s -> Collections.singletonList(((BeeSimulator) s).queenStack));
+ }
+
+ private boolean isCacheDirty = true;
+ private final HashMap<String, String> flowersCache = new HashMap<>();
+ private final HashSet<String> flowersCheck = new HashSet<>();
+ private boolean flowersError = false;
+ private boolean needsTVarUpdate = false;
+ private int megaApiaryStorageVersion = 0;
+
+ private void flowerCheck(final World world, final int x, final int y, final int z) {
+ if (!flowersCheck.isEmpty() && !world.isAirBlock(x, y, z))
+ flowersCheck.removeIf(s -> FlowerManager.flowerRegistry.isAcceptedFlower(s, world, x, y, z));
+ }
+
+ @Override
+ public void construct(ItemStack stackSize, boolean hintsOnly) {
+ buildPiece(STRUCTURE_PIECE_MAIN, stackSize, hintsOnly, 7, 8, 0);
+ }
+
+ @Override
+ public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
+ int built = survivialBuildPiece(STRUCTURE_PIECE_MAIN_SURVIVAL, stackSize, 7, 8, 0, elementBudget, env, true);
+ if (built == -1) {
+ GT_Utility.sendChatToPlayer(
+ env.getActor(),
+ EnumChatFormatting.GREEN + "Auto placing done ! Now go place the water and flowers yourself !");
+ return 0;
+ }
+ return built;
+ }
+
+ @Override
+ public IStructureDefinition<GT_MetaTileEntity_MegaIndustrialApiary> getStructureDefinition() {
+ return STRUCTURE_DEFINITION;
+ }
+
+ @Override
+ protected IAlignmentLimits getInitialAlignmentLimits() {
+ return (d, r, f) -> d.offsetY == 0 && r.isNotRotated();
+ }
+
+ @Override
+ protected GT_Multiblock_Tooltip_Builder createTooltip() {
+ GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder();
+ tt.addMachineType("Mega Apiary")
+ .addInfo("Controller block for Industrial Apicultural Acclimatiser and Drone Domestication Station")
+ .addInfo(buildAuthorList("kuba6000", "Runakai"))
+ .addInfo("The ideal home for your bees")
+ .addInfo("Use screwdriver to change primary mode (INPUT/OUTPUT/OPERATING)")
+ .addInfo("Use screwdriver + shift to change operation mode (NORMAL/SWARMER)")
+ .addInfo("--------------------- INPUT MODE ---------------------")
+ .addInfo("- Does not take power")
+ .addInfo("- Put your queens in the input bus to put them in the internal buffer")
+ .addInfo("-------------------- OUTPUT MODE ---------------------")
+ .addInfo("- Does not take power")
+ .addInfo("- Will give your bees back to output bus")
+ .addInfo("------------------- OPERATING MODE -------------------")
+ .addInfo("- NORMAL:")
+ .addInfo(" - For each " + voltageTooltipFormatted(6) + " amp you can insert 1 bee")
+ .addInfo(" - Processing time: 5 seconds")
+ .addInfo(" - Uses 1 " + voltageTooltipFormatted(6) + " amp per queen")
+ .addInfo(" - All bees are accelerated 64 times")
+ .addInfo(" - 8 production upgrades are applied")
+ .addInfo(" - Genetic Stabilizer upgrade applied")
+ .addInfo(" - Simulates perfect environment for your bees")
+ .addInfo(" - Additionally you can provide royal jelly to increase the outputs:")
+ .addInfo(" - 1 royal jelly grants 5% bonus per bee")
+ .addInfo(" - They will be consumed on each start of operation")
+ .addInfo(" - and be applied to that operation only")
+ .addInfo(" - Max bonus: 200%")
+ .addInfo("- SWARMER:")
+ .addInfo(" - You can only insert 1 queen")
+ .addInfo(" - It will slowly produce ignoble princesses")
+ .addInfo(" - Consumes 100 royal jelly per operation")
+ .addInfo(" - Base processing time: 1 minute")
+ .addInfo(" - Uses 1 amp " + voltageTooltipFormatted(5))
+ .addInfo(" - Can overclock")
+ .addInfo(StructureHologram)
+ .addSeparator()
+ .beginStructureBlock(15, 17, 15, false)
+ .addController("Front Bottom Center")
+ .addCasingInfoMin("Bronze Plated Bricks", 190, false)
+ .addOtherStructurePart("Borosilicate Glass", "Look at the hologram")
+ .addStructureInfo("The glass tier limits the Energy Input tier")
+ .addStructureInfo("Regular water and IC2 Distilled Water are accepted")
+ .addOtherStructurePart("Flowers", "On dirt/grass", 2)
+ .addInputBus("Any casing", 1)
+ .addOutputBus("Any casing", 1)
+ .addEnergyHatch("Any casing", 1)
+ .addMaintenanceHatch("Any casing", 1)
+ .toolTipFinisher(Tags.MODNAME);
+ return tt;
+ }
+
+ @Override
+ public void saveNBTData(NBTTagCompound aNBT) {
+ super.saveNBTData(aNBT);
+ aNBT.setByte("mGlassTier", mGlassTier);
+ aNBT.setInteger("mPrimaryMode", mPrimaryMode);
+ aNBT.setInteger("mSecondaryMode", mSecondaryMode);
+ aNBT.setInteger("mStorageSize", mStorage.size());
+ for (int i = 0; i < mStorage.size(); i++) aNBT.setTag(
+ "mStorage." + i,
+ mStorage.get(i)
+ .toNBTTagCompound());
+ aNBT.setInteger("MEGA_APIARY_STORAGE_VERSION", MEGA_APIARY_STORAGE_VERSION);
+ }
+
+ @Override
+ public void loadNBTData(NBTTagCompound aNBT) {
+ super.loadNBTData(aNBT);
+ mGlassTier = aNBT.getByte("mGlassTier");
+ mPrimaryMode = aNBT.getInteger("mPrimaryMode");
+ mSecondaryMode = aNBT.getInteger("mSecondaryMode");
+ for (int i = 0, isize = aNBT.getInteger("mStorageSize"); i < isize; i++)
+ mStorage.add(new BeeSimulator(aNBT.getCompoundTag("mStorage." + i)));
+ megaApiaryStorageVersion = aNBT.getInteger("MEGA_APIARY_STORAGE_VERSION");
+ flowersCache.clear();
+ mStorage.forEach(s -> flowersCache.put(s.flowerType, s.flowerTypeDescription));
+ flowersCache.remove("");
+ isCacheDirty = false;
+ }
+
+ @Override
+ public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "Can't change mode when running !");
+ return;
+ }
+ if (!aPlayer.isSneaking()) {
+ mPrimaryMode++;
+ if (mPrimaryMode == 3) mPrimaryMode = 0;
+ switch (mPrimaryMode) {
+ case 0:
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed primary mode to: Input mode");
+ break;
+ case 1:
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed primary mode to: Output mode");
+ break;
+ case 2:
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed primary mode to: Operating mode");
+ break;
+ }
+ } else {
+ if (!mStorage.isEmpty()) {
+ GT_Utility.sendChatToPlayer(aPlayer, "Can't change operating mode when the multi is not empty !");
+ return;
+ }
+ mSecondaryMode++;
+ if (mSecondaryMode == 2) mSecondaryMode = 0;
+ switch (mSecondaryMode) {
+ case 0:
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed secondary mode to: Normal mode");
+ break;
+ case 1:
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed secondary mode to: Swarmer mode");
+ break;
+ }
+ }
+ }
+
+ private void updateMaxSlots() {
+ int mOld = mMaxSlots;
+ long v = this.getMaxInputEu();
+ if (v < GT_Values.V[6]) mMaxSlots = 0;
+ else if (mSecondaryMode == 0) mMaxSlots = (int) (v / GT_Values.V[6]);
+ else mMaxSlots = 1;
+ if (mOld != 0 && mOld != mMaxSlots) {
+ needsTVarUpdate = true;
+ }
+ }
+
+ @Override
+ public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) {
+ super.onPostTick(aBaseMetaTileEntity, aTick);
+ if (aBaseMetaTileEntity.isServerSide()) {
+ // TODO: Look for proper fix
+ if (mUpdate < -550) mUpdate = 50;
+ } else {
+ if (aBaseMetaTileEntity.isActive() && aTick % 100 == 0) {
+ int[] abc = new int[] { 0, -2, 7 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ this.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += aBaseMetaTileEntity.getXCoord();
+ xyz[1] += aBaseMetaTileEntity.getYCoord();
+ xyz[2] += aBaseMetaTileEntity.getZCoord();
+ showBees(aBaseMetaTileEntity.getWorld(), xyz[0], xyz[1], xyz[2], 100);
+ }
+ }
+ }
+
+ @SideOnly(Side.CLIENT)
+ private void showBees(World world, int x, int y, int z, int age) {
+ MegaApiaryBeesRenderer bee = new MegaApiaryBeesRenderer(world, x, y, z, age);
+ Minecraft.getMinecraft().effectRenderer.addEffect(bee);
+ }
+
+ @Override
+ @NotNull
+ public CheckRecipeResult checkProcessing() {
+ updateMaxSlots();
+ if (mPrimaryMode < 2) {
+ if (mPrimaryMode == 0 && mStorage.size() < mMaxSlots) {
+ World w = getBaseMetaTileEntity().getWorld();
+ float t = (float) getVoltageTierExact();
+ ArrayList<ItemStack> inputs = getStoredInputs();
+ for (ItemStack input : inputs) {
+ if (beeRoot.getType(input) == EnumBeeType.QUEEN) {
+ BeeSimulator bs = new BeeSimulator(input, w, t);
+ if (bs.isValid) {
+ mStorage.add(bs);
+ isCacheDirty = true;
+ }
+ }
+ if (mStorage.size() >= mMaxSlots) break;
+ }
+ updateSlots();
+ } else if (mPrimaryMode == 1 && mStorage.size() > 0) {
+ if (tryOutputAll(mStorage, s -> Collections.singletonList(((BeeSimulator) s).queenStack)))
+ isCacheDirty = true;
+ } else return CheckRecipeResultRegistry.NO_RECIPE;
+ mMaxProgresstime = 10;
+ mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ mEfficiencyIncrease = 10000;
+ lEUt = 0;
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ } else if (mPrimaryMode == 2) {
+ if (mMaxSlots > 0 && !mStorage.isEmpty()) {
+ if (mSecondaryMode == 0) {
+ if (megaApiaryStorageVersion != MEGA_APIARY_STORAGE_VERSION) {
+ megaApiaryStorageVersion = MEGA_APIARY_STORAGE_VERSION;
+ World w = getBaseMetaTileEntity().getWorld();
+ float t = (float) getVoltageTierExact();
+ mStorage.forEach(s -> s.generate(w, t));
+ }
+
+ if (mStorage.size() > mMaxSlots)
+ return SimpleCheckRecipeResult.ofFailure("MegaApiary_slotoverflow");
+
+ if (flowersError) return SimpleCheckRecipeResult.ofFailure("MegaApiary_noflowers");
+
+ if (needsTVarUpdate) {
+ float t = (float) getVoltageTierExact();
+ needsTVarUpdate = false;
+ World w = getBaseMetaTileEntity().getWorld();
+ mStorage.forEach(s -> s.updateTVar(w, t));
+ }
+
+ int maxConsume = Math.min(mStorage.size(), mMaxSlots) * 40;
+ int toConsume = maxConsume;
+ ArrayList<ItemStack> inputs = getStoredInputs();
+
+ for (ItemStack input : inputs) {
+ if (!input.isItemEqual(royalJelly)) continue;
+ int consumed = Math.min(input.stackSize, toConsume);
+ toConsume -= consumed;
+ input.stackSize -= consumed;
+ if (toConsume == 0) break;
+ }
+ double boosted = 1d;
+ if (toConsume != maxConsume) {
+ boosted += (((double) maxConsume - (double) toConsume) / (double) maxConsume) * 2d;
+ this.updateSlots();
+ }
+
+ List<ItemStack> stacks = new ArrayList<>();
+ for (int i = 0, mStorageSize = Math.min(mStorage.size(), mMaxSlots); i < mStorageSize; i++) {
+ BeeSimulator beeSimulator = mStorage.get(i);
+ stacks.addAll(beeSimulator.getDrops(this, 64_00d * boosted));
+ }
+
+ this.lEUt = -(int) ((double) GT_Values.V[6] * (double) mMaxSlots * 0.99d);
+ this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ this.mEfficiencyIncrease = 10000;
+ this.mMaxProgresstime = 100;
+ this.mOutputItems = stacks.toArray(new ItemStack[0]);
+ } else {
+ if (!depleteInput(PluginApiculture.items.royalJelly.getItemStack(64))
+ || !depleteInput(PluginApiculture.items.royalJelly.getItemStack(36))) {
+ this.updateSlots();
+ return CheckRecipeResultRegistry.NO_RECIPE;
+ }
+ calculateOverclock(GT_Values.V[5] - 2L, 1200);
+ if (this.lEUt > 0) this.lEUt = -this.lEUt;
+ this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
+ this.mEfficiencyIncrease = 10000;
+ this.mOutputItems = new ItemStack[] { this.mStorage.get(0)
+ .createIgnobleCopy() };
+ this.updateSlots();
+ }
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+ }
+
+ return CheckRecipeResultRegistry.NO_RECIPE;
+ }
+
+ @Override
+ public String[] getInfoData() {
+ ArrayList<String> info = new ArrayList<>(Arrays.asList(super.getInfoData()));
+ info.add(
+ "Running in mode: " + EnumChatFormatting.GOLD
+ + (mPrimaryMode == 0 ? "Input mode"
+ : (mPrimaryMode == 1 ? "Output mode"
+ : (mSecondaryMode == 0 ? "Operating mode (NORMAL)" : "Operating mode (SWARMER)"))));
+ info.add(
+ "Bee storage (" + EnumChatFormatting.GOLD
+ + mStorage.size()
+ + EnumChatFormatting.RESET
+ + "/"
+ + (mStorage.size() > mMaxSlots ? EnumChatFormatting.DARK_RED.toString()
+ : EnumChatFormatting.GOLD.toString())
+ + mMaxSlots
+ + EnumChatFormatting.RESET
+ + "):");
+ HashMap<String, Integer> infos = new HashMap<>();
+ for (int i = 0; i < mStorage.size(); i++) {
+ StringBuilder builder = new StringBuilder();
+ if (i > mMaxSlots) builder.append(EnumChatFormatting.DARK_RED);
+ builder.append(EnumChatFormatting.GOLD);
+ builder.append(mStorage.get(i).queenStack.getDisplayName());
+ infos.merge(builder.toString(), 1, Integer::sum);
+ }
+ infos.forEach((key, value) -> info.add("x" + value + ": " + key));
+
+ return info.toArray(new String[0]);
+ }
+
+ @Override
+ public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) {
+ mGlassTier = 0;
+ mCasing = 0;
+ if (isCacheDirty) {
+ flowersCache.clear();
+ mStorage.forEach(s -> flowersCache.put(s.flowerType, s.flowerTypeDescription));
+ flowersCache.remove("");
+ isCacheDirty = false;
+ }
+ flowersCheck.clear();
+ flowersCheck.addAll(flowersCache.keySet());
+ if (!checkPiece(STRUCTURE_PIECE_MAIN, 7, 8, 0)) return false;
+ if (this.mGlassTier < 10 && !this.mEnergyHatches.isEmpty())
+ for (GT_MetaTileEntity_Hatch_Energy hatchEnergy : this.mEnergyHatches)
+ if (this.mGlassTier < hatchEnergy.mTier) return false;
+ boolean valid = this.mMaintenanceHatches.size() == 1 && this.mEnergyHatches.size() >= 1 && this.mCasing >= 190;
+ flowersError = valid && !this.flowersCheck.isEmpty();
+ if (valid) updateMaxSlots();
+ return valid;
+ }
+
+ @Override
+ public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) {
+ return new GT_MetaTileEntity_MegaIndustrialApiary(this.mName);
+ }
+
+ @Override
+ public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection facing,
+ int colorIndex, boolean aActive, boolean aRedstone) {
+ if (side == facing) {
+ if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX), TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER)
+ .extFacing()
+ .build(),
+ TextureFactory.builder()
+ .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_GLOW)
+ .extFacing()
+ .glow()
+ .build() };
+ }
+ return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX) };
+ }
+
+ private static final UIInfo<?, ?> MegaApiaryUI = createKTMetaTileEntityUI(
+ KT_ModulaUIContainer_MegaIndustrialApiary::new);
+
+ @Override
+ public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer) {
+ if (aBaseMetaTileEntity.isClientSide()) return true;
+ MegaApiaryUI.open(
+ aPlayer,
+ aBaseMetaTileEntity.getWorld(),
+ aBaseMetaTileEntity.getXCoord(),
+ aBaseMetaTileEntity.getYCoord(),
+ aBaseMetaTileEntity.getZCoord());
+ return true;
+ }
+
+ private static class KT_ModulaUIContainer_MegaIndustrialApiary extends ModularUIContainer {
+
+ final WeakReference<GT_MetaTileEntity_MegaIndustrialApiary> parent;
+
+ public KT_ModulaUIContainer_MegaIndustrialApiary(ModularUIContext context, ModularWindow mainWindow,
+ GT_MetaTileEntity_MegaIndustrialApiary mte) {
+ super(context, mainWindow);
+ parent = new WeakReference<>(mte);
+ }
+
+ @Override
+ public ItemStack transferStackInSlot(EntityPlayer aPlayer, int aSlotIndex) {
+ if (!(aPlayer instanceof EntityPlayerMP)) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ final Slot s = getSlot(aSlotIndex);
+ if (s == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ if (aSlotIndex >= 36) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ final ItemStack aStack = s.getStack();
+ if (aStack == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ GT_MetaTileEntity_MegaIndustrialApiary mte = parent.get();
+ if (mte == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ if (mte.mStorage.size() >= mte.mMaxSlots) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ if (beeRoot.getType(aStack) == EnumBeeType.QUEEN) {
+ if (mte.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, EnumChatFormatting.RED + "Can't insert while running !");
+ return super.transferStackInSlot(aPlayer, aSlotIndex);
+ }
+ World w = mte.getBaseMetaTileEntity()
+ .getWorld();
+ float t = (float) mte.getVoltageTierExact();
+ BeeSimulator bs = new BeeSimulator(aStack, w, t);
+ if (bs.isValid) {
+ mte.mStorage.add(bs);
+ s.putStack(null);
+ detectAndSendChanges();
+ mte.isCacheDirty = true;
+ return null;
+ }
+ }
+ return super.transferStackInSlot(aPlayer, aSlotIndex);
+ }
+ }
+
+ DynamicInventory<BeeSimulator> dynamicInventory = new DynamicInventory<>(
+ 128,
+ 60,
+ () -> mMaxSlots,
+ mStorage,
+ s -> s.queenStack).allowInventoryInjection(input -> {
+ World w = getBaseMetaTileEntity().getWorld();
+ float t = (float) getVoltageTierExact();
+ BeeSimulator bs = new BeeSimulator(input, w, t);
+ if (bs.isValid) {
+ mStorage.add(bs);
+ return input;
+ }
+ return null;
+ })
+ .allowInventoryExtraction(mStorage::remove)
+ .allowInventoryReplace((i, stack) -> {
+ if (stack.stackSize != 1) return null;
+ World w = getBaseMetaTileEntity().getWorld();
+ float t = (float) getVoltageTierExact();
+ BeeSimulator bs = new BeeSimulator(stack, w, t);
+ if (bs.isValid) {
+ BeeSimulator removed = mStorage.remove(i);
+ mStorage.add(i, bs);
+ return removed.queenStack;
+ }
+ return null;
+ })
+ .setEnabled(() -> this.mMaxProgresstime == 0);
+
+ @Override
+ public void createInventorySlots() {
+
+ }
+
+ private boolean isInInventory = true;
+
+ @Override
+ public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ isInInventory = !getBaseMetaTileEntity().isActive();
+ builder.widget(
+ new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
+ .setPos(4, 4)
+ .setSize(190, 85)
+ .setEnabled(w -> !isInInventory));
+ builder.widget(
+ dynamicInventory.asWidget(builder, buildContext)
+ .setPos(10, 16)
+ .setBackground(new Rectangle().setColor(Color.rgb(163, 163, 198)))
+ .setEnabled(w -> isInInventory));
+ builder.widget(
+ new CycleButtonWidget().setToggle(() -> isInInventory, i -> isInInventory = i)
+ .setTextureGetter(i -> i == 0 ? new Text("Inventory") : new Text("Status"))
+ .setBackground(GT_UITextures.BUTTON_STANDARD)
+ .setPos(140, 4)
+ .setSize(55, 16));
+
+ final DynamicPositionedColumn screenElements = new DynamicPositionedColumn();
+ drawTexts(screenElements, null);
+ builder.widget(screenElements.setEnabled(w -> !isInInventory));
+
+ 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)));
+ }
+
+ @Override
+ protected void addConfigurationWidgets(DynamicPositionedRow configurationElements, UIBuildContext buildContext) {
+ buildContext.addSyncedWindow(CONFIGURATION_WINDOW_ID, this::createConfigurationWindow);
+ configurationElements.setSynced(false);
+ configurationElements.widget(
+ new ButtonWidget().setOnClick(
+ (clickData, widget) -> {
+ if (!widget.isClient()) widget.getContext()
+ .openSyncedWindow(CONFIGURATION_WINDOW_ID);
+ })
+ .setBackground(GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_BUTTON_CYCLIC)
+ .addTooltip("Configuration")
+ .setSize(16, 16));
+ }
+
+ protected ModularWindow createConfigurationWindow(final EntityPlayer player) {
+ ModularWindow.Builder builder = ModularWindow.builder(200, 100);
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ builder.widget(
+ new DrawableWidget().setDrawable(GT_UITextures.OVERLAY_BUTTON_CYCLIC)
+ .setPos(5, 5)
+ .setSize(16, 16))
+ .widget(new TextWidget("Configuration").setPos(25, 9))
+ .widget(
+ ButtonWidget.closeWindowButton(true)
+ .setPos(185, 3))
+ .widget(
+ new Column().widget(
+ new CycleButtonWidget().setLength(3)
+ .setGetter(() -> mPrimaryMode)
+ .setSetter(val -> {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(player, "Can't change mode when running !");
+ return;
+ }
+ mPrimaryMode = val;
+
+ if (!(player instanceof EntityPlayerMP)) return;
+ switch (mPrimaryMode) {
+ case 0:
+ GT_Utility.sendChatToPlayer(player, "Changed primary mode to: Input mode");
+ break;
+ case 1:
+ GT_Utility.sendChatToPlayer(player, "Changed primary mode to: Output mode");
+ break;
+ case 2:
+ GT_Utility.sendChatToPlayer(player, "Changed primary mode to: Operating mode");
+ break;
+ }
+ })
+ .addTooltip(0, new Text("Input").color(Color.YELLOW.dark(3)))
+ .addTooltip(1, new Text("Output").color(Color.YELLOW.dark(3)))
+ .addTooltip(2, new Text("Operating").color(Color.GREEN.dark(3)))
+ .setTextureGetter(
+ i -> i == 0 ? new Text("Input").color(Color.YELLOW.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : i == 1 ? new Text("Output").color(Color.YELLOW.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : new Text("Operating").color(Color.GREEN.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0))
+ .setBackground(
+ ModularUITextures.VANILLA_BACKGROUND,
+ GT_UITextures.OVERLAY_BUTTON_CYCLIC.withFixedSize(18, 18))
+ .setSize(70, 18)
+ .addTooltip("Primary mode"))
+ .widget(
+ new CycleButtonWidget().setLength(2)
+ .setGetter(() -> mSecondaryMode)
+ .setSetter(val -> {
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(player, "Can't change mode when running !");
+ return;
+ }
+
+ mSecondaryMode = val;
+
+ if (!(player instanceof EntityPlayerMP)) return;
+ switch (mSecondaryMode) {
+ case 0:
+ GT_Utility.sendChatToPlayer(player, "Changed secondary mode to: Normal mode");
+ break;
+ case 1:
+ GT_Utility.sendChatToPlayer(player, "Changed secondary mode to: Swarmer mode");
+ break;
+ }
+ })
+ .addTooltip(0, new Text("Normal").color(Color.GREEN.dark(3)))
+ .addTooltip(1, new Text("Swarmer").color(Color.YELLOW.dark(3)))
+ .setTextureGetter(
+ i -> i == 0 ? new Text("Normal").color(Color.GREEN.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0)
+ : new Text("Swarmer").color(Color.YELLOW.dark(3))
+ .withFixedSize(70 - 18, 18, 15, 0))
+ .setBackground(
+ ModularUITextures.VANILLA_BACKGROUND,
+ GT_UITextures.OVERLAY_BUTTON_CYCLIC.withFixedSize(18, 18))
+ .setSize(70, 18)
+ .addTooltip("Secondary mode"))
+ .setEnabled(widget -> !getBaseMetaTileEntity().isActive())
+ .setPos(10, 30))
+ .widget(
+ new Column().widget(new TextWidget("Primary mode").setSize(100, 18))
+ .widget(new TextWidget("Secondary mode").setSize(100, 18))
+ .setEnabled(widget -> !getBaseMetaTileEntity().isActive())
+ .setPos(80, 30))
+ .widget(
+ new DrawableWidget().setDrawable(GT_UITextures.OVERLAY_BUTTON_CROSS)
+ .setSize(18, 18)
+ .setPos(10, 30)
+ .addTooltip(new Text("Can't change configuration when running !").color(Color.RED.dark(3)))
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive()));
+ return builder.build();
+ }
+
+ // private List<String> flowersGUI = Collections.emptyList();
+
+ private HashMap<ItemStack, Double> GUIDropProgress = new HashMap<>();
+
+ @Override
+ protected String generateCurrentRecipeInfoString() {
+ if (mSecondaryMode == 1) return super.generateCurrentRecipeInfoString();
+ StringBuilder ret = new StringBuilder(EnumChatFormatting.WHITE + "Progress: ")
+ .append(String.format("%,.2f", (double) mProgresstime / 20))
+ .append("s / ")
+ .append(String.format("%,.2f", (double) mMaxProgresstime / 20))
+ .append("s (")
+ .append(String.format("%,.1f", (double) mProgresstime / mMaxProgresstime * 100))
+ .append("%)\n");
+
+ for (Map.Entry<ItemStack, Double> drop : GUIDropProgress.entrySet()) {
+ int outputSize = Arrays.stream(mOutputItems)
+ .filter(s -> s.isItemEqual(drop.getKey()))
+ .mapToInt(i -> i.stackSize)
+ .sum();
+ ret.append(EnumChatFormatting.AQUA)
+ .append(
+ drop.getKey()
+ .getDisplayName())
+ .append(EnumChatFormatting.WHITE)
+ .append(": ");
+ if (outputSize == 0) {
+ ret.append(String.format("%.2f", drop.getValue() * 100))
+ .append("%\n");
+ } else {
+ ret.append(EnumChatFormatting.GOLD)
+ .append(
+ String.format(
+ "x%d %s(+%.2f/sec)\n",
+ outputSize,
+ EnumChatFormatting.WHITE,
+ (double) outputSize / (mMaxProgresstime / 20)));
+ }
+ }
+
+ return ret.toString();
+ }
+
+ @Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+
+ screenElements.widget(new FakeSyncWidget.IntegerSyncer(() -> mSecondaryMode, b -> mSecondaryMode = b));
+ screenElements.widget(new FakeSyncWidget<>(() -> {
+ HashMap<ItemStack, Double> ret = new HashMap<>();
+ HashMap<ItemID, Double> dropProgress = new HashMap<>();
+
+ for (Map.Entry<ItemID, Double> drop : this.dropProgress.entrySet()) {
+ dropProgress.merge(drop.getKey(), drop.getValue(), Double::sum);
+ }
+
+ for (Map.Entry<ItemID, Double> drop : dropProgress.entrySet()) {
+ ret.put(BeeSimulator.dropstacks.get(drop.getKey()), drop.getValue());
+ }
+ return ret;
+ }, h -> GUIDropProgress = h, (buffer, h) -> {
+ buffer.writeVarIntToBuffer(h.size());
+ for (Map.Entry<ItemStack, Double> itemStackDoubleEntry : h.entrySet()) {
+ try {
+ buffer.writeItemStackToBuffer(itemStackDoubleEntry.getKey());
+ buffer.writeDouble(itemStackDoubleEntry.getValue());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, buffer -> {
+ int len = buffer.readVarIntFromBuffer();
+ HashMap<ItemStack, Double> ret = new HashMap<>(len);
+ for (int i = 0; i < len; i++) {
+ try {
+ ret.put(buffer.readItemStackFromBuffer(), buffer.readDouble());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return ret;
+ }));
+ super.drawTexts(screenElements, inventorySlot);
+ }
+
+ final HashMap<ItemID, Double> dropProgress = new HashMap<>();
+
+ private static class BeeSimulator {
+
+ final ItemStack queenStack;
+ boolean isValid;
+ List<BeeDrop> drops = new ArrayList<>();
+ List<BeeDrop> specialDrops = new ArrayList<>();
+ float beeSpeed;
+
+ float maxBeeCycles;
+ String flowerType;
+ String flowerTypeDescription;
+ private static IBeekeepingMode mode;
+
+ public BeeSimulator(ItemStack queenStack, World world, float t) {
+ isValid = false;
+ this.queenStack = queenStack.copy();
+ this.queenStack.stackSize = 1;
+ generate(world, t);
+ isValid = true;
+ queenStack.stackSize--;
+ }
+
+ public void generate(World world, float t) {
+ if (mode == null) mode = beeRoot.getBeekeepingMode(world);
+ drops.clear();
+ specialDrops.clear();
+ if (beeRoot.getType(this.queenStack) != EnumBeeType.QUEEN) return;
+ IBee queen = beeRoot.getMember(this.queenStack);
+ IBeeModifier beeModifier = mode.getBeeModifier();
+ float mod = beeModifier.getLifespanModifier(null, null, 1.f);
+ int h = queen.getMaxHealth();
+ maxBeeCycles = (float) h / (1.f / mod);
+ IBeeGenome genome = queen.getGenome();
+ this.flowerType = genome.getFlowerProvider()
+ .getFlowerType();
+ this.flowerTypeDescription = genome.getFlowerProvider()
+ .getDescription();
+ IAlleleBeeSpecies primary = genome.getPrimary();
+ beeSpeed = genome.getSpeed();
+ genome.getPrimary()
+ .getProductChances()
+ .forEach((key, value) -> drops.add(new BeeDrop(key, value, beeSpeed, t)));
+ genome.getSecondary()
+ .getProductChances()
+ .forEach((key, value) -> drops.add(new BeeDrop(key, value / 2.f, beeSpeed, t)));
+ primary.getSpecialtyChances()
+ .forEach((key, value) -> specialDrops.add(new BeeDrop(key, value, beeSpeed, t)));
+ }
+
+ public BeeSimulator(NBTTagCompound tag) {
+ queenStack = readItemStackFromNBT(tag.getCompoundTag("queenStack"));
+ isValid = tag.getBoolean("isValid");
+ drops = new ArrayList<>();
+ specialDrops = new ArrayList<>();
+ for (int i = 0, isize = tag.getInteger("dropssize"); i < isize; i++)
+ drops.add(new BeeDrop(tag.getCompoundTag("drops" + i)));
+ for (int i = 0, isize = tag.getInteger("specialDropssize"); i < isize; i++)
+ specialDrops.add(new BeeDrop(tag.getCompoundTag("specialDrops" + i)));
+ beeSpeed = tag.getFloat("beeSpeed");
+ maxBeeCycles = tag.getFloat("maxBeeCycles");
+ if (tag.hasKey("flowerType") && tag.hasKey("flowerTypeDescription")) {
+ flowerType = tag.getString("flowerType");
+ flowerTypeDescription = tag.getString("flowerTypeDescription");
+ } else {
+ IBee queen = beeRoot.getMember(this.queenStack);
+ IBeeGenome genome = queen.getGenome();
+ this.flowerType = genome.getFlowerProvider()
+ .getFlowerType();
+ this.flowerTypeDescription = genome.getFlowerProvider()
+ .getDescription();
+ }
+ }
+
+ public NBTTagCompound toNBTTagCompound() {
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setTag("queenStack", writeItemStackToNBT(queenStack));
+ tag.setBoolean("isValid", isValid);
+ tag.setInteger("dropssize", drops.size());
+ for (int i = 0; i < drops.size(); i++) tag.setTag(
+ "drops" + i,
+ drops.get(i)
+ .toNBTTagCompound());
+ tag.setInteger("specialDropssize", specialDrops.size());
+ for (int i = 0; i < specialDrops.size(); i++) tag.setTag(
+ "specialDrops" + i,
+ specialDrops.get(i)
+ .toNBTTagCompound());
+ tag.setFloat("beeSpeed", beeSpeed);
+ tag.setFloat("maxBeeCycles", maxBeeCycles);
+ tag.setString("flowerType", flowerType);
+ tag.setString("flowerTypeDescription", flowerTypeDescription);
+ return tag;
+ }
+
+ static final Map<ItemID, ItemStack> dropstacks = new HashMap<>();
+
+ public List<ItemStack> getDrops(final GT_MetaTileEntity_MegaIndustrialApiary MTE, final double timePassed) {
+ drops.forEach(d -> {
+ MTE.dropProgress.merge(d.id, d.getAmount(timePassed / 550d), Double::sum);
+ if (!dropstacks.containsKey(d.id)) dropstacks.put(d.id, d.stack);
+ });
+ specialDrops.forEach(d -> {
+ MTE.dropProgress.merge(d.id, d.getAmount(timePassed / 550d), Double::sum);
+ if (!dropstacks.containsKey(d.id)) dropstacks.put(d.id, d.stack);
+ });
+ List<ItemStack> currentDrops = new ArrayList<>();
+ MTE.dropProgress.entrySet()
+ .forEach(e -> {
+ double v = e.getValue();
+ while (v > 1.f) {
+ int size = Math.min((int) v, 64);
+ ItemStack stack = dropstacks.get(e.getKey())
+ .copy();
+ stack.stackSize = size;
+ currentDrops.add(stack);
+ v -= size;
+ e.setValue(v);
+ }
+ });
+ return currentDrops;
+ }
+
+ public ItemStack createIgnobleCopy() {
+ IBee princess = beeRoot.getMember(queenStack);
+ princess.setIsNatural(false);
+ return beeRoot.getMemberStack(princess, EnumBeeType.PRINCESS.ordinal());
+ }
+
+ public void updateTVar(World world, float t) {
+ if (mode == null) mode = beeRoot.getBeekeepingMode(world);
+ drops.forEach(d -> d.updateTVar(t));
+ specialDrops.forEach(d -> d.updateTVar(t));
+ }
+
+ private static class BeeDrop {
+
+ private static final float MAX_PRODUCTION_MODIFIER_FROM_UPGRADES = 17.19926784f; // 4*1.2^8
+ final ItemStack stack;
+ double amount;
+ final ItemID id;
+
+ final float chance;
+ final float beeSpeed;
+ float t;
+
+ public BeeDrop(ItemStack stack, float chance, float beeSpeed, float t) {
+ this.stack = stack;
+ this.chance = chance;
+ this.beeSpeed = beeSpeed;
+ this.t = t;
+ id = ItemID.createNoCopy(this.stack);
+ evaluate();
+ }
+
+ public void updateTVar(float t) {
+ if (this.t != t) {
+ this.t = t;
+ evaluate();
+ }
+ }
+
+ public void evaluate() {
+ this.amount = Bee.getFinalChance(
+ chance,
+ beeSpeed,
+ MAX_PRODUCTION_MODIFIER_FROM_UPGRADES + mode.getBeeModifier()
+ .getProductionModifier(null, MAX_PRODUCTION_MODIFIER_FROM_UPGRADES),
+ t);
+ }
+
+ public double getAmount(double speedModifier) {
+ return amount * speedModifier;
+ }
+
+ public ItemStack get(int amount) {
+ ItemStack r = stack.copy();
+ r.stackSize = amount;
+ return r;
+ }
+
+ public BeeDrop(NBTTagCompound tag) {
+ stack = readItemStackFromNBT(tag.getCompoundTag("stack"));
+ chance = tag.getFloat("chance");
+ beeSpeed = tag.getFloat("beeSpeed");
+ t = tag.getFloat("t");
+ amount = tag.getDouble("amount");
+ id = ItemID.createNoCopy(stack);
+ }
+
+ public NBTTagCompound toNBTTagCompound() {
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setTag("stack", writeItemStackToNBT(stack));
+ tag.setFloat("chance", chance);
+ tag.setFloat("beeSpeed", beeSpeed);
+ tag.setFloat("t", t);
+ tag.setDouble("amount", amount);
+ return tag;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java
new file mode 100644
index 0000000000..1c5588c335
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java
@@ -0,0 +1,73 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockFlower;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGFlowerBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGFlowerBucket.Factory();
+ private static final String NBT_IDENTIFIER = "FLOWER";
+ private static final int REVISION_NUMBER = 0;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a flower, reed or cacti. They all drop their source item multiplied by their seed count
+ Item item = input.getItem();
+ Block block = Block.getBlockFromItem(item);
+ if (item != Items.reeds && block != Blocks.cactus && !(block instanceof BlockFlower)) return null;
+ return new EIGFlowerBucket(input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGFlowerBucket(nbt);
+ }
+ }
+
+ private EIGFlowerBucket(ItemStack input) {
+ super(input, 1, null);
+ }
+
+ private EIGFlowerBucket(NBTTagCompound nbt) {
+ super(nbt);
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ tracker.addDrop(this.seed, this.seedCount * multiplier);
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ return this.isValid();
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java
new file mode 100644
index 0000000000..7daa524d5d
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java
@@ -0,0 +1,905 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockLiquid;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.oredict.OreDictionary;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.ItemList;
+import gregtech.common.blocks.GT_Block_Ores_Abstract;
+import gregtech.common.blocks.GT_Item_Ores;
+import gregtech.common.blocks.GT_TileEntity_Ores;
+import ic2.api.crops.CropCard;
+import ic2.api.crops.Crops;
+import ic2.core.Ic2Items;
+import ic2.core.crop.CropStickreed;
+import ic2.core.crop.IC2Crops;
+import ic2.core.crop.TileEntityCrop;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGIC2Bucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGIC2Bucket.Factory();
+ private static final String NBT_IDENTIFIER = "IC2";
+ private static final int REVISION_NUMBER = 0;
+
+ // region crop simulation variables
+
+ private final static int NUMBER_OF_DROPS_TO_SIMULATE = 1000;
+ // nutrient factors
+ /**
+ * Set to true if you want to assume the crop is on wet farmland for a +2 bonus to nutrients
+ */
+ private static final boolean IS_ON_WET_FARMLAND = true;
+ /**
+ * The amount of water stored in the crop stick when hydration is turned on.
+ * bounds of 0 to 200 inclusive
+ */
+ private static final int WATER_STORAGE_VALUE = 200;
+ // nutrient factors
+ /**
+ * The number of blocks of dirt we assume are under. Subtract 1 if we have a block under our crop.
+ * bounds of 0 to 3, inclusive
+ */
+ private static final int NUMBER_OF_DIRT_BLOCKS_UNDER = 0;
+ /**
+ * The amount of fertilizer stored in the crop stick
+ * bounds of 0 to 200, inclusive
+ */
+ private static final int FERTILIZER_STORAGE_VALUE = 0;
+ // air quality factors
+ /**
+ * How many blocks in a 3x3 area centered on the crop do not contain solid blocks or other crops.
+ * Max value is 8 because the crop always counts itself.
+ * bound of 0-8 inclusive
+ */
+ private static final int CROP_OBSTRUCTION_VALUE = 5;
+ /**
+ * Being able to see the sky gives a +2 bonus to the air quality
+ */
+ private static final boolean CROP_CAN_SEE_SKY = false;
+
+ // endregion crop simulation variables
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a seed.
+ if (!ItemList.IC2_Crop_Seeds.isStackEqual(input, true, true)) return null;
+ if (!input.hasTagCompound()) return null;
+ // Validate that stat nbt data exists.
+ NBTTagCompound nbt = input.getTagCompound();
+ if (!(nbt.hasKey("growth") && nbt.hasKey("gain") && nbt.hasKey("resistance"))) return null;
+
+ CropCard cc = IC2Crops.instance.getCropCard(input);
+ if (cc == null) return null;
+ return new EIGIC2Bucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGIC2Bucket(nbt);
+ }
+ }
+
+ public final boolean useNoHumidity;
+ /**
+ * The average amount of growth cycles needed to reach maturity.
+ */
+ private double growthTime = 0;
+ private EIGDropTable drops = new EIGDropTable();
+ private boolean isValid = false;
+
+ /**
+ * Used to migrate old EIG greenhouse slots to the new bucket system, needs custom handling as to not void the
+ * support blocks.
+ *
+ * @implNote DOES NOT VALIDATE THE CONTENTS OF THE BUCKET, YOU'LL HAVE TO REVALIDATE WHEN THE WORLD IS LOADED.
+ *
+ * @param seed The item stack for the item that served as the seed before
+ * @param count The number of seed in the bucket
+ * @param supportBlock The block that goes under the bucket
+ * @param useNoHumidity Whether to use no humidity in growth speed calculations.
+ */
+ public EIGIC2Bucket(ItemStack seed, int count, ItemStack supportBlock, boolean useNoHumidity) {
+ super(seed, count, supportBlock == null ? null : new ItemStack[] { supportBlock });
+ this.useNoHumidity = useNoHumidity;
+ // revalidate me
+ this.isValid = false;
+ }
+
+ private EIGIC2Bucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack seed) {
+ super(seed, 1, null);
+ this.useNoHumidity = greenhouse.isInNoHumidityMode();
+ this.recalculateDrops(greenhouse);
+ }
+
+ private EIGIC2Bucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.useNoHumidity = nbt.getBoolean("useNoHumidity");
+ // If the invalid key exists then drops and growth time haven't been saved
+ if (!nbt.hasKey("invalid")) {
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.growthTime = nbt.getDouble("growthTime");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && this.growthTime > 0 && !this.drops.isEmpty();
+ }
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setBoolean("useNoHumidity", this.useNoHumidity);
+ if (this.isValid) {
+ nbt.setTag("drops", this.drops.save());
+ nbt.setDouble("growthTime", this.growthTime);
+ } else {
+ nbt.setBoolean("invalid", true);
+ }
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ // abort early if the bucket is invalid
+ if (!this.isValid()) return;
+ // else apply drops to tracker
+ double growthPercent = multiplier / (this.growthTime * TileEntityCrop.tickRate);
+ if (this.drops != null) {
+ this.drops.addTo(tracker, this.seedCount * growthPercent);
+ }
+ }
+
+ @Override
+ protected void getAdditionalInfoData(StringBuilder sb) {
+ sb.append(" | Humidity: ");
+ sb.append(this.useNoHumidity ? "Off" : "On");
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ /**
+ * (Re-)calculates the pre-generated drop table for this bucket.
+ *
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that contains this bucket.
+ */
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ World world = greenhouse.getBaseMetaTileEntity()
+ .getWorld();
+ int[] abc = new int[] { 0, -2, 3 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ greenhouse.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += greenhouse.getBaseMetaTileEntity()
+ .getXCoord();
+ xyz[1] += greenhouse.getBaseMetaTileEntity()
+ .getYCoord();
+ xyz[2] += greenhouse.getBaseMetaTileEntity()
+ .getZCoord();
+ boolean cheating = false;
+ FakeTileEntityCrop crop;
+ try {
+ if (world.getBlock(xyz[0], xyz[1] - 2, xyz[2]) != GregTech_API.sBlockCasings4
+ || world.getBlockMetadata(xyz[0], xyz[1] - 2, xyz[2]) != 1) {
+ // no
+ cheating = true;
+ return;
+ }
+
+ // instantiate the TE in which we grow the seed.
+ crop = new FakeTileEntityCrop(this, greenhouse, xyz);
+ if (!crop.isValid) return;
+ CropCard cc = crop.getCrop();
+
+ // region can grow checks
+
+ // Check if we can put the current block under the soil.
+ if (this.supportItems != null && this.supportItems.length == 1 && this.supportItems[0] != null) {
+ if (!setBlock(this.supportItems[0], xyz[0], xyz[1] - 2, xyz[2], world)) {
+ return;
+ }
+ // update nutrients if we need a block under.
+ crop.updateNutrientsForBlockUnder();
+ }
+
+ // Check if the crop has a chance to die in the current environment
+ if (calcAvgGrowthRate(crop, cc, 0) < 0) return;
+ // Check if the crop has a chance to grow in the current environment.
+ if (calcAvgGrowthRate(crop, cc, 6) <= 0) return;
+
+ ItemStack blockInputStackToConsume = null;
+ if (!crop.canMature()) {
+ // If the block we have in storage no longer functions, we are no longer valid, the seed and block
+ // should be ejected if possible.
+ if (this.supportItems != null) return;
+ // assume we need a block under the farmland/fertilized dirt and update nutrients accordingly
+ crop.updateNutrientsForBlockUnder();
+ // Try to find the needed block in the inputs
+ boolean canGrow = false;
+ ArrayList<ItemStack> inputs = greenhouse.getStoredInputs();
+ for (ItemStack potentialBlock : inputs) {
+ // if the input can't be placed in the world skip to the next input
+ if (potentialBlock == null || potentialBlock.stackSize <= 0) continue;
+ if (!setBlock(potentialBlock, xyz[0], xyz[1] - 2, xyz[2], world)) continue;
+ // check if the crop can grow with the block under it.
+ if (!crop.canMature()) continue;
+ // If we don't have enough blocks to consume, abort.
+ if (this.seedCount > potentialBlock.stackSize) return;
+ canGrow = true;
+ blockInputStackToConsume = potentialBlock;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlock.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ break;
+ }
+
+ if (!canGrow) return;
+ }
+
+ // check if the crop does a block under check and try to put a requested block if possible
+ if (this.supportItems == null) {
+ // some crops get increased outputs if a specific block is under them.
+ cc.getGain(crop);
+ if (crop.hasRequestedBlockUnder()) {
+ ArrayList<ItemStack> inputs = greenhouse.getStoredInputs();
+ boolean keepLooking = !inputs.isEmpty();
+ if (keepLooking && !crop.reqBlockOreDict.isEmpty()) {
+ oreDictLoop: for (String reqOreDictName : crop.reqBlockOreDict) {
+ if (reqOreDictName == null || OreDictionary.doesOreNameExist(reqOreDictName)) continue;
+ int oreId = OreDictionary.getOreID(reqOreDictName);
+ for (ItemStack potentialBlock : inputs) {
+ if (potentialBlock == null || potentialBlock.stackSize <= 0) continue;
+ for (int inputOreId : OreDictionary.getOreIDs(potentialBlock)) {
+ if (inputOreId != oreId) continue;
+ blockInputStackToConsume = potentialBlock;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlock.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ keepLooking = false;
+ crop.updateNutrientsForBlockUnder();
+ break oreDictLoop;
+ }
+ }
+ }
+ }
+ if (keepLooking && !crop.reqBlockSet.isEmpty()) {
+ blockLoop: for (Block reqBlock : crop.reqBlockSet) {
+ if (reqBlock == null || reqBlock instanceof BlockLiquid) continue;
+ for (ItemStack potentialBlockStack : inputs) {
+ // TODO: figure out a way to handle liquid block requirements
+ // water lilly looks for water and players don't really have access to those.
+ if (potentialBlockStack == null || potentialBlockStack.stackSize <= 0) continue;
+ // check if it places a block that is equal to the the one we are looking for
+ Block inputBlock = Block.getBlockFromItem(potentialBlockStack.getItem());
+ if (inputBlock != reqBlock) continue;
+ blockInputStackToConsume = potentialBlockStack;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlockStack.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ keepLooking = false;
+ crop.updateNutrientsForBlockUnder();
+ break blockLoop;
+ }
+ }
+ }
+ }
+ }
+
+ // check if the crop can be harvested at its max size
+ // Eg: the Eating plant cannot be harvested at its max size of 6, only 4 or 5 can
+ crop.setSize((byte) cc.maxSize());
+ if (!cc.canBeHarvested(crop)) return;
+
+ // endregion can grow checks
+
+ // region drop rate calculations
+
+ // PRE CALCULATE DROP RATES
+ // TODO: Add better loot table handling for crops like red wheat
+ // berries, etc.
+ EIGDropTable drops = new EIGDropTable();
+ // Multiply drop sizes by the average number drop rounds per harvest.
+ double avgDropRounds = getRealAverageDropRounds(crop, cc);
+ double avgStackIncrease = getRealAverageDropIncrease(crop, cc);
+ HashMap<Integer, Integer> sizeAfterHarvestFrequencies = new HashMap<>();
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ // try generating some loot drop
+ ItemStack drop = cc.getGain(crop);
+ if (drop == null || drop.stackSize <= 0) continue;
+ sizeAfterHarvestFrequencies.merge((int) cc.getSizeAfterHarvest(crop), 1, Integer::sum);
+
+ // Merge the new drop with the current loot table.
+ double avgAmount = (drop.stackSize + avgStackIncrease) * avgDropRounds;
+ drops.addDrop(drop, avgAmount / NUMBER_OF_DROPS_TO_SIMULATE);
+ }
+ if (drops.isEmpty()) return;
+
+ // endregion drop rate calculations
+
+ // region growth time calculation
+
+ // Just doing average(ceil(stageGrowth/growthSpeed)) isn't good enough it's off by as much as 20%
+ double avgGrowthCyclesToHarvest = calcRealAvgGrowthRate(crop, cc, sizeAfterHarvestFrequencies);
+ if (avgGrowthCyclesToHarvest <= 0) {
+ return;
+ }
+
+ // endregion growth time calculation
+
+ // Consume new under block if necessary
+ if (blockInputStackToConsume != null) blockInputStackToConsume.stackSize -= this.seedCount;
+ // We are good return success
+ this.growthTime = avgGrowthCyclesToHarvest;
+ this.drops = drops;
+ this.isValid = true;
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ } finally {
+ // always reset the world to it's original state
+ if (!cheating) world.setBlock(xyz[0], xyz[1] - 2, xyz[2], GregTech_API.sBlockCasings4, 1, 0);
+ // world.setBlockToAir(xyz[0], xyz[1], xyz[2]);
+ }
+ }
+
+ /**
+ * Attempts to place a block in the world, used for testing crop viability and drops.
+ *
+ * @param stack The {@link ItemStack} to place.
+ * @param x The x coordinate at which to place the block.
+ * @param y The y coordinate at which to place the block.
+ * @param z The z coordinate at which to place the block.
+ * @param world The world in which to place the block.
+ * @return true of a block was placed.
+ */
+ private static boolean setBlock(ItemStack stack, int x, int y, int z, World world) {
+ Item item = stack.getItem();
+ Block b = Block.getBlockFromItem(item);
+ if (b == Blocks.air || !(item instanceof ItemBlock)) return false;
+ short tDamage = (short) item.getDamage(stack);
+ if (item instanceof GT_Item_Ores && tDamage > 0) {
+ if (!world.setBlock(
+ x,
+ y,
+ z,
+ b,
+ GT_TileEntity_Ores.getHarvestData(
+ tDamage,
+ ((GT_Block_Ores_Abstract) b).getBaseBlockHarvestLevel(tDamage % 16000 / 1000)),
+ 0)) {
+ return false;
+ }
+ GT_TileEntity_Ores tTileEntity = (GT_TileEntity_Ores) world.getTileEntity(x, y, z);
+ tTileEntity.mMetaData = tDamage;
+ tTileEntity.mNatural = false;
+ } else world.setBlock(x, y, z, b, tDamage, 0);
+ return true;
+ }
+
+ // region drop rate calculations
+
+ /**
+ * Calculates the average number of separate item drops to be rolled per harvest using information obtained by
+ * decompiling IC2.
+ *
+ * @see TileEntityCrop#harvest_automated(boolean)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average number of drops to computer per harvest
+ */
+ private static double getRealAverageDropRounds(TileEntityCrop te, CropCard cc) {
+ // this should be ~99.995% accurate
+ double chance = (double) cc.dropGainChance() * Math.pow(1.03, te.getGain());
+ // this is essentially just performing an integration using the composite trapezoidal rule.
+ double min = -10, max = 10;
+ int steps = 10000;
+ double stepSize = (max - min) / steps;
+ double sum = 0;
+ for (int k = 1; k <= steps - 1; k++) {
+ sum += getWeightedDropChance(min + k * stepSize, chance);
+ }
+ double minVal = getWeightedDropChance(min, chance);
+ double maxVal = getWeightedDropChance(max, chance);
+ return stepSize * ((minVal + maxVal) / 2 + sum);
+ }
+
+ /**
+ * Evaluates the value of y for a standard normal distribution
+ *
+ * @param x The value of x to evaluate
+ * @return The value of y
+ */
+ private static double stdNormDistr(double x) {
+ return Math.exp(-0.5 * (x * x)) / SQRT2PI;
+ }
+
+ private static final double SQRT2PI = Math.sqrt(2.0d * Math.PI);
+
+ /**
+ * Calculates the weighted drop chance using
+ *
+ * @param x The value rolled by nextGaussian
+ * @param chance the base drop chance
+ * @return the weighted drop chance
+ */
+ private static double getWeightedDropChance(double x, double chance) {
+ return Math.max(0L, Math.round(x * chance * 0.6827d + chance)) * stdNormDistr(x);
+ }
+
+ /**
+ * Calculates the average drop of the stack size caused by seed's gain using information obtained by
+ * decompiling IC2.
+ *
+ * @see TileEntityCrop#harvest_automated(boolean)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average number of drops to computer per harvest
+ */
+ private static double getRealAverageDropIncrease(TileEntityCrop te, CropCard cc) {
+ // yes gain has the amazing ability to sometimes add 1 to your stack size!
+ return (te.getGain() + 1) / 100.0d;
+ }
+
+ // endregion drop rate calculations
+
+ // region growth time approximation
+
+ /**
+ * Calculates the average number growth cycles needed for a crop to grow to maturity.
+ *
+ * @see EIGIC2Bucket#calcAvgGrowthRate(TileEntityCrop, CropCard, int)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average growth rate as a floating point number
+ */
+ private static double calcRealAvgGrowthRate(TileEntityCrop te, CropCard cc,
+ HashMap<Integer, Integer> sizeAfterHarvestFrequencies) {
+ // Compute growth speeds.
+ int[] growthSpeeds = new int[7];
+ for (int i = 0; i < 7; i++) growthSpeeds[i] = calcAvgGrowthRate(te, cc, i);
+
+ // if it's stick reed, we know what the distribution should look like
+ if (cc.getClass() == CropStickreed.class) {
+ sizeAfterHarvestFrequencies.clear();
+ sizeAfterHarvestFrequencies.put(1, 1);
+ sizeAfterHarvestFrequencies.put(2, 1);
+ sizeAfterHarvestFrequencies.put(3, 1);
+ }
+
+ // Get the duration of all growth stages
+ int[] growthDurations = new int[cc.maxSize()];
+ // , index 0 is assumed to be 0 since stage 0 is usually impossible.
+ // The frequency table should prevent stage 0 from having an effect on the result.
+ growthDurations[0] = 0; // stage 0 doesn't usually exist.
+ for (byte i = 1; i < growthDurations.length; i++) {
+ te.setSize(i);
+ growthDurations[i] = cc.growthDuration(te);
+ }
+
+ return calcRealAvgGrowthRate(growthSpeeds, growthDurations, sizeAfterHarvestFrequencies);
+ }
+
+ /**
+ * Calculates the average number growth cycles needed for a crop to grow to maturity.
+ *
+ * @implNote This method is entirely self-contained and can therefore be unit tested.
+ *
+ * @param growthSpeeds The speeds at which the crop can grow.
+ * @param stageGoals The total to reach for each stage
+ * @param startStageFrequency How often the growth starts from a given stage
+ * @return The average growth rate as a floating point number
+ */
+ public static double calcRealAvgGrowthRate(int[] growthSpeeds, int[] stageGoals,
+ HashMap<Integer, Integer> startStageFrequency) {
+
+ // taking out the zero rolls out of the calculation tends to make the math more accurate for lower speeds.
+ int[] nonZeroSpeeds = Arrays.stream(growthSpeeds)
+ .filter(x -> x > 0)
+ .toArray();
+ int zeroRolls = growthSpeeds.length - nonZeroSpeeds.length;
+ if (zeroRolls >= growthSpeeds.length) return -1;
+
+ // compute stage lengths and stage frequencies
+ double[] avgCyclePerStage = new double[stageGoals.length];
+ double[] normalizedStageFrequencies = new double[stageGoals.length];
+ long frequenciesSum = startStageFrequency.values()
+ .parallelStream()
+ .mapToInt(x -> x)
+ .sum();
+ for (int i = 0; i < stageGoals.length; i++) {
+ avgCyclePerStage[i] = calcAvgCyclesToGoal(nonZeroSpeeds, stageGoals[i]);
+ normalizedStageFrequencies[i] = startStageFrequency.getOrDefault(i, 0) * stageGoals.length
+ / (double) frequenciesSum;
+ }
+
+ // Compute multipliers based on how often the growth starts at a given rate.
+ double[] frequencyMultipliers = new double[avgCyclePerStage.length];
+ Arrays.fill(frequencyMultipliers, 1.0d);
+ conv1DAndCopyToSignal(
+ frequencyMultipliers,
+ normalizedStageFrequencies,
+ new double[avgCyclePerStage.length],
+ 0,
+ frequencyMultipliers.length,
+ 0);
+
+ // apply multipliers to length
+ for (int i = 0; i < avgCyclePerStage.length; i++) avgCyclePerStage[i] *= frequencyMultipliers[i];
+
+ // lengthen average based on number of 0 rolls.
+ double average = Arrays.stream(avgCyclePerStage)
+ .average()
+ .orElse(-1);
+ if (average <= 0) return -1;
+ if (zeroRolls > 0) {
+ average = average / nonZeroSpeeds.length * growthSpeeds.length;
+ }
+
+ // profit
+ return average;
+ }
+
+ /**
+ * Computes the average number of rolls of an N sided fair dice with irregular number progressions needed to surpass
+ * a given total.
+ *
+ * @param speeds The speeds at which the crop grows.
+ * @param goal The total to match or surpass.
+ * @return The average number of rolls of speeds to meet or surpass the goal.
+ */
+ private static double calcAvgCyclesToGoal(int[] speeds, int goal) {
+ // even if the goal is 0, it will always take at least 1 cycle.
+ if (goal <= 0) return 1;
+ double mult = 1.0d;
+ int goalCap = speeds[speeds.length - 1] * 1000;
+ if (goal > goalCap) {
+ mult = (double) goal / goalCap;
+ goal = goalCap;
+ }
+ // condition start signal
+ double[] signal = new double[goal];
+ Arrays.fill(signal, 0);
+ signal[0] = 1;
+
+ // Create kernel out of our growth speeds
+ double[] kernel = tabulate(speeds, 1.0d / speeds.length);
+ double[] convolutionTarget = new double[signal.length];
+ LinkedList<Double> P = new LinkedList<Double>();
+
+ // Perform convolutions on the signal until it's too weak to be recognised.
+ double p, avgRolls = 1;
+ int iterNo = 0;
+ // 1e-1 is a threshold, you can increase it for to increase the accuracy of the output.
+ // 1e-1 is already accurate enough that any value beyond that is unwarranted.
+ int min = speeds[0];
+ int max = speeds[speeds.length - 1];
+ do {
+ avgRolls += p = conv1DAndCopyToSignal(signal, kernel, convolutionTarget, min, max, iterNo);
+ iterNo += 1;
+ } while (p >= 1e-1 / goal);
+ return avgRolls * mult;
+ }
+
+ /**
+ * Creates an array that corresponds to the amount of times a number appears in a list.
+ *
+ * Ex: {1,2,3,4} -> {0,1,1,1,1}, {0,2,2,4} -> {1,0,2,0,1}
+ *
+ * @param bin The number list to tabulate
+ * @param multiplier A multiplier to apply the output list
+ * @return The number to tabulate
+ */
+ private static double[] tabulate(int[] bin, double multiplier) {
+ double[] ret = new double[bin[bin.length - 1] + 1];
+ Arrays.fill(ret, 0);
+ for (int i : bin) ret[i] += multiplier;
+ return ret;
+ }
+
+ /**
+ * Computes a 1D convolution of a signal and stores the results in the signal array.
+ * Essentially performs `X <- convolve(X,rev(Y))[1:length(X)]` in R
+ *
+ * @param signal The signal to apply the convolution to.
+ * @param kernel The kernel to compute with.
+ * @param fixedLengthTarget A memory optimisation so we don't just create a ton of arrays since we overwrite it.
+ * Should be the same length as the signal.
+ */
+ private static double conv1DAndCopyToSignal(double[] signal, double[] kernel, double[] fixedLengthTarget,
+ int minValue, int maxValue, int iterNo) {
+ // for a 1d convolution we would usually use kMax = signal.length + kernel.length - 1
+ // but since we are directly applying our result to our signal, there is no reason to compute
+ // values where k > signal.length.
+ // we could probably run this loop in parallel.
+ double sum = 0;
+ int maxK = Math.min(signal.length, (iterNo + 1) * maxValue + 1);
+ int startAt = Math.min(signal.length, minValue * (iterNo + 1));
+ int k = Math.max(0, startAt - kernel.length);
+ for (; k < startAt; k++) fixedLengthTarget[k] = 0;
+ for (; k < maxK; k++) {
+ // I needs to be a valid index of the kernel.
+ fixedLengthTarget[k] = 0;
+ for (int i = Math.max(0, k - kernel.length + 1); i <= k; i++) {
+ double v = signal[i] * kernel[k - i];
+ sum += v;
+ fixedLengthTarget[k] += v;
+ }
+ }
+ System.arraycopy(fixedLengthTarget, 0, signal, 0, signal.length);
+ return sum;
+ }
+
+ /**
+ * Calculates the average growth rate of an ic2 crop using information obtained though decompiling IC2.
+ * Calls to random functions have been either replaced with customisable values or boundary tests.
+ *
+ * @see TileEntityCrop#calcGrowthRate()
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @param rngRoll The role for the base rng
+ * @return The amounts of growth point added to the growth progress in average every growth tick
+ */
+ private static int calcAvgGrowthRate(TileEntityCrop te, CropCard cc, int rngRoll) {
+ // the original logic uses IC2.random.nextInt(7)
+ int base = 3 + rngRoll + te.getGrowth();
+ int need = Math.max(0, (cc.tier() - 1) * 4 + te.getGrowth() + te.getGain() + te.getResistance());
+ int have = cc.weightInfluences(te, te.getHumidity(), te.getNutrients(), te.getAirQuality()) * 5;
+
+ if (have >= need) {
+ // The crop has a good enough environment to grow normally
+ return base * (100 + (have - need)) / 100;
+ } else {
+ // this only happens if we don't have enough
+ // resources to grow properly.
+ int neg = (need - have) * 4;
+
+ if (neg > 100) {
+ // a crop with a resistance 31 will never die since the original
+ // checks for `IC2.random.nextInt(32) > this.statResistance`
+ // so assume that the crop will eventually die if it doesn't
+ // have maxed out resistance stats. 0 means no growth this tick
+ // -1 means the crop dies.
+ return te.getResistance() >= 31 ? 0 : -1;
+ }
+ // else apply neg to base
+ return Math.max(0, base * (100 - neg) / 100);
+ }
+ }
+
+ // endregion growth time approximation
+
+ // region deterministic environmental calculations
+
+ /**
+ * Calculates the humidity at the location of the controller using information obtained by decompiling IC2.
+ * Returns 0 if the greenhouse is in no humidity mode.
+ *
+ * @see EIGIC2Bucket#IS_ON_WET_FARMLAND
+ * @see EIGIC2Bucket#WATER_STORAGE_VALUE
+ * @see TileEntityCrop#updateHumidity()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The humidity environmental value at the controller's location.
+ */
+ public static byte getHumidity(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, boolean useNoHumidity) {
+ if (useNoHumidity) return 0;
+ int value = Crops.instance.getHumidityBiomeBonus(
+ greenhouse.getBaseMetaTileEntity()
+ .getBiome());
+ if (IS_ON_WET_FARMLAND) value += 2;
+ // we add 2 if we have more than 5 water in storage
+ if (WATER_STORAGE_VALUE >= 5) value += 2;
+ // add 1 for every 25 water stored (max of 200
+ value += (WATER_STORAGE_VALUE + 24) / 25;
+ return (byte) value;
+ }
+
+ /**
+ * Calculates the nutrient value at the location of the controller using information obtained by decompiling IC2
+ *
+ * @see EIGIC2Bucket#NUMBER_OF_DIRT_BLOCKS_UNDER
+ * @see EIGIC2Bucket#FERTILIZER_STORAGE_VALUE
+ * @see TileEntityCrop#updateNutrients()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The nutrient environmental value at the controller's location.
+ */
+ public static byte getNutrients(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ int value = Crops.instance.getNutrientBiomeBonus(
+ greenhouse.getBaseMetaTileEntity()
+ .getBiome());
+ value += NUMBER_OF_DIRT_BLOCKS_UNDER;
+ value += (FERTILIZER_STORAGE_VALUE + 19) / 20;
+ return (byte) value;
+ }
+
+ /**
+ * Calculates the air quality at the location of the controller bucket using information obtained by decompiling IC2
+ *
+ * @see EIGIC2Bucket#CROP_OBSTRUCTION_VALUE
+ * @see EIGIC2Bucket#CROP_CAN_SEE_SKY
+ * @see TileEntityCrop#updateAirQuality()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The air quality environmental value at the controller's location.
+ */
+ public static byte getAirQuality(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ // clamp height bonus to 0-4, use the height of the crop itself
+ // TODO: check if we want to add the extra +2 for the actual height of the crop stick in the EIG.
+ int value = Math.max(
+ 0,
+ Math.min(
+ 4,
+ (greenhouse.getBaseMetaTileEntity()
+ .getYCoord() - 64) / 15));
+ // min value of fresh is technically 8 since the crop itself will count as an obstruction at xOff = 0, zOff = 0
+ value += CROP_OBSTRUCTION_VALUE / 2;
+ // you get a +2 bonus for being able to see the sky
+ if (CROP_CAN_SEE_SKY) value += 2;
+ return (byte) value;
+ }
+
+ // endregion deterministic environmental calculations
+
+ private static class FakeTileEntityCrop extends TileEntityCrop {
+
+ private boolean isValid;
+ public Set<Block> reqBlockSet = new HashSet<>();
+ public Set<String> reqBlockOreDict = new HashSet<>();
+ private int lightLevel = 15;
+
+ public FakeTileEntityCrop(EIGIC2Bucket bucket, GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse,
+ int[] xyz) {
+ super();
+ this.isValid = false;
+ this.ticker = 1;
+
+ // put seed in crop stick
+ CropCard cc = Crops.instance.getCropCard(bucket.seed);
+ this.setCrop(cc);
+ NBTTagCompound nbt = bucket.seed.getTagCompound();
+ this.setGrowth(nbt.getByte("growth"));
+ this.setGain(nbt.getByte("gain"));
+ this.setResistance(nbt.getByte("resistance"));
+ this.setWorldObj(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld());
+
+ this.xCoord = xyz[0];
+ this.yCoord = xyz[1];
+ this.zCoord = xyz[2];
+ this.blockType = Block.getBlockFromItem(Ic2Items.crop.getItem());
+ this.blockMetadata = 0;
+
+ this.waterStorage = bucket.useNoHumidity ? 0 : WATER_STORAGE_VALUE;
+ this.humidity = EIGIC2Bucket.getHumidity(greenhouse, bucket.useNoHumidity);
+ this.nutrientStorage = FERTILIZER_STORAGE_VALUE;
+ this.nutrients = EIGIC2Bucket.getNutrients(greenhouse);
+ this.airQuality = EIGIC2Bucket.getAirQuality(greenhouse);
+
+ this.isValid = true;
+ }
+
+ public boolean canMature() {
+ CropCard cc = this.getCrop();
+ this.size = cc.maxSize() - 1;
+ // try with a high light level
+ this.lightLevel = 15;
+ if (cc.canGrow(this)) return true;
+ // and then with a low light level.
+ this.lightLevel = 9;
+ return cc.canGrow(this);
+ }
+
+ @Override
+ public boolean isBlockBelow(Block reqBlock) {
+ this.reqBlockSet.add(reqBlock);
+ return super.isBlockBelow(reqBlock);
+ }
+
+ @Override
+ public boolean isBlockBelow(String oreDictionaryName) {
+ this.reqBlockOreDict.add(oreDictionaryName);
+ return super.isBlockBelow(oreDictionaryName);
+ }
+
+ // region environment simulation
+
+ @Override
+ public int getLightLevel() {
+ // 9 should allow most light dependent crops to grow
+ // the only exception I know of the eating plant which checks
+ return this.lightLevel;
+ }
+
+ @Override
+ public byte getHumidity() {
+ return this.humidity;
+ }
+
+ @Override
+ public byte updateHumidity() {
+ return this.humidity;
+ }
+
+ @Override
+ public byte getNutrients() {
+ return this.nutrients;
+ }
+
+ @Override
+ public byte updateNutrients() {
+ return this.nutrients;
+ }
+
+ @Override
+ public byte getAirQuality() {
+ return this.airQuality;
+ }
+
+ @Override
+ public byte updateAirQuality() {
+ return this.nutrients;
+ }
+
+ // endregion environment simulation
+
+ /**
+ * Updates the nutrient value based on the fact tha the crop needs a block under it.
+ */
+ public void updateNutrientsForBlockUnder() {
+ // -1 because the farm land is included in the root check.
+ if ((this.getCrop()
+ .getrootslength(this) - 1
+ - NUMBER_OF_DIRT_BLOCKS_UNDER) <= 0 && this.nutrients > 0) {
+ this.nutrients--;
+ }
+ }
+
+ /**
+ * Checks if the crop stick has requested a block to be under it yet.
+ *
+ * @return true if a block under check was made.
+ */
+ public boolean hasRequestedBlockUnder() {
+ return !this.reqBlockSet.isEmpty() || !this.reqBlockOreDict.isEmpty();
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java
new file mode 100644
index 0000000000..6342080722
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java
@@ -0,0 +1,77 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+import tb.common.block.BlockRainbowCactus;
+import tb.init.TBBlocks;
+
+// This is more of a proof of concept to show how to implement a custom handler.
+
+public class EIGRainbowCactusBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGRainbowCactusBucket.Factory();
+ private static final String NBT_IDENTIFIER = "TB:RAINCACTI";
+ private static final int REVISION_NUMBER = 0;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // check if input is rainbow cacti;
+ if (!(Block.getBlockFromItem(input.getItem()) instanceof BlockRainbowCactus)) return null;
+ return new EIGRainbowCactusBucket(input, 1);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGRainbowCactusBucket(nbt);
+ }
+
+ }
+
+ private final Random random = new Random();
+
+ public EIGRainbowCactusBucket(ItemStack seed, int seedCount) {
+ super(seed, seedCount, null);
+ }
+
+ public EIGRainbowCactusBucket(NBTTagCompound nbt) {
+ super(nbt);
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ return this.isValid();
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ // TODO: make the addDyeDropsToOutput static in TB.
+ ArrayList<ItemStack> drops = new ArrayList<>();
+ ((BlockRainbowCactus) TBBlocks.rainbowCactus).addDyeDropsToOutput(this.random, drops);
+ for (ItemStack drop : drops) {
+ tracker.addDrop(drop, drop.stackSize * multiplier * this.seedCount);
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java
new file mode 100644
index 0000000000..51b4a7162a
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java
@@ -0,0 +1,286 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+
+import net.minecraft.block.Block;
+import net.minecraft.init.Blocks;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemSeedFood;
+import net.minecraft.item.ItemSeeds;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.CraftingManager;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.common.IPlantable;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.GT_DummyWorld;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGSeedBucket extends EIGBucket {
+
+ public static final IEIGBucketFactory factory = new EIGSeedBucket.Factory();
+ private static final String NBT_IDENTIFIER = "SEED";
+ private static final int REVISION_NUMBER = 0;
+ private static final int NUMBER_OF_DROPS_TO_SIMULATE = 1000;
+ private static final int FORTUNE_LEVEL = 0;
+ private static final EIGSeedBucket.GreenHouseWorld fakeWorld = new EIGSeedBucket.GreenHouseWorld(5, 5, 5);
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ return new EIGSeedBucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGSeedBucket(nbt);
+ }
+
+ }
+
+ private boolean isValid = false;
+ private EIGDropTable drops = new EIGDropTable();
+
+ private EIGSeedBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack seed) {
+ super(seed, 1, null);
+ this.recalculateDrops(greenhouse);
+ }
+
+ private EIGSeedBucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && !this.drops.isEmpty();
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setTag("drops", this.drops.save());
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ this.drops.addTo(tracker, multiplier * this.seedCount);
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ int optimalGrowthMetadata = 7;
+ // Get the relevant item and block for this item.
+ Item item = this.seed.getItem();
+ Block block;
+ if (!(item instanceof IPlantable)) return;
+ if (item instanceof ItemSeeds) {
+ block = ((ItemSeeds) item).getPlant(fakeWorld, 0, 0, 0);
+ } else if (item instanceof ItemSeedFood) {
+ block = ((ItemSeedFood) item).getPlant(fakeWorld, 0, 0, 0);
+ } else {
+ // We can't plant it, we can't handle it, get out.
+ return;
+ }
+
+ // Natura crops have an optimal harvest stage of 8.
+ GameRegistry.UniqueIdentifier u = GameRegistry.findUniqueIdentifierFor(item);
+ if (u != null && Objects.equals(u.modId, "Natura")) optimalGrowthMetadata = 8;
+
+ // Pre-Generate drops.
+ EIGDropTable drops = new EIGDropTable();
+ World world = greenhouse.getBaseMetaTileEntity()
+ .getWorld();
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ ArrayList<ItemStack> blockDrops = block.getDrops(world, 0, 0, 0, optimalGrowthMetadata, FORTUNE_LEVEL);
+ for (ItemStack drop : blockDrops) {
+ drops.addDrop(drop, drop.stackSize);
+ }
+ }
+
+ // reduce the number of drops to account for the seeds
+ if (!removeSeedFromDrops(world, drops, this.seed, NUMBER_OF_DROPS_TO_SIMULATE)) return;
+
+ // reduce drop count to account for the number of simulations
+ drops.entrySet()
+ .forEach(x -> x.setValue(x.getValue() / NUMBER_OF_DROPS_TO_SIMULATE));
+
+ // make sure we actually got a drop
+ if (drops.isEmpty()) return;
+
+ // and we are good, see ya.
+ this.drops = drops;
+ this.isValid = true;
+ }
+
+ private boolean removeSeedFromDrops(World world, EIGDropTable drops, ItemStack seed, int seedsToConsume) {
+ // make a safe copy of the seed just in case
+ ItemStack seedSafe = seed.copy();
+ seedSafe.stackSize = 1;
+ // first check if we dropped an item identical to our seed item.
+ int inputSeedDropCountAfterRemoval = (int) Math.round(drops.getItemAmount(seedSafe)) - seedsToConsume;
+ if (inputSeedDropCountAfterRemoval > 0) {
+ drops.setItemAmount(seedSafe, inputSeedDropCountAfterRemoval);
+ } else {
+ drops.removeItem(seedSafe);
+ }
+ // return true if we were able to find enough seeds in the drops.
+ if (inputSeedDropCountAfterRemoval >= 0) return true;
+
+ // else try to find items that can be crafted into the seed
+ int seedsToCraft = -inputSeedDropCountAfterRemoval;
+ IRecipe[] validRecipes = CraftingManager.getInstance()
+ .getRecipeList()
+ .parallelStream()
+ .filter(recipe -> GT_Utility.areStacksEqual(recipe.getRecipeOutput(), seed))
+ .toArray(IRecipe[]::new);
+
+ // if no recipes outputs the input seed, abort.
+ if (validRecipes.length == 0) return false;
+
+ // check the recipes we found for one that can consume our seed
+ for (Iterator<Map.Entry<ItemStack, Double>> dropIterator = drops.entrySet()
+ .iterator(); dropIterator.hasNext();) {
+ Map.Entry<ItemStack, Double> entry = dropIterator.next();
+ int inputCount = (int) Math.round(entry.getValue());
+ ItemStack input = entry.getKey()
+ .copy();
+ input.stackSize = 1;
+ EIGCraftingSeedFinder seedFinder = new EIGCraftingSeedFinder(input);
+ for (IRecipe recipe : validRecipes) {
+ if (recipe.matches(seedFinder, world)) {
+ // account for recipes that potentially drop more than 1 seed per input.
+ int outputsPerCraft = recipe.getCraftingResult(seedFinder).stackSize;
+ int craftableSeeds = outputsPerCraft * inputCount;
+ if (seedsToCraft >= craftableSeeds) {
+ // if the entire drop is consumed, just remove it from the list.
+ dropIterator.remove();
+ seedsToCraft -= craftableSeeds;
+ if (seedsToCraft <= 0) {
+ return true;
+ }
+ } else {
+ // else remove the right amount from the drop, and get out.
+ entry.setValue(entry.getValue() - (double) seedsToCraft / outputsPerCraft);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ class EIGCraftingSeedFinder extends InventoryCrafting {
+
+ public ItemStack recipeInput;
+
+ public EIGCraftingSeedFinder(ItemStack recipeInput) {
+ super(null, 3, 3);
+ this.recipeInput = recipeInput;
+ }
+
+ @Override
+ public ItemStack getStackInSlot(int p_70301_1_) {
+ if (p_70301_1_ == 0) return this.recipeInput.copy();
+ return null;
+ }
+
+ @Override
+ public ItemStack getStackInSlotOnClosing(int par1) {
+ return null;
+ }
+
+ @Override
+ public ItemStack decrStackSize(int par1, int par2) {
+ return null;
+ }
+
+ @SuppressWarnings("EmptyMethod")
+ @Override
+ public void setInventorySlotContents(int par1, ItemStack par2ItemStack) {}
+ }
+
+ private static class GreenHouseWorld extends GT_DummyWorld {
+
+ public int x, y, z, meta = 0;
+ public Block block;
+
+ GreenHouseWorld(int x, int y, int z) {
+ super();
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.rand = new EIGSeedBucket.GreenHouseRandom();
+ }
+
+ @Override
+ public int getBlockMetadata(int aX, int aY, int aZ) {
+ if (aX == x && aY == y && aZ == z) return 7;
+ return 0;
+ }
+
+ @Override
+ public Block getBlock(int aX, int aY, int aZ) {
+ if (aY == y - 1) return Blocks.farmland;
+ return Blocks.air;
+ }
+
+ @Override
+ public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) {
+ return 10;
+ }
+
+ @Override
+ public boolean setBlock(int aX, int aY, int aZ, Block aBlock, int aMeta, int aFlags) {
+ if (aBlock == Blocks.air) return false;
+ if (aX == x && aY == y && aZ == z) return false;
+ block = aBlock;
+ meta = aMeta;
+ return true;
+ }
+ }
+
+ private static class GreenHouseRandom extends Random {
+
+ private static final long serialVersionUID = -387271808935248890L;
+
+ @Override
+ public int nextInt(int bound) {
+ return 0;
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java
new file mode 100644
index 0000000000..e0ebcf8652
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java
@@ -0,0 +1,158 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockStem;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.common.IPlantable;
+
+import kubatech.api.IBlockStemAccesor;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGStemBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGStemBucket.Factory();
+ private static final String NBT_IDENTIFIER = "STEM";
+ private static final int REVISION_NUMBER = 0;
+ private final static int NUMBER_OF_DROPS_TO_SIMULATE = 100;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a flower, reed or cacti. They all drop their source item multiplied by their seed count
+ Item item = input.getItem();
+ if (!(item instanceof IPlantable)) return null;
+ Block block = ((IPlantable) item).getPlant(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ 0,
+ 0,
+ 0);
+ if (!(block instanceof BlockStem)) return null;
+ return new EIGStemBucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGStemBucket(nbt);
+ }
+ }
+
+ private boolean isValid = false;
+ private EIGDropTable drops = new EIGDropTable();
+
+ private EIGStemBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ super(input, 1, null);
+ recalculateDrops(greenhouse);
+ }
+
+ private EIGStemBucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && !this.drops.isEmpty();
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ if (this.drops != null) {
+ nbt.setTag("drops", this.drops.save());
+ }
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ this.drops.addTo(tracker, multiplier * this.seedCount);
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ /**
+ * Attempts to predetermine what item the stem crop will drop.
+ *
+ * @param greenhouse The greenhouse that houses this bucket.
+ */
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ Item item = this.seed.getItem();
+ if (!(item instanceof IPlantable)) return;
+ Block stemBlock = ((IPlantable) item).getPlant(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ 0,
+ 0,
+ 0);
+ if (!(stemBlock instanceof BlockStem)) return;
+ Block cropBlock = ((IBlockStemAccesor) stemBlock).getCropBlock();
+ if (cropBlock == null || cropBlock == Blocks.air) return;
+ // if we know some crops needs a specific metadata, remap here
+ int metadata = 0;
+
+ EIGDropTable drops = new EIGDropTable();
+
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ // simulate 1 round of drops
+ ArrayList<ItemStack> blockDrops = cropBlock.getDrops(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ greenhouse.getBaseMetaTileEntity()
+ .getXCoord(),
+ greenhouse.getBaseMetaTileEntity()
+ .getYCoord(),
+ greenhouse.getBaseMetaTileEntity()
+ .getZCoord(),
+ metadata,
+ 0);
+ if (blockDrops == null || blockDrops.isEmpty()) continue;
+ // if the droped item is a block that places itself, assume this is the only possible drop
+ // eg: pumpkin, redlon
+ if (i == 0 && blockDrops.size() == 1) {
+ ItemStack drop = blockDrops.get(0);
+ if (drop != null && drop.stackSize >= 1 && drop.getItem() == Item.getItemFromBlock(cropBlock)) {
+ drops.addDrop(drop, drop.stackSize);
+ break;
+ }
+ }
+ // else append all the drops
+ for (ItemStack drop : blockDrops) {
+ drops.addDrop(drop, drop.stackSize / (double) NUMBER_OF_DROPS_TO_SIMULATE);
+ }
+ }
+ // check that we did in fact drop something.s
+ if (drops.isEmpty()) return;
+
+ // all checks passed we are good to go
+ this.drops = drops;
+ this.isValid = true;
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java
new file mode 100644
index 0000000000..6c12373779
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java
@@ -0,0 +1,95 @@
+package kubatech.tileentity.gregtech.multiblock.eigmodes;
+
+import static kubatech.api.utils.StringUtils.voltageTooltipFormatted;
+import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_ACCELERATOR_TIER;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGIC2Mode extends EIGMode {
+
+ public static final EIGIC2Mode instance = new EIGIC2Mode();
+
+ @Override
+ public int getUIIndex() {
+ return 1;
+ }
+
+ @Override
+ public String getName() {
+ return "IC2";
+ }
+
+ @Override
+ public int getMinVoltageTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getMinGlassTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getStartingSlotCount() {
+ return 4;
+ }
+
+ @Override
+ public int getSlotPerTierMultiplier() {
+ return 4;
+ }
+
+ @Override
+ public int getSeedCapacityPerSlot() {
+ return 1;
+ }
+
+ @Override
+ public int getWeedEXMultiplier() {
+ return 5;
+ }
+
+ @Override
+ public int getMaxFertilizerUsagePerSeed() {
+ return 40;
+ }
+
+ @Override
+ public double getFertilizerBoost() {
+ return 0.1d;
+ }
+
+ @Override
+ public GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder) {
+ String minVoltageTier = voltageTooltipFormatted(this.getMinVoltageTier());
+ String minGlassTier = voltageTooltipFormatted(this.getMinGlassTier());
+
+ int acceleration = (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER);
+
+ double fertilizerBonusMultiplier = this.getFertilizerBoost() * 100;
+ String fertilizerBonus = String.format("%.0f%%", fertilizerBonusMultiplier);
+
+ return builder.addInfo("---------------------- IC2 CROPS ---------------------")
+ .addInfo("Minimal voltage tier: " + minVoltageTier)
+ .addInfo("Minimal glass tier: " + minGlassTier)
+ .addInfo("Starting with " + this.getStartingSlotCount() + " slot")
+ .addInfo(
+ "Every tier past " + minVoltageTier + ", slots are multiplied by " + this.getSlotPerTierMultiplier())
+ .addInfo("Every slot adds " + this.getSeedCapacityPerSlot() + " seed to the total seed capacity")
+ .addInfo("Process time: 5 sec")
+ .addInfo("All crops are accelerated by x" + acceleration + " times")
+ .addInfo("Can consume up to " + this.getMaxFertilizerUsagePerSeed() + " fertilizer per seed per cycle")
+ .addInfo("Boost per fertilizer: " + fertilizerBonus)
+ .addInfo("Weed-EX 9000 consumption is multiplied by " + this.getWeedEXMultiplier());
+ }
+
+ @Override
+ public int getSlotCount(int machineTier) {
+ int tierAboveMin = machineTier - this.getMinVoltageTier();
+ if (tierAboveMin < 0) return 0;
+ return 4 << (2 * (tierAboveMin));
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java
new file mode 100644
index 0000000000..a1c5fcf393
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java
@@ -0,0 +1,91 @@
+package kubatech.tileentity.gregtech.multiblock.eigmodes;
+
+import static kubatech.api.utils.StringUtils.voltageTooltipFormatted;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGNormalMode extends EIGMode {
+
+ public static final EIGNormalMode instance = new EIGNormalMode();
+
+ @Override
+ public int getUIIndex() {
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return "normal";
+ }
+
+ @Override
+ public int getMinVoltageTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_REGULAR_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getMinGlassTier() {
+ return 0;
+ }
+
+ @Override
+ public int getStartingSlotCount() {
+ return 1;
+ }
+
+ @Override
+ public int getSlotPerTierMultiplier() {
+ return 2;
+ }
+
+ @Override
+ public int getSeedCapacityPerSlot() {
+ return 64;
+ }
+
+ @Override
+ public int getWeedEXMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public int getMaxFertilizerUsagePerSeed() {
+ return 2;
+ }
+
+ @Override
+ public double getFertilizerBoost() {
+ return 2.0d;
+ }
+
+ @Override
+ public GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder) {
+ String minVoltageTier = voltageTooltipFormatted(this.getMinVoltageTier());
+ String minVoltageTierMinus1 = voltageTooltipFormatted(this.getMinVoltageTier() - 1);
+
+ double fertilizerBonusMultiplier = this.getFertilizerBoost() * 100;
+ String fertilizerBonus = String.format("%.0f%%", fertilizerBonusMultiplier);
+
+ return builder.addInfo("-------------------- NORMAL CROPS --------------------")
+ .addInfo("Minimal voltage tier: " + minVoltageTier)
+ .addInfo("Starting with " + this.getStartingSlotCount() + " slot")
+ .addInfo(
+ "Every tier past " + minVoltageTier + ", slots are multiplied by " + this.getSlotPerTierMultiplier())
+ .addInfo("Every slot adds " + this.getSeedCapacityPerSlot() + " seed to the total seed capacity")
+ .addInfo("Base process time: 5 sec")
+ .addInfo("Process time is divided by number of tiers past " + minVoltageTierMinus1 + " (Minimum 1 sec)")
+ .addInfo("All crops are grown at the end of the operation")
+ .addInfo("Does not drop seeds")
+ .addInfo("Can consume up to " + this.getMaxFertilizerUsagePerSeed() + " fertilizer per seed per cycle")
+ .addInfo("Boost per fertilizer: " + fertilizerBonus);
+ }
+
+ @Override
+ public int getSlotCount(int machineTier) {
+ int tierAboveMin = machineTier - this.getMinVoltageTier();
+ if (tierAboveMin < 0) return 0;
+ return (1 << tierAboveMin);
+ }
+}