From 568962ca584b6b9ee1bc70acc769ec96e5caa18f Mon Sep 17 00:00:00 2001 From: GlodBlock <1356392126@qq.com> Date: Sun, 9 Jul 2023 14:00:04 +0800 Subject: [PATCH] init --- .gitattributes | 2 + .gitignore | 24 + LICENSE.txt | 165 ++++++ README.md | 50 ++ build.gradle | 137 +++++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++++++ gradlew.bat | 84 +++ logo.png | Bin 0 -> 11939 bytes .../java/com/glodblock/github/FluidCraft.java | 141 +++++ .../github/client/GuiFCPriority.java | 67 +++ .../github/client/GuiFluidDualInterface.java | 49 ++ .../github/client/GuiFluidPacketDecoder.java | 15 + .../client/GuiFluidPatternTerminal.java | 166 ++++++ .../github/client/GuiIngredientBuffer.java | 59 ++ .../github/client/GuiItemDualInterface.java | 93 +++ .../client/GuiLargeIngredientBuffer.java | 60 ++ .../github/client/button/BlitMap.java | 52 ++ .../github/client/button/FCToggleButton.java | 98 +++ .../client/container/ContainerFCPriority.java | 69 +++ .../ContainerFluidDualInterface.java | 82 +++ .../ContainerFluidPacketDecoder.java | 23 + .../ContainerFluidPatternTerminal.java | 558 ++++++++++++++++++ .../container/ContainerIngredientBuffer.java | 46 ++ .../container/ContainerItemDualInterface.java | 121 ++++ .../ContainerLargeIngredientBuffer.java | 46 ++ .../model/FluidEncodedPatternModel.java | 66 +++ .../github/client/model/FluidPacketModel.java | 183 ++++++ .../client/render/DropColourHandler.java | 61 ++ .../client/render/RenderIngredientBuffer.java | 55 ++ .../github/client/slot/SlotSingleItem.java | 114 ++++ .../common/block/BlockDualInterface.java | 81 +++ .../common/block/BlockFluidDiscretizer.java | 14 + .../common/block/BlockFluidPacketDecoder.java | 43 ++ .../common/block/BlockIngredientBuffer.java | 64 ++ .../block/BlockLargeIngredientBuffer.java | 64 ++ .../github/common/item/ItemFluidDrop.java | 140 +++++ .../common/item/ItemFluidEncodedPattern.java | 206 +++++++ .../github/common/item/ItemFluidPacket.java | 130 ++++ .../common/item/ItemPartDualInterface.java | 41 ++ .../item/ItemPartFluidPatternTerminal.java | 41 ++ .../common/me/DualityDualInterface.java | 204 +++++++ .../github/common/part/PartDualInterface.java | 261 ++++++++ .../common/part/PartFluidPatternTerminal.java | 175 ++++++ .../github/common/tile/TileDualInterface.java | 327 ++++++++++ .../common/tile/TileFluidDiscretizer.java | 288 +++++++++ .../common/tile/TileFluidPacketDecoder.java | 125 ++++ .../github/common/tile/TileSimpleBuffer.java | 102 ++++ .../github/coreutil/ExtendedInterface.java | 29 + .../github/handler/ButtonMouseHandler.java | 49 ++ .../github/handler/ClientRegistryHandler.java | 66 +++ .../github/handler/RegistryHandler.java | 106 ++++ .../github/handler/TankMouseHandler.java | 39 ++ .../builder/RecipeTransferBuilder.java | 138 +++++ .../github/integration/jei/FCJeiPlugin.java | 26 + ...dPatternTerminalRecipeTransferHandler.java | 50 ++ .../github/interfaces/AeStackInventory.java | 19 + .../github/interfaces/ConfigData.java | 9 + .../github/interfaces/HasCustomModel.java | 9 + .../github/interfaces/PatternConsumer.java | 10 + .../github/interfaces/SlotFluid.java | 14 + .../github/interfaces/TankDumpable.java | 9 + .../FluidConvertingInventoryAdaptor.java | 369 ++++++++++++ .../github/loader/ChannelLoader.java | 44 ++ .../com/glodblock/github/loader/FCBlocks.java | 37 ++ .../com/glodblock/github/loader/FCItems.java | 49 ++ .../github/mixins/AESubScreenMixin.java | 40 ++ .../github/mixins/ApiCraftingMixin.java | 33 ++ .../mixins/CraftConfirmContainerMixin.java | 35 ++ .../CraftConfirmTableRendererMixin.java | 43 ++ .../github/mixins/CraftingCpuMixin.java | 136 +++++ .../github/mixins/CraftingGridCacheMixin.java | 20 + .../CraftingStatusTableRendererMixin.java | 44 ++ .../github/mixins/CraftingTreeNodeMixin.java | 87 +++ .../github/mixins/DualityInterfaceMixin.java | 84 +++ .../mixins/EncodedPatternBakedModelMixin.java | 31 + .../github/mixins/InterfaceSlotMixin.java | 32 + .../InterfaceTerminalContainerMixin.java | 98 +++ .../github/mixins/PatternSlotPacketMixin.java | 31 + .../mixins/RestrictedInputSlotMixin.java | 32 + .../com/glodblock/github/mixins/TMixin.java | 32 + .../github/network/NetworkManager.java | 16 + .../network/packets/CPacketDumpTank.java | 51 ++ .../packets/CPacketFluidCraftBtns.java | 86 +++ .../network/packets/CPacketLoadPattern.java | 106 ++++ .../github/network/packets/IMessage.java | 18 + .../com/glodblock/github/util/Ae2Reflect.java | 178 ++++++ .../github/util/Ae2ReflectClient.java | 60 ++ .../com/glodblock/github/util/ConfigSet.java | 38 ++ .../com/glodblock/github/util/FCUtil.java | 183 ++++++ .../github/util/FluidPatternDetails.java | 152 +++++ .../github/util/FluidRenderUtils.java | 124 ++++ .../com/glodblock/github/util/HashUtil.java | 35 ++ .../github/util/InvalidFCPatternHelper.java | 92 +++ .../github/util/ModAndClassUtil.java | 30 + .../github/util/MouseRegionManager.java | 84 +++ .../com/glodblock/github/util/NameConst.java | 57 ++ src/main/resources/META-INF/ae2fc_at.cfg | 1 + src/main/resources/META-INF/mods.toml | 48 ++ .../ae2fc/blockstates/dual_interface.json | 48 ++ .../ae2fc/blockstates/fluid_discretizer.json | 7 + .../blockstates/fluid_packet_decoder.json | 7 + .../ae2fc/blockstates/ingredient_buffer.json | 7 + .../blockstates/large_ingredient_buffer.json | 7 + .../resources/assets/ae2fc/lang/en_us.json | 57 ++ .../assets/ae2fc/models/block/burette.json | 12 + .../ae2fc/models/block/dual_interface.json | 6 + .../models/block/dual_interface_oriented.json | 12 + .../ae2fc/models/block/fluid_assembler.json | 6 + .../ae2fc/models/block/fluid_discretizer.json | 6 + .../models/block/fluid_level_maintainer.json | 12 + .../models/block/fluid_packet_decoder.json | 6 + .../models/block/fluid_pattern_encoder.json | 12 + .../ae2fc/models/block/ingredient_buffer.json | 6 + .../models/block/large_ingredient_buffer.json | 6 + .../assets/ae2fc/models/item/burette.json | 3 + .../item/dense_craft_encoded_pattern.json | 6 + .../ae2fc/models/item/dual_interface.json | 3 + .../ae2fc/models/item/fluid_assembler.json | 3 + .../ae2fc/models/item/fluid_discretizer.json | 3 + .../assets/ae2fc/models/item/fluid_drop.json | 6 + .../models/item/fluid_encoded_pattern.json | 4 + .../item/fluid_encoded_pattern_base.json | 6 + .../models/item/fluid_level_maintainer.json | 3 + .../ae2fc/models/item/fluid_packet.json | 8 + .../models/item/fluid_packet_decoder.json | 3 + .../models/item/fluid_pattern_encoder.json | 3 + .../ae2fc/models/item/ingredient_buffer.json | 3 + .../models/item/large_ingredient_buffer.json | 3 + .../models/item/part_dual_interface.json | 106 ++++ .../item/part_fluid_pattern_ex_terminal.json | 9 + .../item/part_fluid_pattern_terminal.json | 9 + .../models/part/f_pattern_ex_term_off.json | 8 + .../models/part/f_pattern_ex_term_on.json | 76 +++ .../ae2fc/models/part/f_pattern_term_off.json | 8 + .../ae2fc/models/part/f_pattern_term_on.json | 8 + .../ae2fc/models/part/interface_base.json | 104 ++++ .../models/part/interface_has_channel.json | 54 ++ .../ae2fc/models/part/interface_off.json | 33 ++ .../ae2fc/models/part/interface_on.json | 54 ++ .../ae2fc/textures/blocks/burette_side.png | Bin 0 -> 933 bytes .../ae2fc/textures/blocks/burette_top.png | Bin 0 -> 2128 bytes .../ae2fc/textures/blocks/fluid_assembler.png | Bin 0 -> 361 bytes .../textures/blocks/fluid_discretizer.png | Bin 0 -> 527 bytes .../blocks/fluid_level_maintainer.png | Bin 0 -> 194 bytes .../blocks/fluid_level_maintainer_side.png | Bin 0 -> 105 bytes .../textures/blocks/fluid_packet_decoder.png | Bin 0 -> 2450 bytes .../blocks/fluid_pattern_encoder_bottom.png | Bin 0 -> 2028 bytes .../blocks/fluid_pattern_encoder_side.png | Bin 0 -> 2159 bytes .../blocks/fluid_pattern_encoder_top.png | Bin 0 -> 1919 bytes .../textures/blocks/ingredient_buffer.png | Bin 0 -> 6180 bytes .../ae2fc/textures/blocks/interface.png | Bin 0 -> 546 bytes .../textures/blocks/interface_alternate.png | Bin 0 -> 545 bytes .../blocks/interface_alternate_arrow.png | Bin 0 -> 575 bytes .../blocks/large_ingredient_buffer.png | Bin 0 -> 383 bytes .../items/dense_craft_encoded_pattern.png | Bin 0 -> 379 bytes .../ae2fc/textures/items/fluid_drop.png | Bin 0 -> 1482 bytes .../textures/items/fluid_encoded_pattern.png | Bin 0 -> 392 bytes .../ae2fc/textures/items/fluid_packet.png | Bin 0 -> 253 bytes .../textures/items/fluid_packet_mask.png | Bin 0 -> 99 bytes .../parts/pattern_terminal_bright.png | Bin 0 -> 1488 bytes .../textures/parts/pattern_terminal_dark.png | Bin 0 -> 1427 bytes .../parts/pattern_terminal_ex_bright.png | Bin 0 -> 133 bytes .../parts/pattern_terminal_ex_dark.png | Bin 0 -> 1427 bytes .../parts/pattern_terminal_ex_medium.png | Bin 0 -> 147 bytes .../parts/pattern_terminal_medium.png | Bin 0 -> 1506 bytes .../screens/dual_fluid_interface.json | 90 +++ .../screens/dual_item_interface.json | 73 +++ .../screens/fc_priority.json | 49 ++ .../screens/fluid_packet_decoder.json | 26 + .../screens/fluid_pattern_terminal.json | 104 ++++ .../screens/ingredient_buffer.json | 26 + .../screens/large_ingredient_buffer.json | 26 + .../textures/gui/burette.png | Bin 0 -> 6072 bytes .../textures/gui/fluid_assembler.png | Bin 0 -> 1772 bytes .../textures/gui/fluid_assembler2.png | Bin 0 -> 1771 bytes .../textures/gui/fluid_assembler3.png | Bin 0 -> 1771 bytes .../textures/gui/fluid_assembler4.png | Bin 0 -> 1732 bytes .../textures/gui/fluid_level_maintainer.png | Bin 0 -> 1602 bytes .../textures/gui/fluid_packet_decoder.png | Bin 0 -> 5240 bytes .../textures/gui/fluid_pattern_encoder.png | Bin 0 -> 7072 bytes .../textures/gui/ingredient_buffer.png | Bin 0 -> 1614 bytes .../textures/gui/large_ingredient_buffer.png | Bin 0 -> 1382 bytes .../textures/gui/states.png | Bin 0 -> 2082 bytes .../data/ae2fc/recipes/dual_interface.json | 15 + .../ae2fc/recipes/dual_interface_alter.json | 11 + .../data/ae2fc/recipes/fluid_discretizer.json | 29 + .../ae2fc/recipes/fluid_packet_decoder.json | 29 + .../data/ae2fc/recipes/ingredient_buffer.json | 32 + .../recipes/large_ingredient_buffer.json | 23 + .../ae2fc/recipes/part_dual_interface.json | 11 + .../recipes/part_fluid_pattern_terminal.json | 14 + src/main/resources/logo.png | Bin 0 -> 11939 bytes src/main/resources/mixins.ae2fc.json | 25 + src/main/resources/pack.mcmeta | 6 + 197 files changed, 9711 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 logo.png create mode 100644 src/main/java/com/glodblock/github/FluidCraft.java create mode 100644 src/main/java/com/glodblock/github/client/GuiFCPriority.java create mode 100644 src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java create mode 100644 src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java create mode 100644 src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java create mode 100644 src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/client/GuiItemDualInterface.java create mode 100644 src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/client/button/BlitMap.java create mode 100644 src/main/java/com/glodblock/github/client/button/FCToggleButton.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java create mode 100644 src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java create mode 100644 src/main/java/com/glodblock/github/client/model/FluidPacketModel.java create mode 100644 src/main/java/com/glodblock/github/client/render/DropColourHandler.java create mode 100644 src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockDualInterface.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java create mode 100644 src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java create mode 100644 src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java create mode 100644 src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java create mode 100644 src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java create mode 100644 src/main/java/com/glodblock/github/common/me/DualityDualInterface.java create mode 100644 src/main/java/com/glodblock/github/common/part/PartDualInterface.java create mode 100644 src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java create mode 100644 src/main/java/com/glodblock/github/common/tile/TileDualInterface.java create mode 100644 src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java create mode 100644 src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java create mode 100644 src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java create mode 100644 src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java create mode 100644 src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java create mode 100644 src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java create mode 100644 src/main/java/com/glodblock/github/handler/RegistryHandler.java create mode 100644 src/main/java/com/glodblock/github/handler/TankMouseHandler.java create mode 100644 src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java create mode 100644 src/main/java/com/glodblock/github/interfaces/AeStackInventory.java create mode 100644 src/main/java/com/glodblock/github/interfaces/ConfigData.java create mode 100644 src/main/java/com/glodblock/github/interfaces/HasCustomModel.java create mode 100644 src/main/java/com/glodblock/github/interfaces/PatternConsumer.java create mode 100644 src/main/java/com/glodblock/github/interfaces/SlotFluid.java create mode 100644 src/main/java/com/glodblock/github/interfaces/TankDumpable.java create mode 100644 src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java create mode 100644 src/main/java/com/glodblock/github/loader/ChannelLoader.java create mode 100644 src/main/java/com/glodblock/github/loader/FCBlocks.java create mode 100644 src/main/java/com/glodblock/github/loader/FCItems.java create mode 100644 src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java create mode 100644 src/main/java/com/glodblock/github/mixins/TMixin.java create mode 100644 src/main/java/com/glodblock/github/network/NetworkManager.java create mode 100644 src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java create mode 100644 src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java create mode 100644 src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java create mode 100644 src/main/java/com/glodblock/github/network/packets/IMessage.java create mode 100644 src/main/java/com/glodblock/github/util/Ae2Reflect.java create mode 100644 src/main/java/com/glodblock/github/util/Ae2ReflectClient.java create mode 100644 src/main/java/com/glodblock/github/util/ConfigSet.java create mode 100644 src/main/java/com/glodblock/github/util/FCUtil.java create mode 100644 src/main/java/com/glodblock/github/util/FluidPatternDetails.java create mode 100644 src/main/java/com/glodblock/github/util/FluidRenderUtils.java create mode 100644 src/main/java/com/glodblock/github/util/HashUtil.java create mode 100644 src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java create mode 100644 src/main/java/com/glodblock/github/util/ModAndClassUtil.java create mode 100644 src/main/java/com/glodblock/github/util/MouseRegionManager.java create mode 100644 src/main/java/com/glodblock/github/util/NameConst.java create mode 100644 src/main/resources/META-INF/ae2fc_at.cfg create mode 100644 src/main/resources/META-INF/mods.toml create mode 100644 src/main/resources/assets/ae2fc/blockstates/dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json create mode 100644 src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json create mode 100644 src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/lang/en_us.json create mode 100644 src/main/resources/assets/ae2fc/models/block/burette.json create mode 100644 src/main/resources/assets/ae2fc/models/block/dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json create mode 100644 src/main/resources/assets/ae2fc/models/block/fluid_assembler.json create mode 100644 src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json create mode 100644 src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json create mode 100644 src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json create mode 100644 src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json create mode 100644 src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/models/item/burette.json create mode 100644 src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json create mode 100644 src/main/resources/assets/ae2fc/models/item/dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_assembler.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_drop.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_packet.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json create mode 100644 src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json create mode 100644 src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/models/item/part_dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json create mode 100644 src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json create mode 100644 src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json create mode 100644 src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json create mode 100644 src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json create mode 100644 src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json create mode 100644 src/main/resources/assets/ae2fc/models/part/interface_base.json create mode 100644 src/main/resources/assets/ae2fc/models/part/interface_has_channel.json create mode 100644 src/main/resources/assets/ae2fc/models/part/interface_off.json create mode 100644 src/main/resources/assets/ae2fc/models/part/interface_on.json create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/burette_side.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/burette_top.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_assembler.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_top.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/interface.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png create mode 100644 src/main/resources/assets/ae2fc/textures/blocks/large_ingredient_buffer.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/dense_craft_encoded_pattern.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/fluid_drop.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/fluid_encoded_pattern.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/fluid_packet.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/fluid_packet_mask.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_dark.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_bright.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_dark.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.png create mode 100644 src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png create mode 100644 src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/fc_priority.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json create mode 100644 src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/burette.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler2.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler3.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_level_maintainer.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_packet_decoder.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png create mode 100644 src/main/resources/assets/appliedenergistics2/textures/gui/states.png create mode 100644 src/main/resources/data/ae2fc/recipes/dual_interface.json create mode 100644 src/main/resources/data/ae2fc/recipes/dual_interface_alter.json create mode 100644 src/main/resources/data/ae2fc/recipes/fluid_discretizer.json create mode 100644 src/main/resources/data/ae2fc/recipes/fluid_packet_decoder.json create mode 100644 src/main/resources/data/ae2fc/recipes/ingredient_buffer.json create mode 100644 src/main/resources/data/ae2fc/recipes/large_ingredient_buffer.json create mode 100644 src/main/resources/data/ae2fc/recipes/part_dual_interface.json create mode 100644 src/main/resources/data/ae2fc/recipes/part_fluid_pattern_terminal.json create mode 100644 src/main/resources/logo.png create mode 100644 src/main/resources/mixins.ae2fc.json create mode 100644 src/main/resources/pack.mcmeta diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..44062dc5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +run +classes +logs/debug.log +logs/latest.log diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 000000000..1f1fa328c --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ + +Port with permission from phantamanta44. + +# AE2 Fluid Crafting Rework + +[![Downloads](https://cf.way2muchnoise.eu/full_623955_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/ae2-fluid-crafting-rework) ![MCVsrsion](https://cf.way2muchnoise.eu/versions/623955.svg) + +Put fluids in the pattern! + +AE2 autocrafting is amazing and everyone loves it, but it is always becoming painful when dealing with fluids. You have to put fluids in a container or use a dummy item to write patterns with fluids. + +That's because AE2 doesn't support fluid as valid crafting ingredients before 1.16, so it can't handle fluids directly. + +However, it is changed now! With **AE2 Fluid Crafting** you can write patterns with fluids freely. Your AE system can output and accept fluids like items without worrying about how to handle these fluid cells. + +This is a rework and ported version of [ae2-fluid-crafting](https://github.com/phantamanta44/ae2-fluid-crafting) + +## Features + + - You can code fluid patterns on fluid terminal directly. + - Mult/Div/Add/Sub Button also works on fluid. + - An extended fluid terminal with 16 inputs and 4 outputs. + - You can add or decrease fluid's amount by clicking with container. + - Fluid Pattern can display its contents when put on pattern terminal. + - [1.12.2] Fix the fluid amount display error when removing fluid. + - [1.12.2] Upgrade cards can be inserted in Dual Interface. + - [1.12.2] Fluid pattern is searchable in interface terminal. + - [1.12.2] Fix crash when using block mode, fluid will also be considered as blockable. + - [1.12.2] Fluid export bus supports crafting card. + +## Installation + +### 1.7.10 +Any version of AE2(Both Official AE2 and GTNH edition AE2 works). + +**Extra Cells isn't needed** + +### 1.12.2 +Unofficial AE2([PAE2](https://www.curseforge.com/minecraft/mc-mods/ae2-extended-life)). + +You can directly upgrade origin AE2FC2 in your old save. + +Official AE2 isn't supported, you can use origin [AE2FC](https://github.com/phantamanta44/ae2-fluid-crafting) if you are playing with Official AE2. + +## How to Use It +https://github.com/GlodBlock/AE2FluidCraft-Rework/wiki + +## Credited Works + +E. Geng(@phantamanta44) and KilaBash (@Yefancy) - Their amazing origin work in 1.12. diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..65e2f8de9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,137 @@ +buildscript { + repositories { + maven { + url 'https://maven.minecraftforge.net/' + } + maven { + name 'Scala CI dependencies' + url 'https://repo1.maven.org/maven2/' + } + maven { + name "forge" + url "https://files.minecraftforge.net/maven" + } + maven { + name "mixins" + url "https://repo.spongepowered.org/repository/maven-public/" + } + mavenLocal() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true + classpath "org.spongepowered:mixingradle:0.7-SNAPSHOT" + } +} + +apply plugin: 'net.minecraftforge.gradle' +apply plugin: 'idea' +apply plugin: 'org.spongepowered.mixin' + +version = "1.0.0-a" +group = "ae2fc" +archivesBaseName = "Fluid Craft for AE2" + +sourceCompatibility = targetCompatibility = '1.8' +compileJava { + sourceCompatibility = targetCompatibility = '1.8' +} + + +// Generate a fixed tsrg file after generating the default tsrg file +createMcpToSrg { + outputs.upToDateWhen {false} + doLast { + fixFG5TsrgForMixinAP(output.get().asFile, file("${buildDir}/fixMcpToSrg/output.tsrg")) + } +} + +// Function that actually fixes the TSRG file +static def fixFG5TsrgForMixinAP(File inFile, File outFile) { + // Make directory if needed + outFile.parentFile.mkdirs() + + try (Scanner scanner = new Scanner(inFile); PrintWriter out = new PrintWriter(outFile)) { + boolean firstLine = true + while (scanner.hasNextLine()) { + String next = scanner.nextLine() + + // Skip first 'tsrg left right' header line + if (firstLine) { + firstLine = false + continue + } + + // Skip 'static' indicators + if (next.trim() == "static") { + continue + } + + // Export line otherwise + out.println(next) + } + } +} + +repositories { + maven { url 'https://dvs1.progwml6.com/files/maven/' } + maven { url 'https://cursemaven.com/' } + maven { url "https://www.cursemaven.com" } +} + +dependencies { + minecraft([ + group : "net.minecraftforge", + name : "forge", + version: "1.16.5-36.1.17" + ]) + annotationProcessor 'org.spongepowered:mixin:0.8.2:processor' + implementation fg.deobf('mezz.jei:jei-1.16.5:7.7.0.98') //jei + implementation fg.deobf('curse.maven:ae2-extended-life-223794:3608871') //ae2 + implementation fg.deobf('curse.maven:packagedauto-308380:4478954') //pauto +} + +minecraft { + mappings channel: 'snapshot', version: '20210309-1.16.5' + accessTransformer = file('src/main/resources/META-INF/ae2fc_at.cfg') + runs { + client = { + properties "forge.logging.markers": "SCAN,REGISTRIES,REGISTRYDUMP" + properties "forge.logging.console.level": "debug" + properties "mixin.env.remapRefMap": "true" + properties "mixin.env.refMapRemappingFile": "${projectDir}/build/createSrgToMcp/output.srg".toString() + workingDirectory project.file("run").canonicalPath + arg "-mixin.config=" + "mixins.ae2fc.json" + source sourceSets.main + } + server = { + properties "forge.logging.markers": "SCAN,REGISTRIES,REGISTRYDUMP" + properties "forge.logging.console.level": "debug" + properties "mixin.env.remapRefMap": "true" + properties "mixin.env.refMapRemappingFile": "${projectDir}/build/createSrgToMcp/output.srg".toString() + workingDirectory project.file("run").canonicalPath + arg "-mixin.config=" + "mixins.ae2fc.json" + source sourceSets.main + } + } +} + +jar { + manifest { + attributes([ + "Specification-Title": "FluidCraftForAE2", + "Specification-Vendor": "GlodBlock", + "Specification-Version": "1", + "Implementation-Title": "${archivesBaseName}", + "Implementation-Version": "${version}", + "Implementation-Vendor": "GlodBlock", + "TweakClass": "org.spongepowered.asm.launch.MixinTweaker", + "TweakOrder": 0, + "MixinConfigs": "mixins.ae2fc.json" + ]) + } +} + +mixin { + reobfSrgFile = file("${buildDir}/fixMcpToSrg/output.tsrg") + add sourceSets.main, "mixins.ae2fc.refmap.json" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..e6a23a955 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxPermSize=512m diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..490fda8577df6c95960ba7077c43220e5bb2c0d9 GIT binary patch literal 58694 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..d06389f3b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..129cbc24fe3af47321d64d827f5a26ae9a43aced GIT binary patch literal 11939 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 literal 0 HcmV?d00001 diff --git a/src/main/java/com/glodblock/github/FluidCraft.java b/src/main/java/com/glodblock/github/FluidCraft.java new file mode 100644 index 000000000..0ee319350 --- /dev/null +++ b/src/main/java/com/glodblock/github/FluidCraft.java @@ -0,0 +1,141 @@ +package com.glodblock.github; + +import appeng.api.config.Upgrades; +import appeng.api.definitions.IItemDefinition; +import appeng.api.features.AEFeature; +import appeng.api.util.AEColor; +import appeng.core.Api; +import appeng.core.features.ItemDefinition; +import appeng.recipes.game.DisassembleRecipe; +import com.glodblock.github.client.model.FluidEncodedPatternModel; +import com.glodblock.github.client.render.DropColourHandler; +import com.glodblock.github.client.render.RenderIngredientBuffer; +import com.glodblock.github.common.block.BlockIngredientBuffer; +import com.glodblock.github.common.block.BlockLargeIngredientBuffer; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.handler.ClientRegistryHandler; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.loader.ChannelLoader; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.Ae2Reflect; +import com.glodblock.github.util.FCUtil; +import com.glodblock.github.util.ModAndClassUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.item.Item; +import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.item.crafting.SpecialRecipeSerializer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Objects; + +@Mod(FluidCraft.MODID) +public class FluidCraft { + + public static final String MODID = "ae2fc"; + + public static Logger log; + public static FluidCraft INSTANCE; + public FluidCraft() { + assert INSTANCE == null; + INSTANCE = this; + FCItems.init(RegistryHandler.INSTANCE); + FCBlocks.init(RegistryHandler.INSTANCE); + IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> bus.register(ClientRegistryHandler.INSTANCE)); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> bus.register(DropColourHandler.INSTANCE)); + bus.register(RegistryHandler.INSTANCE); + bus.addListener(this::commonSetup); + bus.addListener(this::clientSetup); + bus.addListener(this::imcWait); + bus.addListener(this::imcProcess); + bus.addListener(this::finish); + ModAndClassUtil.init(); + } + + public void commonSetup(FMLCommonSetupEvent event) { + log = LogManager.getLogger(); + ChannelLoader.load(); + if (ModAndClassUtil.AUTO_P) { + //PackagedFluidCrafting.init(); + } + RegistryHandler.INSTANCE.onInit(); + IRecipeSerializer disassembleRecipe = DisassembleRecipe.SERIALIZER; + if (disassembleRecipe instanceof SpecialRecipeSerializer) { + Map map = Ae2Reflect.getDisassemblyNonCellMap( + (DisassembleRecipe) Ae2Reflect.getRecipeFactory((SpecialRecipeSerializer)disassembleRecipe).apply( + new ResourceLocation("appliedenergistics2", "disassemble") + ) + ); + map.put( + createItemDefn(FCItems.DENSE_ENCODED_PATTERN, AEFeature.PATTERNS), + Api.instance().definitions().materials().blankPattern() + ); + } + Upgrades.CRAFTING.registerItem(FCBlocks.DUAL_INTERFACE, 1); + Upgrades.CRAFTING.registerItem(FCItems.PART_DUAL_INTERFACE, 1); + } + + public void clientSetup(FMLClientSetupEvent event) { + ClientRegistryHandler.INSTANCE.onInit(); + ClientRegistry.bindTileEntityRenderer(FCUtil.getTileType(BlockIngredientBuffer.TileIngredientBuffer.class, FCBlocks.INGREDIENT_BUFFER), RenderIngredientBuffer::new); + ClientRegistry.bindTileEntityRenderer(FCUtil.getTileType(BlockLargeIngredientBuffer.TileLargeIngredientBuffer.class, FCBlocks.LARGE_INGREDIENT_BUFFER), RenderIngredientBuffer::new); + + Minecraft.getInstance().getItemColors().register((s, i) -> { + FluidStack fluid = ItemFluidDrop.getFluidStack(s); + return !fluid.isEmpty() ? DropColourHandler.INSTANCE.getColour(fluid) : 0xFFFFFFFF; + }, FCItems.FLUID_DROP); + Minecraft.getInstance().getItemColors().register((s, i) -> { + if (i == 0) { + return 0xFFFFFFFF; + } + FluidStack fluid = ItemFluidPacket.getFluidStack(s); + return !fluid.isEmpty() ? fluid.getFluid().getAttributes().getColor(fluid) : 0xFFFFFFFF; + }, FCItems.FLUID_PACKET); + Minecraft.getInstance().getItemColors().register((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_FLUID_PATTERN_TERMINAL); + Minecraft.getInstance().getItemColors().register((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_DUAL_INTERFACE); + Minecraft.getInstance().getItemColors().register(FluidEncodedPatternModel.PATTERN_ITEM_COLOR_HANDLER); + } + + public void imcWait(InterModEnqueueEvent event) { + + } + + public void imcProcess(InterModEnqueueEvent event) { + + } + + public void finish(FMLLoadCompleteEvent event) { + + } + + private static IItemDefinition createItemDefn(Item item, AEFeature... feature) { + return new ItemDefinition( + Objects.requireNonNull(item.getRegistryName()).toString(), + item, + feature.length > 0 ? + EnumSet.of(AEFeature.PATTERNS) : EnumSet.noneOf(AEFeature.class) + ); + } + + public static ResourceLocation resource(String id) { + return new ResourceLocation(MODID, id); + } + +} diff --git a/src/main/java/com/glodblock/github/client/GuiFCPriority.java b/src/main/java/com/glodblock/github/client/GuiFCPriority.java new file mode 100644 index 000000000..3ece43f5c --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFCPriority.java @@ -0,0 +1,67 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.NumberEntryType; +import appeng.client.gui.implementations.NumberEntryWidget; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.TabButton; +import appeng.core.sync.network.NetworkHandler; +import appeng.core.sync.packets.SwitchGuisPacket; +import com.glodblock.github.client.container.ContainerFCPriority; +import com.glodblock.github.common.tile.TileDualInterface; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketFluidCraftBtns; +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; + +import java.util.OptionalInt; + +public class GuiFCPriority extends AEBaseScreen { + private final ContainerType originGui; + private final TabButton back; + private final NumberEntryWidget priority; + + public GuiFCPriority(ContainerFCPriority container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + this.originGui = container.getPriorityHost().getContainerType(); + ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer(); + ItemStack dual; + if (container.getPriorityHost() instanceof TileDualInterface) { + dual = new ItemStack(FCBlocks.DUAL_INTERFACE); + } else { + dual = new ItemStack(FCItems.PART_DUAL_INTERFACE); + } + this.back = new TabButton(dual, dual.getDisplayName(), itemRender, btn -> + NetworkHandler.instance().sendToServer(new SwitchGuisPacket(this.originGui))); + this.priority = new NumberEntryWidget(NumberEntryType.PRIORITY); + this.priority.setTextFieldBounds(62, 57, 50); + this.priority.setMinValue(Integer.MIN_VALUE); + this.priority.setValue(this.container.getPriorityValue()); + this.priority.setOnChange(this::savePriority); + this.priority.setOnConfirm(() -> { + this.savePriority(); + this.back.onPress(); + }); + this.widgets.add("priority", this.priority); + this.widgets.add("back", this.back); + } + + private void savePriority() { + OptionalInt priority = this.priority.getIntValue(); + if (priority.isPresent()) { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("priority", priority.getAsInt())); + } + } + + public void drawBG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY, float partialTicks) { + super.drawBG(matrixStack, offsetX, offsetY, mouseX, mouseY, partialTicks); + this.priority.render(matrixStack, mouseX, mouseY, partialTicks); + } +} diff --git a/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java new file mode 100644 index 000000000..05329834d --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java @@ -0,0 +1,49 @@ +package com.glodblock.github.client; + +import appeng.client.gui.implementations.UpgradeableScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.TabButton; +import appeng.container.SlotSemantic; +import appeng.core.Api; +import appeng.core.localization.GuiText; +import appeng.core.sync.network.NetworkHandler; +import appeng.core.sync.packets.SwitchGuisPacket; +import appeng.fluids.client.gui.widgets.FluidSlotWidget; +import appeng.fluids.client.gui.widgets.FluidTankWidget; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.client.container.ContainerFCPriority; +import com.glodblock.github.client.container.ContainerFluidDualInterface; +import com.glodblock.github.client.container.ContainerItemDualInterface; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; + +public class GuiFluidDualInterface extends UpgradeableScreen { + + private final TabButton switchInterface; + private final TabButton priorityBtn; + + public GuiFluidDualInterface(ContainerFluidDualInterface container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + IAEFluidTank configFluids = this.container.getFluidConfigInventory(); + for(int i = 0; i < 6; ++i) { + this.addSlot(new FluidSlotWidget(configFluids, i), SlotSemantic.CONFIG); + } + IAEFluidTank fluidTank = this.container.getTanks(); + for(int i = 0; i < 6; ++i) { + this.widgets.add("tank" + (i + 1), new FluidTankWidget(fluidTank, i)); + } + ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer(); + ItemStack iconItem = Api.instance().definitions().blocks().iface().maybeStack(1).orElse(ItemStack.EMPTY); + switchInterface = new TabButton(iconItem, iconItem.getDisplayName(), itemRender, btn -> + NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerItemDualInterface.TYPE))); + this.widgets.add("switchInterface", switchInterface); + ItemStack iconWrench = Api.instance().definitions().items().certusQuartzWrench().maybeStack(1).orElse(ItemStack.EMPTY); + this.priorityBtn = new TabButton(iconWrench, GuiText.Priority.text(), itemRender, btn -> + NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFCPriority.TYPE))); + this.widgets.add("priorityBtn", priorityBtn); + } + +} diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java new file mode 100644 index 000000000..5d65b296b --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java @@ -0,0 +1,15 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.style.ScreenStyle; +import com.glodblock.github.client.container.ContainerFluidPacketDecoder; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.util.text.ITextComponent; + +public class GuiFluidPacketDecoder extends AEBaseScreen { + + public GuiFluidPacketDecoder(ContainerFluidPacketDecoder container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + } + +} diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java new file mode 100644 index 000000000..1db6c743e --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java @@ -0,0 +1,166 @@ +package com.glodblock.github.client; + +import appeng.api.config.ActionItems; +import appeng.client.gui.me.common.StackSizeRenderer; +import appeng.client.gui.me.items.ItemTerminalScreen; +import appeng.client.gui.style.Blitter; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.ActionButton; +import appeng.client.gui.widgets.TabButton; +import appeng.container.SlotSemantic; +import appeng.container.slot.FakeSlot; +import appeng.core.localization.GuiText; +import appeng.util.item.AEItemStack; +import com.glodblock.github.client.button.BlitMap; +import com.glodblock.github.client.button.FCToggleButton; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.client.slot.SlotSingleItem; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketFluidCraftBtns; +import com.glodblock.github.util.Ae2ReflectClient; +import com.glodblock.github.util.FluidRenderUtils; +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.block.Blocks; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; + +import javax.annotation.Nonnull; + +public class GuiFluidPatternTerminal extends ItemTerminalScreen { + private static final String MODES_TEXTURE = "guis/pattern_modes.png"; + private static final Blitter CRAFTING_MODE_BG = Blitter.texture(MODES_TEXTURE).src(0, 0, 126, 68); + private static final Blitter PROCESSING_MODE_BG = Blitter.texture(MODES_TEXTURE).src(0, 70, 126, 68); + private static final String SUBSTITUTION_DISABLE = "0"; + private static final String SUBSTITUTION_ENABLE = "1"; + private static final String CRAFTMODE_CRAFTING = "1"; + private static final String CRAFTMODE_PROCESSING = "0"; + private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getGuiStyle(this).getStackSizeRenderer(); + private final TabButton tabCraftButton; + private final TabButton tabProcessButton; + private final ActionButton substitutionsEnabledBtn; + private final ActionButton substitutionsDisabledBtn; + private final FCToggleButton combineBtn; + private final FCToggleButton fluidBtn; + + public GuiFluidPatternTerminal(ContainerFluidPatternTerminal container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + this.tabCraftButton = new TabButton( + new ItemStack(Blocks.CRAFTING_TABLE), GuiText.CraftingPattern.text(), this.itemRenderer, + btn -> toggleCraftMode(CRAFTMODE_PROCESSING)); + widgets.add("craftingPatternMode", this.tabCraftButton); + + this.tabProcessButton = new TabButton( + new ItemStack(Blocks.FURNACE), GuiText.ProcessingPattern.text(), this.itemRenderer, + btn -> toggleCraftMode(CRAFTMODE_CRAFTING)); + widgets.add("processingPatternMode", this.tabProcessButton); + + this.substitutionsEnabledBtn = new ActionButton( + ActionItems.ENABLE_SUBSTITUTION, act -> toggleSubstitutions(SUBSTITUTION_DISABLE)); + this.substitutionsEnabledBtn.setHalfSize(true); + widgets.add("substitutionsEnabled", this.substitutionsEnabledBtn); + + this.substitutionsDisabledBtn = new ActionButton( + ActionItems.DISABLE_SUBSTITUTION, act -> toggleSubstitutions(SUBSTITUTION_ENABLE)); + this.substitutionsDisabledBtn.setHalfSize(true); + widgets.add("substitutionsDisabled", this.substitutionsDisabledBtn); + + this.combineBtn = new FCToggleButton( + btn -> { + FCToggleButton fbtn = (FCToggleButton) btn; + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("combine", fbtn.getActive() == 1)); + }, BlitMap.NOT_COMBINE, BlitMap.COMBINE + ); + this.combineBtn.setHalfSize(true); + widgets.add("combineBtn", this.combineBtn); + + this.fluidBtn = new FCToggleButton( + btn -> { + FCToggleButton fbtn = (FCToggleButton) btn; + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("fluidFirst", fbtn.getActive() == 0)); + }, BlitMap.FLUID_FIRST, BlitMap.ITEM_FIRST + ); + this.fluidBtn.setHalfSize(true); + widgets.add("fluidBtn", this.fluidBtn); + + ActionButton clearBtn = new ActionButton(ActionItems.CLOSE, act -> clear()); + clearBtn.setHalfSize(true); + widgets.add("clearPattern", clearBtn); + + ActionButton encodeBtn = new ActionButton(ActionItems.ENCODE, act -> encode()); + widgets.add("encodePattern", encodeBtn); + } + + @Override + protected void updateBeforeRender() { + super.updateBeforeRender(); + if (this.container.isCraftingMode()) { + this.tabCraftButton.visible = true; + this.tabProcessButton.visible = false; + + if (this.container.substitute) { + this.substitutionsEnabledBtn.visible = true; + this.substitutionsDisabledBtn.visible = false; + } else { + this.substitutionsEnabledBtn.visible = false; + this.substitutionsDisabledBtn.visible = true; + } + this.fluidBtn.visible = false; + this.combineBtn.visible = false; + } else { + this.tabCraftButton.visible = false; + this.tabProcessButton.visible = true; + this.substitutionsEnabledBtn.visible = false; + this.substitutionsDisabledBtn.visible = false; + this.fluidBtn.visible = true; + this.combineBtn.visible = true; + } + + setSlotsHidden(SlotSemantic.CRAFTING_RESULT, !this.container.isCraftingMode()); + setSlotsHidden(SlotSemantic.PROCESSING_RESULT, this.container.isCraftingMode()); + } + + private void toggleCraftMode(String mode) { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", mode.equals("1"))); + } + + private void toggleSubstitutions(String mode) { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("substitute", mode.equals("1"))); + } + + private void encode() { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("encode")); + } + + private void clear() { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("clear")); + } + + @Override + protected void moveItems(MatrixStack matrices, Slot slot) { + if (!(slot instanceof FakeSlot && (FluidRenderUtils.renderFluidPacketIntoGuiSlot( + slot, slot.getStack(), stackSizeRenderer, font) || renderMEStyleSlot(slot, slot.getStack(), matrices)))) { + super.moveItems(matrices, slot); + } + } + + private boolean renderMEStyleSlot(Slot slot, @Nonnull ItemStack stack, MatrixStack matrices) { + if (slot instanceof FakeSlot && !stack.isEmpty() && !(stack.getItem() instanceof ItemFluidPacket)) { + super.moveItems(matrices, new SlotSingleItem(slot)); + if (stack.getCount() > 1) { + this.stackSizeRenderer.renderStackSize(font, AEItemStack.fromItemStack(stack).getStackSize(), false, slot.xPos, slot.yPos); + } + return true; + } + return false; + } + + @Override + public void drawBG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY, float partialTicks) { + super.drawBG(matrixStack, offsetX, offsetY, mouseX, mouseY, partialTicks); + Blitter modeBg = this.container.isCraftingMode() ? CRAFTING_MODE_BG : PROCESSING_MODE_BG; + modeBg.dest(this.guiLeft + 9, this.guiTop + this.ySize - 164).blit(matrixStack, this.getBlitOffset()); + } +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java new file mode 100644 index 000000000..46db5b6b2 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java @@ -0,0 +1,59 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.client.container.ContainerIngredientBuffer; +import com.glodblock.github.handler.ButtonMouseHandler; +import com.glodblock.github.handler.TankMouseHandler; +import com.glodblock.github.util.FluidRenderUtils; +import com.glodblock.github.util.MouseRegionManager; +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.PlayerContainer; +import net.minecraft.util.text.ITextComponent; + +public class GuiIngredientBuffer extends AEBaseScreen { + + private static final int TANK_X = 47, TANK_X_OFF = 22, TANK_Y = 18; + private static final int TANK_WIDTH = 16, TANK_HEIGHT = 74; + private final MouseRegionManager mouseRegions = new MouseRegionManager(this); + + public GuiIngredientBuffer(ContainerIngredientBuffer container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + for (int i = 0; i < 4; i++) { + mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + new TankMouseHandler(container.getTile().getFluidInventory(), i)); + mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7, + ButtonMouseHandler.dumpTank(container, i)); + } + } + + @Override + public boolean mouseClicked(double xCoord, double yCoord, int btn) { + if (mouseRegions.onClick(xCoord, yCoord, btn)) { + return super.mouseClicked(xCoord, yCoord, btn); + } + return true; + } + + @Override + public void drawFG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY) { + GlStateManager.color4f(1F, 1F, 1F, 1F); + IAEFluidTank fluidInv = container.getTile().getFluidInventory(); + assert minecraft != null; + minecraft.getTextureManager().bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE); + Tessellator tess = Tessellator.getInstance(); + BufferBuilder buf = tess.getBuffer(); + for (int i = 0; i < 4; i++) { + FluidRenderUtils.renderFluidIntoGui(tess, buf, TANK_X + i * TANK_X_OFF, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + fluidInv.getFluidInSlot(i), fluidInv.getTankCapacity(i)); + } + GlStateManager.color4f(1F, 1F, 1F, 1F); + mouseRegions.render(matrixStack, mouseX, mouseY); + } + +} diff --git a/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java new file mode 100644 index 000000000..c92674618 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java @@ -0,0 +1,93 @@ +package com.glodblock.github.client; + +import appeng.api.config.Settings; +import appeng.api.config.YesNo; +import appeng.client.gui.Icon; +import appeng.client.gui.implementations.UpgradeableScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.ServerSettingToggleButton; +import appeng.client.gui.widgets.SettingToggleButton; +import appeng.client.gui.widgets.TabButton; +import appeng.client.gui.widgets.ToggleButton; +import appeng.core.Api; +import appeng.core.localization.GuiText; +import appeng.core.sync.network.NetworkHandler; +import appeng.core.sync.packets.ConfigButtonPacket; +import appeng.core.sync.packets.SwitchGuisPacket; +import com.glodblock.github.client.button.BlitMap; +import com.glodblock.github.client.button.FCToggleButton; +import com.glodblock.github.client.container.ContainerFCPriority; +import com.glodblock.github.client.container.ContainerFluidDualInterface; +import com.glodblock.github.client.container.ContainerItemDualInterface; +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketFluidCraftBtns; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; + +public class GuiItemDualInterface extends UpgradeableScreen { + + private final SettingToggleButton blockMode; + private final ToggleButton interfaceMode; + private final TabButton switchInterface; + private final TabButton priorityBtn; + private final FCToggleButton fluidPacketBtn; + private final FCToggleButton splittingBtn; + private final FCToggleButton blockingBtn; + + public GuiItemDualInterface(ContainerItemDualInterface container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + this.blockMode = new ServerSettingToggleButton<>(Settings.BLOCK, YesNo.NO); + this.addToLeftToolbar(this.blockMode); + this.interfaceMode = new ToggleButton(Icon.INTERFACE_TERMINAL_SHOW, Icon.INTERFACE_TERMINAL_HIDE, GuiText.InterfaceTerminal.text(), GuiText.InterfaceTerminalHint.text(), (btn) -> { + this.selectNextInterfaceMode(); + }); + this.addToLeftToolbar(this.interfaceMode); + ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer(); + ItemStack iconFluid = Api.instance().definitions().blocks().fluidIface().maybeStack(1).orElse(ItemStack.EMPTY); + this.switchInterface = new TabButton(iconFluid, iconFluid.getDisplayName(), itemRender, btn -> + NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFluidDualInterface.TYPE))); + this.widgets.add("switchInterface", switchInterface); + ItemStack iconWrench = Api.instance().definitions().items().certusQuartzWrench().maybeStack(1).orElse(ItemStack.EMPTY); + this.priorityBtn = new TabButton(iconWrench, GuiText.Priority.text(), itemRender, btn -> + NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFCPriority.TYPE))); + this.widgets.add("priorityBtn", priorityBtn); + fluidPacketBtn = new FCToggleButton( + btn -> { + FCToggleButton fbtn = (FCToggleButton) btn; + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("fluidPacket", fbtn.getActive() == 1)); + }, BlitMap.SEND_FLUID, BlitMap.SEND_PACKET); + this.addToLeftToolbar(fluidPacketBtn); + splittingBtn = new FCToggleButton( + btn -> { + FCToggleButton fbtn = (FCToggleButton) btn; + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("allowSplitting", fbtn.getActive() == 1)); + }, BlitMap.NOT_SPLITTING, BlitMap.SPLITTING); + this.addToLeftToolbar(splittingBtn); + blockingBtn = new FCToggleButton( + btn -> { + FCToggleButton fbtn = (FCToggleButton) btn; + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("blockModeEx", fbtn.getActive())); + }, BlitMap.BLOCK_ALL, BlitMap.BLOCK_ITEM, BlitMap.BLOCK_FLUID); + this.addToLeftToolbar(blockingBtn); + } + + @Override + protected void updateBeforeRender() { + super.updateBeforeRender(); + this.blockMode.set(this.container.getBlockingMode()); + this.interfaceMode.setState(this.container.getInterfaceTerminalMode() == YesNo.YES); + this.fluidPacketBtn.forceActive(this.container.fluidPacket ? 1 : 0); + this.splittingBtn.forceActive(this.container.allowSplitting ? 1 : 0); + this.blockingBtn.forceActive(this.container.blockModeEx); + this.blockingBtn.visible = this.blockMode.getCurrentValue() == YesNo.YES; + } + + private void selectNextInterfaceMode() { + boolean backwards = this.isHandlingRightClick(); + NetworkHandler.instance().sendToServer(new ConfigButtonPacket(Settings.INTERFACE_TERMINAL, backwards)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java new file mode 100644 index 000000000..d529525e1 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java @@ -0,0 +1,60 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.client.container.ContainerLargeIngredientBuffer; +import com.glodblock.github.handler.ButtonMouseHandler; +import com.glodblock.github.handler.TankMouseHandler; +import com.glodblock.github.util.FluidRenderUtils; +import com.glodblock.github.util.MouseRegionManager; +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.PlayerContainer; +import net.minecraft.util.text.ITextComponent; + +public class GuiLargeIngredientBuffer extends AEBaseScreen { + + private static final int TANK_X = 13, TANK_X_OFF = 22, TANK_Y = 18; + private static final int TANK_WIDTH = 16, TANK_HEIGHT = 37; + + private final MouseRegionManager mouseRegions = new MouseRegionManager(this); + + public GuiLargeIngredientBuffer(ContainerLargeIngredientBuffer container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) { + super(container, playerInventory, title, style); + for (int i = 0; i < 7; i++) { + mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + new TankMouseHandler(container.getTile().getFluidInventory(), i)); + mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7, + ButtonMouseHandler.dumpTank(container, i)); + } + } + + @Override + public boolean mouseClicked(double xCoord, double yCoord, int btn) { + if (mouseRegions.onClick(xCoord, yCoord, btn)) { + return super.mouseClicked(xCoord, yCoord, btn); + } + return true; + } + + @Override + public void drawFG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY) { + GlStateManager.color4f(1F, 1F, 1F, 1F); + IAEFluidTank fluidInv = container.getTile().getFluidInventory(); + assert minecraft != null; + minecraft.getTextureManager().bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE); + Tessellator tess = Tessellator.getInstance(); + BufferBuilder buf = tess.getBuffer(); + for (int i = 0; i < 7; i++) { + FluidRenderUtils.renderFluidIntoGui(tess, buf, TANK_X + i * TANK_X_OFF, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + fluidInv.getFluidInSlot(i), fluidInv.getTankCapacity(i)); + } + GlStateManager.color4f(1F, 1F, 1F, 1F); + mouseRegions.render(matrixStack, mouseX, mouseY); + } + +} diff --git a/src/main/java/com/glodblock/github/client/button/BlitMap.java b/src/main/java/com/glodblock/github/client/button/BlitMap.java new file mode 100644 index 000000000..5b17ec215 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/button/BlitMap.java @@ -0,0 +1,52 @@ +package com.glodblock.github.client.button; + +import appeng.client.gui.style.Blitter; +import com.glodblock.github.util.NameConst; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; + +public enum BlitMap { + + NOT_COMBINE(0, 0), + COMBINE(16, 0), + SEND_FLUID(2*16, 0), + SEND_PACKET(3*16, 0), + FLUID_FIRST(0, 16), + ITEM_FIRST(16, 16), + CRAFT_FLUID(2*16, 16), + SPLITTING(3*16, 16), + NOT_SPLITTING(0, 2*16), + BLOCK_ALL(16, 2*16), + BLOCK_ITEM(2*16, 2*16), + BLOCK_FLUID(3*16, 2*16); + + + private final int x; + private final int y; + private final int width; + private final int height; + + BlitMap(int x, int y) { + this(x, y, 16, 16); + } + + BlitMap(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public Blitter getBlitter() { + return Blitter.texture("gui/states.png", 64, 64).src(this.x, this.y, this.width, this.height); + } + + public ITextComponent getDisplayName() { + return new TranslationTextComponent(NameConst.TT_KEY + this.toString().toLowerCase()); + } + + public ITextComponent getHint() { + return new TranslationTextComponent(NameConst.TT_KEY + this.toString().toLowerCase() + ".hint"); + } + +} diff --git a/src/main/java/com/glodblock/github/client/button/FCToggleButton.java b/src/main/java/com/glodblock/github/client/button/FCToggleButton.java new file mode 100644 index 000000000..93a392921 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/button/FCToggleButton.java @@ -0,0 +1,98 @@ +package com.glodblock.github.client.button; + +import appeng.client.gui.Icon; +import appeng.client.gui.widgets.ITooltip; +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.gui.widget.button.Button; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class FCToggleButton extends Button implements ITooltip { + private final BlitMap[] modes; + private final int length; + private int active; + private boolean halfSize = false; + + public FCToggleButton(Button.IPressable onPress, BlitMap... modes) { + super(0, 0, 16, 16, StringTextComponent.EMPTY, onPress); + this.modes = modes; + this.length = modes.length; + } + + public void next() { + active = (active + 1) % length; + } + + public void forceActive(int value) { + active = value % length; + } + + @Override + public void renderWidget(@Nonnull MatrixStack matrixStack, int mouseX, int mouseY, float partial) { + if (this.visible) { + if (this.halfSize) { + matrixStack.push(); + matrixStack.translate(this.x, this.y, 0.0); + matrixStack.scale(0.5F, 0.5F, 1.0F); + Icon.TOOLBAR_BUTTON_BACKGROUND.getBlitter().dest(0, 0).blit(matrixStack, this.getBlitOffset()); + this.getIcon().getBlitter().dest(0, 0).blit(matrixStack, this.getBlitOffset()); + matrixStack.pop(); + } else { + Icon.TOOLBAR_BUTTON_BACKGROUND.getBlitter().dest(this.x, this.y).blit(matrixStack, this.getBlitOffset()); + this.getIcon().getBlitter().dest(this.x, this.y).blit(matrixStack, this.getBlitOffset()); + } + } + } + + public void setHalfSize(boolean value) { + this.halfSize = value; + if (value) { + this.width = 8; + this.height = 8; + } + } + + @Override + public void onPress() { + this.next(); + this.onPress.onPress(this); + } + + private BlitMap getIcon() { + return this.modes[active]; + } + + public int getActive() { + return this.active; + } + + @Nonnull + public List getTooltipMessage() { + return Arrays.asList(this.getIcon().getDisplayName(), this.getIcon().getHint()); + } + + public int getTooltipAreaX() { + return this.x; + } + + public int getTooltipAreaY() { + return this.y; + } + + public int getTooltipAreaWidth() { + return this.width; + } + + public int getTooltipAreaHeight() { + return this.height; + } + + public boolean isTooltipAreaVisible() { + return this.visible; + } +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java b/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java new file mode 100644 index 000000000..cbbd38f5d --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java @@ -0,0 +1,69 @@ +package com.glodblock.github.client.container; + +import appeng.api.config.SecurityPermissions; +import appeng.container.AEBaseContainer; +import appeng.container.guisync.GuiSync; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.helpers.IPriorityHost; +import com.glodblock.github.interfaces.ConfigData; +import com.glodblock.github.util.ConfigSet; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; + +public class ContainerFCPriority extends AEBaseContainer implements ConfigData { + public static final ContainerType TYPE = ContainerTypeBuilder.create(ContainerFCPriority::new, IPriorityHost.class) + .requirePermission(SecurityPermissions.BUILD) + .withInitialData( + (host, buffer) -> buffer.writeVarInt(host.getPriority()), + (host, container, buffer) -> container.priorityValue = buffer.readVarInt()) + .build("fc_priority"); + private final IPriorityHost priHost; + private final ConfigSet exConfig = new ConfigSet(); + @GuiSync(2) + public int priorityValue; + + public ContainerFCPriority(int id, PlayerInventory ip, IPriorityHost te) { + super(TYPE, id, ip, te); + this.priHost = te; + this.priorityValue = te.getPriority(); + this.exConfig.addConfig("priority", v -> { + this.priorityValue = (int) v; + this.priHost.setPriority((int) v); + }, () -> this.priorityValue); + } + + public void setPriority(int newValue) { + if (newValue != this.priorityValue) { + if (this.isServer()) { + this.priHost.setPriority(newValue); + this.priorityValue = newValue; + } + } + } + + public void detectAndSendChanges() { + super.detectAndSendChanges(); + this.verifyPermissions(SecurityPermissions.BUILD, false); + if (this.isServer()) { + this.priorityValue = this.priHost.getPriority(); + } + } + + public int getPriorityValue() { + return this.priorityValue; + } + + public IPriorityHost getPriorityHost() { + return this.priHost; + } + + @Override + public void set(String id, Object value) { + this.exConfig.setConfig(id, value); + } + + @Override + public Object get(String id) { + return this.exConfig.getConfig(id); + } +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java new file mode 100644 index 000000000..754cdb605 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java @@ -0,0 +1,82 @@ +package com.glodblock.github.client.container; + +import appeng.api.config.SecurityPermissions; +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.util.IConfigManager; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.fluids.container.FluidConfigurableContainer; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.FluidSyncHelper; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.fluids.util.IAEFluidTank; +import appeng.util.Platform; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.inventory.container.IContainerListener; + +import java.util.Collections; +import java.util.Map; + +public class ContainerFluidDualInterface extends FluidConfigurableContainer { + + private final DualityFluidInterface myDuality; + private final FluidSyncHelper tankSync; + public static final ContainerType TYPE = ContainerTypeBuilder + .create(ContainerFluidDualInterface::new, IFluidInterfaceHost.class) + .build("dual_fluid_interface"); + + public ContainerFluidDualInterface(int id, PlayerInventory ip, IFluidInterfaceHost te) { + super(TYPE, id, ip, te.getDualityFluidInterface().getHost()); + this.myDuality = te.getDualityFluidInterface(); + this.tankSync = new FluidSyncHelper(this.myDuality.getTanks(), 6); + } + + protected void setupConfig() { + // NO-OP + } + + protected void loadSettingsFromHost(IConfigManager cm) { + // NO-OP + } + + public IAEFluidTank getTanks() { + return this.myDuality.getTanks(); + } + + public IAEFluidTank getFluidConfigInventory() { + return this.myDuality.getConfig(); + } + + @Override + public void detectAndSendChanges() { + this.verifyPermissions(SecurityPermissions.BUILD, false); + super.detectAndSendChanges(); + if (Platform.isServer()) { + this.tankSync.sendDiff(this.listeners); + } + standardDetectAndSendChanges(); + } + + public void addListener(IContainerListener listener) { + super.addListener(listener); + this.tankSync.sendFull(Collections.singleton(listener)); + } + + public void receiveFluidSlots(Map fluids) { + super.receiveFluidSlots(fluids); + this.tankSync.readPacket(fluids); + } + + protected boolean supportCapacity() { + return false; + } + + public int availableUpgrades() { + return 0; + } + + public boolean hasToolbox() { + return false; + } + +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java new file mode 100644 index 000000000..966f9b71f --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java @@ -0,0 +1,23 @@ +package com.glodblock.github.client.container; + +import appeng.container.AEBaseContainer; +import appeng.container.SlotSemantic; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.container.slot.AppEngSlot; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; + +public class ContainerFluidPacketDecoder extends AEBaseContainer { + + public static final ContainerType TYPE = ContainerTypeBuilder + .create(ContainerFluidPacketDecoder::new, TileFluidPacketDecoder.class) + .build("fluid_packet_decoder"); + + public ContainerFluidPacketDecoder(int id, PlayerInventory ip, TileFluidPacketDecoder tile) { + super(TYPE, id, ip, tile); + addSlot(new AppEngSlot(tile.getInventory(), 0), SlotSemantic.STORAGE); + createPlayerInventorySlots(ip); + } + +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java new file mode 100644 index 000000000..de48ae667 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java @@ -0,0 +1,558 @@ +package com.glodblock.github.client.container; + +import appeng.api.config.Actionable; +import appeng.api.crafting.ICraftingHelper; +import appeng.api.definitions.IDefinitions; +import appeng.api.storage.IMEMonitor; +import appeng.api.storage.ITerminalHost; +import appeng.api.storage.channels.IItemStorageChannel; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; +import appeng.container.ContainerNull; +import appeng.container.SlotSemantic; +import appeng.container.guisync.GuiSync; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.container.me.items.ItemTerminalContainer; +import appeng.container.slot.*; +import appeng.core.Api; +import appeng.core.sync.packets.PatternSlotPacket; +import appeng.helpers.IContainerCraftingPacket; +import appeng.helpers.InventoryAction; +import appeng.items.storage.ViewCellItem; +import appeng.me.helpers.MachineSource; +import appeng.util.InventoryAdaptor; +import appeng.util.Platform; +import appeng.util.inv.AdaptorItemHandler; +import appeng.util.inv.WrapperCursorItemHandler; +import appeng.util.item.AEItemStack; +import com.glodblock.github.common.item.ItemFluidEncodedPattern; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.interfaces.ConfigData; +import com.glodblock.github.interfaces.PatternConsumer; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.ConfigSet; +import com.glodblock.github.util.FCUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.inventory.CraftResultInventory; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.inventory.container.CraftingResultSlot; +import net.minecraft.inventory.container.IContainerListener; +import net.minecraft.inventory.container.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.ICraftingRecipe; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.IRecipeType; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.items.wrapper.PlayerInvWrapper; + +import javax.annotation.Nonnull; + +public class ContainerFluidPatternTerminal extends ItemTerminalContainer implements IOptionalSlotHost, IContainerCraftingPacket, PatternConsumer, ConfigData { + + private final PartFluidPatternTerminal patternTerminal; + private final IItemHandler craftingGridInv; + private final FakeCraftingMatrixSlot[] craftingGridSlots = new FakeCraftingMatrixSlot[9]; + private final OptionalFakeSlot[] processingOutputSlots = new OptionalFakeSlot[3]; + private final PatternTermSlot craftOutputSlot; + private final RestrictedInputSlot blankPatternSlot; + private final RestrictedInputSlot encodedPatternSlot; + private final ICraftingHelper craftingHelper; + private final ConfigSet config = new ConfigSet(); + private ICraftingRecipe currentRecipe; + private boolean currentRecipeCraftingMode; + public static ContainerType TYPE = ContainerTypeBuilder + .create(ContainerFluidPatternTerminal::new, ITerminalHost.class) + .build("fluid_pattern_terminal"); + @GuiSync(97) + public boolean craftingMode; + @GuiSync(96) + public boolean substitute; + @GuiSync(105) + public boolean combine = false; + @GuiSync(106) + public boolean fluidFirst = false; + + public ContainerFluidPatternTerminal(int id, PlayerInventory ip, ITerminalHost monitorable) { + super(TYPE, id, ip, monitorable, false); + this.craftingHelper = Api.INSTANCE.crafting(); + this.craftingMode = true; + this.substitute = false; + this.patternTerminal = (PartFluidPatternTerminal) monitorable; + IItemHandler patternInv = this.patternTerminal.getInventoryByName("pattern"); + IItemHandler output = this.patternTerminal.getInventoryByName("output"); + this.craftingGridInv = this.patternTerminal.getInventoryByName("crafting"); + int i; + for(i = 0; i < 9; ++i) { + this.addSlot(this.craftingGridSlots[i] = new FakeCraftingMatrixSlot(this.craftingGridInv, i), SlotSemantic.CRAFTING_GRID); + } + + this.addSlot(this.craftOutputSlot = new PatternTermSlot(ip.player, this.getActionSource(), this.powerSource, monitorable, this.craftingGridInv, patternInv, this, 2, this), SlotSemantic.CRAFTING_RESULT); + this.craftOutputSlot.setIcon(null); + + for(i = 0; i < 3; ++i) { + this.addSlot(this.processingOutputSlots[i] = new PatternOutputsSlot(output, this, i, 1), SlotSemantic.PROCESSING_RESULT); + this.processingOutputSlots[i].setRenderDisabled(false); + this.processingOutputSlots[i].setIcon(null); + } + + this.addSlot(this.blankPatternSlot = new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.BLANK_PATTERN, patternInv, 0), SlotSemantic.BLANK_PATTERN); + this.addSlot(this.encodedPatternSlot = new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.ENCODED_PATTERN, patternInv, 1), SlotSemantic.ENCODED_PATTERN); + this.encodedPatternSlot.setStackLimit(1); + this.createPlayerInventorySlots(ip); + this.config + .addConfig("combine", v -> { + this.combine = (boolean) v; + this.patternTerminal.setCombineMode((boolean) v); + }, () -> this.combine) + .addConfig("fluidFirst", v -> { + this.fluidFirst = (boolean) v; + this.patternTerminal.setFluidPlaceMode((boolean) v); + }, () -> this.fluidFirst) + .addConfig("craft", v -> { + this.craftingMode = (boolean) v; + this.patternTerminal.setCraftingRecipe((boolean) v); + }, () -> this.craftingMode) + .addConfig("substitute", v -> { + this.substitute = (boolean) v; + this.patternTerminal.setSubstitution((boolean) v); + }, () -> this.substitute) + .addConfig("encode", v -> this.encode(), + () -> {throw new IllegalArgumentException("Doesn't support operation!");}) + .addConfig("clear", v -> this.clear(), + () -> {throw new IllegalArgumentException("Doesn't support operation!");}); + + } + + @Override + public void putStackInSlot(int slotID, @Nonnull ItemStack stack) { + super.putStackInSlot(slotID, stack); + this.getAndUpdateOutput(); + } + + private ItemStack getAndUpdateOutput() { + World world = this.getPlayerInventory().player.world; + CraftingInventory ic = new CraftingInventory(this, 3, 3); + + for(int x = 0; x < ic.getSizeInventory(); ++x) { + ic.setInventorySlotContents(x, this.craftingGridInv.getStackInSlot(x)); + } + + if (this.currentRecipe == null || !this.currentRecipe.matches(ic, world)) { + this.currentRecipe = world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, ic, world).orElse(null); + this.currentRecipeCraftingMode = this.craftingMode; + } + + ItemStack is; + if (this.currentRecipe == null) { + is = ItemStack.EMPTY; + } else { + is = this.currentRecipe.getCraftingResult(ic); + } + + this.craftOutputSlot.setDisplayedCraftingOutput(is); + return is; + } + + public void encode() { + if (checkHasFluidPattern()) { + this.encodeFluidPattern(); + } else { + this.encodeItemPattern(); + } + } + + public void encodeItemPattern() { + ItemStack output = this.encodedPatternSlot.getStack(); + ItemStack[] in = this.getInputs(); + ItemStack[] out = this.getOutputs(); + if (in != null && out != null && (!this.isCraftingMode() || this.currentRecipe != null)) { + if (output.isEmpty() || this.craftingHelper.isEncodedPattern(output)) { + if (output.isEmpty()) { + output = this.blankPatternSlot.getStack(); + if (output.isEmpty() || !isPattern(output)) { + return; + } + output.setCount(output.getCount() - 1); + if (output.getCount() == 0) { + this.blankPatternSlot.putStack(ItemStack.EMPTY); + } + output = null; + } else if (output.getItem() instanceof ItemFluidEncodedPattern) { + output = null; + } + if (this.isCraftingMode()) { + output = this.craftingHelper.encodeCraftingPattern(output, this.currentRecipe, in, out[0], this.isSubstitute()); + } else { + output = this.craftingHelper.encodeProcessingPattern(output, in, out); + } + this.encodedPatternSlot.putStack(output); + } + } + } + + private void encodeFluidPattern() { + ItemStack output = this.encodedPatternSlot.getStack(); + ItemStack[] in = this.getInputs(); + ItemStack[] out = this.getOutputs(); + if (in != null && out != null && (!this.isCraftingMode() || this.currentRecipe != null)) { + if (output.isEmpty() || this.craftingHelper.isEncodedPattern(output)) { + if (output.isEmpty()) { + output = this.blankPatternSlot.getStack(); + if (output.isEmpty() || !isPattern(output)) { + return; + } + output.setCount(output.getCount() - 1); + if (output.getCount() == 0) { + this.blankPatternSlot.putStack(ItemStack.EMPTY); + } + } + output = FCItems.DENSE_ENCODED_PATTERN.encodeStack(in, out); + this.encodedPatternSlot.putStack(output); + } + } + } + + private static boolean isPattern(final ItemStack output) { + if (output.isEmpty()) { + return false; + } + if (output.getItem() instanceof ItemFluidEncodedPattern) { + return true; + } + final IDefinitions defs = Api.instance().definitions(); + return defs.items().encodedPattern().isSameAs(output) || defs.materials().blankPattern().isSameAs(output); + } + + private boolean checkHasFluidPattern() { + if (this.craftingMode) { + return false; + } + boolean hasFluid = false, search = false; + for (Slot craftingSlot : this.craftingGridSlots) { + final ItemStack crafting = craftingSlot.getStack(); + if (crafting.isEmpty()) { + continue; + } + search = true; + if (crafting.getItem() instanceof ItemFluidPacket) { + hasFluid = true; + break; + } + } + if (!search) { // search=false -> inputs were empty + return false; + } + // `search` should be true at this point + for (Slot outputSlot : this.processingOutputSlots) { + final ItemStack out = outputSlot.getStack(); + if (out.isEmpty()) { + continue; + } + search = false; + if (hasFluid) { + break; + } else if (out.getItem() instanceof ItemFluidPacket) { + hasFluid = true; + break; + } + } + return hasFluid && !search; // search=true -> outputs were empty + } + + private ItemStack[] getInputs() { + ItemStack[] input = new ItemStack[9]; + boolean hasValue = false; + for(int x = 0; x < this.craftingGridSlots.length; ++x) { + input[x] = this.craftingGridSlots[x].getStack(); + if (!input[x].isEmpty()) { + hasValue = true; + } + } + return hasValue ? input : null; + } + + private ItemStack[] getOutputs() { + if (this.isCraftingMode()) { + ItemStack out = this.getAndUpdateOutput(); + if (!out.isEmpty() && out.getCount() > 0) { + return new ItemStack[] {out}; + } + } else { + boolean hasValue = false; + ItemStack[] list = new ItemStack[3]; + for(int i = 0; i < this.processingOutputSlots.length; ++i) { + ItemStack out = this.processingOutputSlots[i].getStack(); + list[i] = out; + if (!out.isEmpty()) { + hasValue = true; + } + } + if (hasValue) { + return list; + } + } + return null; + } + + public boolean isSlotEnabled(int idx) { + if (idx == 1) { + return this.isServer() ? !this.patternTerminal.isCraftingRecipe() : !this.isCraftingMode(); + } else if (idx == 2) { + return this.isServer() ? this.patternTerminal.isCraftingRecipe() : this.isCraftingMode(); + } else { + return false; + } + } + + public void craftOrGetItem(PatternSlotPacket packetPatternSlot) { + if (packetPatternSlot.slotItem != null && this.monitor != null) { + IAEItemStack out = packetPatternSlot.slotItem.copy(); + InventoryAdaptor inv = new AdaptorItemHandler(new WrapperCursorItemHandler(this.getPlayerInventory().player.inventory)); + InventoryAdaptor playerInv = InventoryAdaptor.getAdaptor(this.getPlayerInventory().player); + if (packetPatternSlot.shift) { + inv = playerInv; + } + + if (!inv.simulateAdd(out.createItemStack()).isEmpty()) { + return; + } + + IAEItemStack extracted = Platform.poweredExtraction(this.powerSource, this.monitor, out, this.getActionSource()); + PlayerEntity p = this.getPlayerInventory().player; + if (extracted != null) { + inv.addItems(extracted.createItemStack()); + if (p instanceof ServerPlayerEntity) { + this.updateHeld((ServerPlayerEntity)p); + } + + this.detectAndSendChanges(); + return; + } + + CraftingInventory ic = new CraftingInventory(new ContainerNull(), 3, 3); + CraftingInventory real = new CraftingInventory(new ContainerNull(), 3, 3); + + for(int x = 0; x < 9; ++x) { + ic.setInventorySlotContents(x, packetPatternSlot.pattern[x] == null ? ItemStack.EMPTY : packetPatternSlot.pattern[x].createItemStack()); + } + + IRecipe r = p.world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, ic, p.world).orElse(null); + if (r == null) { + return; + } + + IMEMonitor storage = this.patternTerminal.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class)); + IItemList all = storage.getStorageList(); + ItemStack is = r.getCraftingResult(ic); + + for(int x = 0; x < ic.getSizeInventory(); ++x) { + if (!ic.getStackInSlot(x).isEmpty()) { + ItemStack pulled = Platform.extractItemsByRecipe(this.powerSource, this.getActionSource(), storage, p.world, r, is, ic, ic.getStackInSlot(x), x, all, Actionable.MODULATE, ViewCellItem.createFilter(this.getViewCells())); + real.setInventorySlotContents(x, pulled); + } + } + + IRecipe rr = p.world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, real, p.world).orElse(null); + if (rr == r && Platform.itemComparisons().isSameItem(rr.getCraftingResult(real), is)) { + CraftResultInventory craftingResult = new CraftResultInventory(); + craftingResult.setRecipeUsed(rr); + CraftingResultSlot sc = new CraftingResultSlot(p, real, craftingResult, 0, 0, 0); + sc.onTake(p, is); + + for(int x = 0; x < real.getSizeInventory(); ++x) { + ItemStack failed = playerInv.addItems(real.getStackInSlot(x)); + if (!failed.isEmpty()) { + p.dropItem(failed, false); + } + } + + inv.addItems(is); + if (p instanceof ServerPlayerEntity) { + this.updateHeld((ServerPlayerEntity)p); + } + + this.detectAndSendChanges(); + } else { + for(int x = 0; x < real.getSizeInventory(); ++x) { + ItemStack failed = real.getStackInSlot(x); + if (!failed.isEmpty()) { + this.monitor.injectItems(AEItemStack.fromItemStack(failed), Actionable.MODULATE, new MachineSource(this.patternTerminal)); + } + } + } + } + } + + public boolean isCraftingMode() { + return this.craftingMode; + } + + @Override + public void acceptPattern(Int2ObjectMap inputs, ItemStack[] outputs, boolean combine) { + this.patternTerminal.onChangeCrafting(inputs, outputs, combine); + } + + @Override + public void doAction(ServerPlayerEntity player, InventoryAction action, int slotId, long id) { + if (this.isCraftingMode()) { + super.doAction(player, action, slotId, id); + return; + } + if (slotId < 0 || slotId >= this.inventorySlots.size()) { + super.doAction(player, action, slotId, id); + return; + } + Slot slot = getSlot(slotId); + ItemStack stack = player.inventory.getItemStack(); + if ((slot instanceof FakeCraftingMatrixSlot || slot instanceof PatternOutputsSlot) && !stack.isEmpty() + && stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).isPresent() && !FCUtil.getFluidFromItem(stack).isEmpty()) { + FluidStack fluid = FluidStack.EMPTY; + switch (action) { + case PICKUP_OR_SET_DOWN: + fluid = FCUtil.getFluidFromItem(stack); + slot.putStack(ItemFluidPacket.newStack(fluid)); + break; + case SPLIT_OR_PLACE_SINGLE: + fluid = FCUtil.getFluidFromItem(ItemHandlerHelper.copyStackWithSize(stack, 1)); + FluidStack origin = ItemFluidPacket.getFluidStack(slot.getStack()); + if (!fluid.isEmpty() && fluid.equals(origin)) { + fluid.grow(origin.getAmount()); + if (fluid.getAmount() <= 0) + fluid = FluidStack.EMPTY; + } + slot.putStack(ItemFluidPacket.newStack(fluid)); + break; + } + if (fluid.isEmpty()) { + super.doAction(player, action, slotId, id); + return; + } + return; + } + if (action == InventoryAction.SPLIT_OR_PLACE_SINGLE) { + if (stack.isEmpty() && !slot.getStack().isEmpty() && slot.getStack().getItem() instanceof ItemFluidPacket) { + FluidStack fluid = ItemFluidPacket.getFluidStack(slot.getStack()); + if (!fluid.isEmpty() && fluid.getAmount() - 1000 >= 1) { + fluid.shrink(1000); + slot.putStack(ItemFluidPacket.newStack(fluid)); + } + } + } + super.doAction(player, action, slotId, id); + } + + @Override + public void detectAndSendChanges() { + super.detectAndSendChanges(); + if (Platform.isServer()) { + if (this.isCraftingMode() != this.patternTerminal.isCraftingRecipe()) { + this.setCraftingMode(this.patternTerminal.isCraftingRecipe()); + } + this.substitute = this.patternTerminal.isSubstitution(); + this.combine = patternTerminal.getCombineMode(); + this.fluidFirst = patternTerminal.getFluidPlaceMode(); + } + } + + @Override + public void onServerDataSync() { + super.onServerDataSync(); + if (this.currentRecipeCraftingMode != this.craftingMode) { + this.getAndUpdateOutput(); + } + } + + @Override + public void onSlotChange(Slot s) { + if (s == this.encodedPatternSlot && isServer()) { + for (final IContainerListener listener : this.listeners) { + for (final Slot slot : this.inventorySlots) { + if (slot instanceof OptionalFakeSlot || slot instanceof FakeCraftingMatrixSlot) { + listener.sendSlotContents(this, slot.slotNumber, slot.getStack()); + } + } + if (listener instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) listener).isChangingQuantityOnly = false; + } + } + this.detectAndSendChanges(); + } + + if (s == this.craftOutputSlot && isClient()) { + this.getAndUpdateOutput(); + } + } + + public void clear() { + for (final Slot s : this.craftingGridSlots) { + s.putStack(ItemStack.EMPTY); + } + + for (final Slot s : this.processingOutputSlots) { + s.putStack(ItemStack.EMPTY); + } + + this.detectAndSendChanges(); + this.getAndUpdateOutput(); + } + + @Override + public IItemHandler getInventoryByName(final String name) { + if (name.equals("player")) { + return new PlayerInvWrapper(this.getPlayerInventory()); + } + return this.patternTerminal.getInventoryByName(name); + } + + @Override + public boolean useRealItems() { + return false; + } + + private boolean isSubstitute() { + return this.substitute; + } + + private void setCraftingMode(boolean craftingMode) { + this.craftingMode = craftingMode; + } + + public void setSubstitute(boolean substitute) { + this.substitute = substitute; + } + + public FakeCraftingMatrixSlot[] getCraftingGridSlots() { + return this.craftingGridSlots; + } + + public OptionalFakeSlot[] getProcessingOutputSlots() { + return this.processingOutputSlots; + } + + public PatternTermSlot getCraftOutputSlot() { + return this.craftOutputSlot; + } + + public PartFluidPatternTerminal getPart() { + return this.patternTerminal; + } + + @Override + public void set(String id, Object value) { + this.config.setConfig(id, value); + } + + @Override + public Object get(String id) { + return this.config.getConfig(id); + } +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java new file mode 100644 index 000000000..ab128809b --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java @@ -0,0 +1,46 @@ +package com.glodblock.github.client.container; + +import appeng.container.AEBaseContainer; +import appeng.container.SlotSemantic; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.container.slot.AppEngSlot; +import com.glodblock.github.common.block.BlockIngredientBuffer; +import com.glodblock.github.interfaces.TankDumpable; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraftforge.items.IItemHandler; + +public class ContainerIngredientBuffer extends AEBaseContainer implements TankDumpable { + + private final BlockIngredientBuffer.TileIngredientBuffer tile; + public static final ContainerType TYPE = ContainerTypeBuilder + .create(ContainerIngredientBuffer::new, BlockIngredientBuffer.TileIngredientBuffer.class) + .build("ingredient_buffer"); + + public ContainerIngredientBuffer(int id, PlayerInventory ip, BlockIngredientBuffer.TileIngredientBuffer tile) { + super(TYPE, id, ip, tile); + this.tile = tile; + IItemHandler inv = tile.getInternalInventory(); + for (int i = 0; i < 9; i++) { + addSlot(new AppEngSlot(inv, i), SlotSemantic.STORAGE); + } + createPlayerInventorySlots(ip); + } + + public BlockIngredientBuffer.TileIngredientBuffer getTile() { + return tile; + } + + @Override + public boolean canDumpTank(int index) { + return tile.getFluidInventory().getFluidInSlot(index) != null; + } + + @Override + public void dumpTank(int index) { + if (index >= 0 && index < tile.getFluidInventory().getSlots()) { + tile.getFluidInventory().setFluidInSlot(index, null); + } + } + +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java b/src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java new file mode 100644 index 000000000..87ebc5558 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java @@ -0,0 +1,121 @@ +package com.glodblock.github.client.container; + +import appeng.api.config.SecurityPermissions; +import appeng.api.config.Settings; +import appeng.api.config.YesNo; +import appeng.api.util.IConfigManager; +import appeng.container.SlotSemantic; +import appeng.container.guisync.GuiSync; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.container.implementations.UpgradeableContainer; +import appeng.container.slot.AppEngSlot; +import appeng.container.slot.FakeSlot; +import appeng.container.slot.RestrictedInputSlot; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.util.Platform; +import com.glodblock.github.interfaces.ConfigData; +import com.glodblock.github.coreutil.ExtendedInterface; +import com.glodblock.github.util.ConfigSet; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; + +public class ContainerItemDualInterface extends UpgradeableContainer implements ConfigData { + + @GuiSync(3) + public YesNo bMode; + @GuiSync(4) + public YesNo iTermMode; + @GuiSync(95) + public boolean fluidPacket = false; + @GuiSync(96) + public boolean allowSplitting = true; + @GuiSync(97) + public int blockModeEx = 0; + private final ConfigSet exConfig = new ConfigSet(); + private final DualityInterface dualityInterfaceCopy; + public static final ContainerType TYPE = ContainerTypeBuilder + .create(ContainerItemDualInterface::new, IInterfaceHost.class) + .build("dual_item_interface"); + + public ContainerItemDualInterface(int id, PlayerInventory ip, IInterfaceHost te) { + super(TYPE, id, ip, te.getInterfaceDuality().getHost()); + this.bMode = YesNo.NO; + this.iTermMode = YesNo.YES; + this.dualityInterfaceCopy = te.getInterfaceDuality(); + int x; + for (x = 0; x < 9; ++x) { + this.addSlot(new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.ENCODED_PATTERN, this.dualityInterfaceCopy.getPatterns(), x), SlotSemantic.ENCODED_PATTERN); + } + for (x = 0; x < 9; ++x) { + this.addSlot(new FakeSlot(this.dualityInterfaceCopy.getConfig(), x), SlotSemantic.CONFIG); + } + for (x = 0; x < 9; ++x) { + this.addSlot(new AppEngSlot(this.dualityInterfaceCopy.getStorage(), x), SlotSemantic.STORAGE); + } + this.exConfig + .addConfig("fluidPacket", v -> { + this.fluidPacket = (boolean) v; + ((ExtendedInterface) dualityInterfaceCopy).setFluidPacketMode((boolean) v); + }, () -> this.fluidPacket) + .addConfig("allowSplitting", v -> { + this.allowSplitting = (boolean) v; + ((ExtendedInterface) dualityInterfaceCopy).setSplittingMode((boolean) v); + }, () -> this.allowSplitting) + .addConfig("blockModeEx", v -> { + this.blockModeEx = (int) v; + ((ExtendedInterface) dualityInterfaceCopy).setExtendedBlockMode((int) v); + }, () -> this.blockModeEx); + } + + protected void setupConfig() { + this.setupUpgrades(); + } + + public int availableUpgrades() { + return 1; + } + + @Override + public void detectAndSendChanges() { + this.verifyPermissions(SecurityPermissions.BUILD, false); + super.detectAndSendChanges(); + if (Platform.isServer()) { + fluidPacket = ((ExtendedInterface) dualityInterfaceCopy).getFluidPacketMode(); + allowSplitting = ((ExtendedInterface) dualityInterfaceCopy).getSplittingMode(); + blockModeEx = ((ExtendedInterface) dualityInterfaceCopy).getExtendedBlockMode(); + } + } + + @Override + protected void loadSettingsFromHost(IConfigManager cm) { + this.setBlockingMode((YesNo)cm.getSetting(Settings.BLOCK)); + this.setInterfaceTerminalMode((YesNo)cm.getSetting(Settings.INTERFACE_TERMINAL)); + } + + public YesNo getBlockingMode() { + return this.bMode; + } + + private void setBlockingMode(YesNo bMode) { + this.bMode = bMode; + } + + public YesNo getInterfaceTerminalMode() { + return this.iTermMode; + } + + private void setInterfaceTerminalMode(YesNo iTermMode) { + this.iTermMode = iTermMode; + } + + @Override + public void set(String id, Object value) { + this.exConfig.setConfig(id, value); + } + + @Override + public Object get(String id) { + return this.exConfig.getConfig(id); + } +} diff --git a/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java new file mode 100644 index 000000000..b4e59a0cc --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java @@ -0,0 +1,46 @@ +package com.glodblock.github.client.container; + +import appeng.container.AEBaseContainer; +import appeng.container.SlotSemantic; +import appeng.container.implementations.ContainerTypeBuilder; +import appeng.container.slot.AppEngSlot; +import com.glodblock.github.common.block.BlockLargeIngredientBuffer; +import com.glodblock.github.interfaces.TankDumpable; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraftforge.items.IItemHandler; + +public class ContainerLargeIngredientBuffer extends AEBaseContainer implements TankDumpable { + + private final BlockLargeIngredientBuffer.TileLargeIngredientBuffer tile; + public static final ContainerType TYPE = ContainerTypeBuilder + .create(ContainerLargeIngredientBuffer::new, BlockLargeIngredientBuffer.TileLargeIngredientBuffer.class) + .build("large_ingredient_buffer"); + + public ContainerLargeIngredientBuffer(int id, PlayerInventory ip, BlockLargeIngredientBuffer.TileLargeIngredientBuffer tile) { + super(TYPE, id, ip, tile); + this.tile = tile; + IItemHandler inv = tile.getInternalInventory(); + for (int i = 0; i < 27; i++) { + addSlot(new AppEngSlot(inv, i), SlotSemantic.STORAGE); + } + createPlayerInventorySlots(ip); + } + + public BlockLargeIngredientBuffer.TileLargeIngredientBuffer getTile() { + return tile; + } + + @Override + public boolean canDumpTank(int index) { + return tile.getFluidInventory().getFluidInSlot(index) != null; + } + + @Override + public void dumpTank(int index) { + if (index >= 0 && index < tile.getFluidInventory().getSlots()) { + tile.getFluidInventory().setFluidInSlot(index, null); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java b/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java new file mode 100644 index 000000000..d1c3dd5f7 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java @@ -0,0 +1,66 @@ +package com.glodblock.github.client.model; + +import appeng.items.misc.EncodedPatternItem; +import com.glodblock.github.util.Ae2ReflectClient; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.renderer.color.IItemColor; +import net.minecraft.client.renderer.model.*; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.item.ItemStack; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModelConfiguration; +import net.minecraftforge.client.model.IModelLoader; +import net.minecraftforge.client.model.geometry.IModelGeometry; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Set; +import java.util.function.Function; + +public class FluidEncodedPatternModel implements IModelGeometry { + private final ResourceLocation baseModel; + + public FluidEncodedPatternModel(ResourceLocation baseModel) { + this.baseModel = baseModel; + } + + public static final IItemColor PATTERN_ITEM_COLOR_HANDLER = (stack, tintIndex) -> { + EncodedPatternItem iep = (EncodedPatternItem) stack.getItem(); + ItemStack output = iep.getOutput(stack); + if (!output.isEmpty() && Screen.hasShiftDown()) { + return Minecraft.getInstance().getItemColors().getColor(output, tintIndex); + } + return 0xFFFFFF; + }; + + public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) { + return modelGetter.apply(this.baseModel).getTextures(modelGetter, missingTextureErrors); + } + + public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function spriteGetter, IModelTransform modelTransform, ItemOverrideList overrides, ResourceLocation modelLocation) { + IBakedModel baseModel = bakery.getBakedModel(this.baseModel, modelTransform, spriteGetter); + return Ae2ReflectClient.bakeEncodedPatternModel(baseModel); + } + + public static class Loader implements IModelLoader { + + @Override + public void onResourceManagerReload(@Nonnull IResourceManager iResourceManager) { + // NO-OP + } + + @Nonnull + @Override + public FluidEncodedPatternModel read(@Nonnull JsonDeserializationContext jsonDeserializationContext, @Nonnull JsonObject jsonObject) { + jsonObject.remove("loader"); + ResourceLocation baseModel = new ResourceLocation(JSONUtils.getString(jsonObject, "baseModel")); + return new FluidEncodedPatternModel(baseModel); + } + } +} diff --git a/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java new file mode 100644 index 000000000..5d24bb962 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java @@ -0,0 +1,183 @@ +package com.glodblock.github.client.model; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.renderer.model.*; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.item.ItemStack; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.Direction; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.TransformationMatrix; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.model.*; +import net.minecraftforge.client.model.geometry.IModelGeometry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.resource.IResourceType; +import net.minecraftforge.resource.VanillaResourceType; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import static net.minecraftforge.fluids.FluidAttributes.BUCKET_VOLUME; + +// Adapted from https://github.com/CoFH/ThermalInnovation/blob/1.16.5/src/main/java/cofh/thermal/innovation/client/model/FluidReservoirItemModel.java +public class FluidPacketModel implements IModelGeometry { + + // minimal Z offset to prevent depth-fighting + private static final float NORTH_Z_FLUID = 7.498F / 16F; + private static final float SOUTH_Z_FLUID = 8.502F / 16F; + + @Nonnull + private final FluidStack fluidStack; + private final boolean isDisplay; + + public FluidPacketModel(@Nonnull FluidStack fluidStack, boolean display) { + this.fluidStack = fluidStack; + this.isDisplay = display; + } + + public FluidPacketModel set(@Nonnull FluidStack fluidStack, boolean display) { + return new FluidPacketModel(fluidStack, display); + } + + @Override + public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function spriteGetter, IModelTransform modelTransform, ItemOverrideList overrides, ResourceLocation modelLocation) { + + RenderMaterial particleLocation = owner.isTexturePresent("particle") ? owner.resolveTexture("particle") : null; + RenderMaterial fluidMaskLocation = owner.resolveTexture("layer0"); + RenderMaterial background = owner.resolveTexture("layer1"); + + IModelTransform transformsFromModel = owner.getCombinedTransform(); + Fluid fluid = fluidStack.getFluid(); + + TextureAtlasSprite fluidSprite; + if (fluidStack.isEmpty()) { + fluidSprite = spriteGetter.apply(ForgeHooksClient.getBlockMaterial(Fluids.WATER.getAttributes().getStillTexture())); + } else { + fluidSprite = spriteGetter.apply(ForgeHooksClient.getBlockMaterial(fluid.getAttributes().getStillTexture())); + } + ImmutableMap transformMap = PerspectiveMapWrapper.getTransforms(new ModelTransformComposition(transformsFromModel, modelTransform)); + + TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null; + if (particleSprite == null) particleSprite = fluidSprite; + if (particleSprite == null) particleSprite = spriteGetter.apply(background); + + TransformationMatrix transform = modelTransform.getRotation(); + ItemMultiLayerBakedModel.Builder builder = ItemMultiLayerBakedModel.builder(owner, particleSprite, new ContainedFluidOverrideHandler(bakery, owner, this), transformMap); + + if (!isDisplay) { + builder.addQuads(ItemLayerModel.getLayerRenderType(false), ItemLayerModel.getQuadsForSprite(0, spriteGetter.apply(background), transform)); + } + + if (fluidSprite != null) { + TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation); + int luminosity = fluid.getAttributes().getLuminosity(fluidStack); + int color = fluid.getAttributes().getColor(fluidStack); + if (isDisplay) { + builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.genQuad(transform, 0F, 0F, 16F, 16F, NORTH_Z_FLUID, fluidSprite, Direction.NORTH, color, 2, luminosity)); + builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.genQuad(transform, 0F, 0F, 16F, 16F, SOUTH_Z_FLUID, fluidSprite, Direction.SOUTH, color, 2, luminosity)); + } else if (templateSprite != null) { + builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, NORTH_Z_FLUID, Direction.NORTH, color, 2, luminosity)); + builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, SOUTH_Z_FLUID, Direction.SOUTH, color, 2, luminosity)); + } + } + builder.setParticle(particleSprite); + return builder.build(); + } + + @Override + public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) { + Set texs = Sets.newHashSet(); + if (owner.isTexturePresent("particle")) { + texs.add(owner.resolveTexture("particle")); + } + if (owner.isTexturePresent("fluid_mask")) { + texs.add(owner.resolveTexture("fluid_mask")); + } + if (owner.isTexturePresent("background")) { + texs.add(owner.resolveTexture("background")); + } + return texs; + } + + public static class Loader implements IModelLoader { + + @Override + public IResourceType getResourceType() { + return VanillaResourceType.MODELS; + } + + @Override + public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { + // NO-OP + } + + @Override + public void onResourceManagerReload(@Nonnull IResourceManager resourceManager, @Nonnull Predicate resourcePredicate) { + // NO-OP + } + + @Nonnull + @Override + public FluidPacketModel read(@Nonnull JsonDeserializationContext deserializationContext, JsonObject modelContents) { + FluidStack stack = FluidStack.EMPTY; + if (modelContents.has("fluid")) { + ResourceLocation fluidName = new ResourceLocation(modelContents.get("fluid").getAsString()); + Fluid fluid = ForgeRegistries.FLUIDS.getValue(fluidName); + if (fluid != null) { + stack = new FluidStack(fluid, BUCKET_VOLUME); + } + } + // create new model with correct liquid + return new FluidPacketModel(stack, false); + } + + } + + private static final class ContainedFluidOverrideHandler extends ItemOverrideList { + + private final Object2ObjectMap, IBakedModel> cache = new Object2ObjectOpenHashMap<>(); // contains all the baked models since they'll never change + private final ModelBakery bakery; + private final IModelConfiguration owner; + private final FluidPacketModel parent; + + private ContainedFluidOverrideHandler(ModelBakery bakery, IModelConfiguration owner, FluidPacketModel parent) { + this.bakery = bakery; + this.owner = owner; + this.parent = parent; + } + + @Override + public IBakedModel getOverrideModel(@Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable ClientWorld world, @Nullable LivingEntity entity) { + FluidStack fluidStack = ItemFluidPacket.getFluidStack(stack); + boolean display = ItemFluidPacket.isDisplay(stack); + Pair p = new Pair<>(fluidStack.getFluid().getRegistryName(), display); + if (!cache.containsKey(p)) { + FluidPacketModel unbaked = this.parent.set(fluidStack, display); + IBakedModel bakedModel = unbaked.bake(owner, bakery, ModelLoader.defaultTextureGetter(), ModelRotation.X0_Y0, this, FluidCraft.resource("fluid_packet_override")); + cache.put(p, bakedModel); + return bakedModel; + } + return cache.get(p); + } + + } + +} diff --git a/src/main/java/com/glodblock/github/client/render/DropColourHandler.java b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java new file mode 100644 index 000000000..acf05dbff --- /dev/null +++ b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java @@ -0,0 +1,61 @@ +package com.glodblock.github.client.render; + +import com.glodblock.github.util.FluidRenderUtils; +import com.glodblock.github.util.HashUtil; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.fluid.Fluid; +import net.minecraft.inventory.container.PlayerContainer; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fluids.FluidStack; + +public class DropColourHandler { + + public static final DropColourHandler INSTANCE = new DropColourHandler(); + private final Object2IntMap colourCache = new Object2IntLinkedOpenCustomHashMap<>(HashUtil.FLUID); + + @SubscribeEvent + public void onTextureMapStitch(TextureStitchEvent event) { + if (event.getMap().getTextureLocation().equals(PlayerContainer.LOCATION_BLOCKS_TEXTURE)) { + colourCache.clear(); + } + } + + public int getColour(FluidStack fluidStack) { + Fluid fluid = fluidStack.getFluid(); + int colour = fluid.getAttributes().getColor(fluidStack); + return colour != 0xFFFFFFFF ? colour : getColour(fluid); + } + + public int getColour(Fluid fluid) { + boolean isCached = colourCache.containsKey(fluid); + if (isCached) { + return colourCache.getOrDefault(fluid, 0xFFFFFFFF); + } + int colour = fluid.getAttributes().getColor(); + if (colour == 0xFFFFFFFF) { + TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluid); + if (sprite != null && sprite.getFrameCount() > 0) { + int r = 0, g = 0, b = 0, count = 0; + for (int row = 0; row < sprite.getHeight(); row ++) + for (int col = 0; col < sprite.getWidth(); col ++) { + int pixel = sprite.getPixelRGBA(0, row, col); + if (((pixel >>> 24) & 0xFF) > 127) { // is alpha above 50%? + r += (pixel) & 0xFF; + g += (pixel >>> 8) & 0xFF; + b += (pixel >>> 16) & 0xFF; + ++count; + } + } + if (count > 0) { + colour = ((r / count) << 16) | ((g / count) << 8) | (b / count); + } + } + } + colourCache.put(fluid, colour); + return colour; + } + +} diff --git a/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java new file mode 100644 index 000000000..cb02a3e2c --- /dev/null +++ b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java @@ -0,0 +1,55 @@ +package com.glodblock.github.client.render; + +import com.glodblock.github.common.tile.TileSimpleBuffer; +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.client.renderer.model.ItemCameraTransforms; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.renderer.tileentity.TileEntityRenderer; +import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; +import net.minecraft.inventory.container.PlayerContainer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.vector.Vector3f; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; + +public class RenderIngredientBuffer extends TileEntityRenderer { + + public RenderIngredientBuffer(TileEntityRendererDispatcher dispatcher) { + super(dispatcher); + } + + @Override + public void render(@Nonnull TileSimpleBuffer tile, float partialTicks, @Nonnull MatrixStack matrixStackIn, @Nonnull IRenderTypeBuffer bufferIn, int combinedLightIn, int combinedOverlayIn) { + GlStateManager.enableBlend(); + GlStateManager.blendFunc( + GlStateManager.SourceFactor.SRC_ALPHA.param, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA.param + ); + GlStateManager.enableLighting(); + matrixStackIn.push(); + matrixStackIn.translate(0.5D, 0.25D, 0.5D); + renderDispatcher.textureManager.bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE); + ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); + IItemHandler inv = tile.getInternalInventory(); + for (int i = 0; i < inv.getSlots(); i++) { + ItemStack stack = inv.getStackInSlot(i); + if (!stack.isEmpty()) { + matrixStackIn.push(); + matrixStackIn.rotate(Vector3f.YP.rotation((renderDispatcher.world.getGameTime() + partialTicks) / 20.0F)); + itemRenderer.renderItem(stack, ItemCameraTransforms.TransformType.GROUND, combinedLightIn, OverlayTexture.NO_OVERLAY, matrixStackIn, bufferIn); + matrixStackIn.pop(); + break; + } + } + + GlStateManager.color4f(1F, 1F, 1F, 1F); + GlStateManager.disableLighting(); + matrixStackIn.pop(); + } + +} diff --git a/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java b/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java new file mode 100644 index 000000000..a8cd8cef6 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java @@ -0,0 +1,114 @@ +package com.glodblock.github.client.slot; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.container.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.items.ItemHandlerHelper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SlotSingleItem extends Slot { + + private final Slot delegate; + + public SlotSingleItem(Slot delegate) { + super(delegate.inventory, delegate.getSlotIndex(), delegate.xPos, delegate.yPos); + this.delegate = delegate; + } + + @Override + @Nonnull + public ItemStack getStack() { + ItemStack stack = delegate.getStack(); + return stack.isEmpty() ? ItemStack.EMPTY : ItemHandlerHelper.copyStackWithSize(stack, 1); + } + + // delegated + + @Override + public void onSlotChange(@Nonnull ItemStack p_75220_1_, @Nonnull ItemStack p_75220_2_) { + delegate.onSlotChange(p_75220_1_, p_75220_2_); + } + + @Override + @Nonnull + public ItemStack onTake(@Nonnull PlayerEntity thePlayer, @Nonnull ItemStack stack) { + return delegate.onTake(thePlayer, stack); + } + + @Override + public boolean isItemValid(@Nonnull ItemStack stack) { + return delegate.isItemValid(stack); + } + + @Override + public boolean getHasStack() { + return delegate.getHasStack(); + } + + @Override + public void putStack(@Nonnull ItemStack stack) { + delegate.putStack(stack); + } + + @Override + public void onSlotChanged() { + delegate.onSlotChanged(); + } + + @Override + public int getSlotStackLimit() { + return delegate.getSlotStackLimit(); + } + + @Override + public int getItemStackLimit(@Nonnull ItemStack stack) { + return delegate.getItemStackLimit(stack); + } + + @Nullable + @OnlyIn(Dist.CLIENT) + @Override + public Pair getBackground() { + return this.delegate.getBackground(); + } + + @Override + @Nonnull + public ItemStack decrStackSize(int amount) { + return delegate.decrStackSize(amount); + } + + @Override + public boolean canTakeStack(@Nonnull PlayerEntity playerIn) { + return delegate.canTakeStack(playerIn); + } + + @OnlyIn(Dist.CLIENT) + @Override + public boolean isEnabled() { + return delegate.isEnabled(); + } + + @Override + public int getSlotIndex() { + return delegate.getSlotIndex(); + } + + @Override + public boolean isSameInventory(@Nonnull Slot other) { + return delegate.isSameInventory(other); + } + + @Nonnull + @Override + public Slot setBackground(@Nonnull ResourceLocation rl1, @Nonnull ResourceLocation rl2) { + return delegate.setBackground(rl1, rl2); + } + +} diff --git a/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java new file mode 100644 index 000000000..f03f2f144 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java @@ -0,0 +1,81 @@ +package com.glodblock.github.common.block; + +import appeng.api.util.IOrientable; +import appeng.block.AEBaseTileBlock; +import appeng.container.ContainerLocator; +import appeng.container.ContainerOpener; +import appeng.util.Platform; +import com.glodblock.github.client.container.ContainerItemDualInterface; +import com.glodblock.github.common.tile.TileDualInterface; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.material.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.state.BooleanProperty; +import net.minecraft.state.DirectionProperty; +import net.minecraft.state.StateContainer; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.EnumSet; + +public class BlockDualInterface extends AEBaseTileBlock { + + private static final BooleanProperty OMNIDIRECTIONAL = BooleanProperty.create("omnidirectional"); + private static final DirectionProperty FACING = DirectionProperty.create("facing", EnumSet.allOf(Direction.class)); + + public BlockDualInterface() { + super(defaultProps(Material.IRON)); + this.setTileEntity(TileDualInterface.class, TileDualInterface::new); + } + + @Override + public ActionResultType onActivated(World w, BlockPos pos, PlayerEntity p, Hand hand, @Nullable ItemStack heldItem, BlockRayTraceResult hit) { + if (p.isSneaking()) { + return ActionResultType.PASS; + } + final TileDualInterface tg = this.getTileEntity(w, pos); + if (tg != null) { + if (Platform.isServer()) { + ContainerOpener.openContainer( + ContainerItemDualInterface.TYPE, + p, + ContainerLocator.forTileEntitySide(tg, hit.getFace()) + ); + } + return ActionResultType.func_233537_a_(w.isRemote); + } + return ActionResultType.PASS; + } + + @Override + protected void fillStateContainer(@Nonnull StateContainer.Builder builder) { + super.fillStateContainer(builder); + builder.add(OMNIDIRECTIONAL, FACING); + } + + @Override + protected BlockState updateBlockStateFromTileEntity(BlockState currentState, TileDualInterface te) { + return currentState.with(OMNIDIRECTIONAL, te.isOmniDirectional()).with(FACING, te.getForward()); + } + + @Override + protected boolean hasCustomRotation() { + return true; + } + + @Override + protected void customRotateBlock(final IOrientable rotatable, final Direction axis) { + if (rotatable instanceof TileDualInterface) { + ((TileDualInterface) rotatable).setSide(axis); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java b/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java new file mode 100644 index 000000000..dc157ee1e --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java @@ -0,0 +1,14 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import com.glodblock.github.common.tile.TileFluidDiscretizer; +import net.minecraft.block.material.Material; + +public class BlockFluidDiscretizer extends AEBaseTileBlock { + + public BlockFluidDiscretizer() { + super(defaultProps(Material.IRON)); + setTileEntity(TileFluidDiscretizer.class, TileFluidDiscretizer::new); + } + +} diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java new file mode 100644 index 000000000..cc5a40aa7 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java @@ -0,0 +1,43 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import appeng.container.ContainerLocator; +import appeng.container.ContainerOpener; +import com.glodblock.github.client.container.ContainerFluidPacketDecoder; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import net.minecraft.block.BlockState; +import net.minecraft.block.material.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; + +public class BlockFluidPacketDecoder extends AEBaseTileBlock { + + public BlockFluidPacketDecoder() { + super(defaultProps(Material.IRON)); + setTileEntity(TileFluidPacketDecoder.class, TileFluidPacketDecoder::new); + } + + @Override + public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + if (player.isSneaking()) { + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + TileFluidPacketDecoder tile = this.getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + ContainerOpener.openContainer( + ContainerFluidPacketDecoder.TYPE, + player, + ContainerLocator.forTileEntitySide(tile, hit.getFace()) + ); + } + return ActionResultType.func_233537_a_(world.isRemote); + } + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + +} diff --git a/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java new file mode 100644 index 000000000..9520edc18 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java @@ -0,0 +1,64 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import appeng.container.ContainerLocator; +import appeng.container.ContainerOpener; +import appeng.fluids.util.AEFluidInventory; +import appeng.tile.inventory.AppEngInternalInventory; +import com.glodblock.github.client.container.ContainerIngredientBuffer; +import com.glodblock.github.common.tile.TileSimpleBuffer; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.FCUtil; +import net.minecraft.block.BlockState; +import net.minecraft.block.material.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; + +public class BlockIngredientBuffer extends AEBaseTileBlock { + + public BlockIngredientBuffer() { + super(defaultProps(Material.IRON).setOpaque((x, y, z) -> false).notSolid()); + setTileEntity(TileIngredientBuffer.class, TileIngredientBuffer::new); + } + + @Override + public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + if (player.isSneaking()) { + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + TileIngredientBuffer tile = this.getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + ContainerOpener.openContainer( + ContainerIngredientBuffer.TYPE, + player, + ContainerLocator.forTileEntitySide(tile, hit.getFace()) + ); + } + return ActionResultType.func_233537_a_(world.isRemote); + } + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + + public static class TileIngredientBuffer extends TileSimpleBuffer { + + public TileIngredientBuffer() { + super(FCUtil.getTileType(TileIngredientBuffer.class, FCBlocks.INGREDIENT_BUFFER)); + } + + @Override + protected AppEngInternalInventory createItemBuffer() { + return new AppEngInternalInventory(this, 9); + } + + @Override + protected AEFluidInventory createFluidBuffer() { + return new AEFluidInventory(this, 4, 16000); + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java new file mode 100644 index 000000000..bcc9a9562 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java @@ -0,0 +1,64 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import appeng.container.ContainerLocator; +import appeng.container.ContainerOpener; +import appeng.fluids.util.AEFluidInventory; +import appeng.tile.inventory.AppEngInternalInventory; +import com.glodblock.github.client.container.ContainerLargeIngredientBuffer; +import com.glodblock.github.common.tile.TileSimpleBuffer; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.FCUtil; +import net.minecraft.block.BlockState; +import net.minecraft.block.material.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; + +public class BlockLargeIngredientBuffer extends AEBaseTileBlock { + + public BlockLargeIngredientBuffer() { + super(defaultProps(Material.IRON).setOpaque((x, y, z) -> false).notSolid()); + setTileEntity(TileLargeIngredientBuffer.class, TileLargeIngredientBuffer::new); + } + + @Override + public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + if (player.isSneaking()) { + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + TileLargeIngredientBuffer tile = this.getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + ContainerOpener.openContainer( + ContainerLargeIngredientBuffer.TYPE, + player, + ContainerLocator.forTileEntitySide(tile, hit.getFace()) + ); + } + return ActionResultType.func_233537_a_(world.isRemote); + } + return super.onBlockActivated(state, world, pos, player, hand, hit); + } + + public static class TileLargeIngredientBuffer extends TileSimpleBuffer { + + public TileLargeIngredientBuffer() { + super(FCUtil.getTileType(TileLargeIngredientBuffer.class, FCBlocks.LARGE_INGREDIENT_BUFFER)); + } + + @Override + protected AppEngInternalInventory createItemBuffer() { + return new AppEngInternalInventory(this, 27); + } + + @Override + protected AEFluidInventory createFluidBuffer() { + return new AEFluidInventory(this, 7, 16000); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java new file mode 100644 index 000000000..dd9360b89 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java @@ -0,0 +1,140 @@ +package com.glodblock.github.common.item; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; +import appeng.fluids.util.AEFluidStack; +import appeng.util.item.AEItemStack; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.registries.ForgeRegistries; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +import static com.glodblock.github.loader.FCItems.defaultProps; + +public class ItemFluidDrop extends Item { + + public ItemFluidDrop() { + super(defaultProps()); + } + + @Override + public void fillItemGroup(@Nonnull ItemGroup tab, @Nonnull NonNullList items) { + if (isInGroup(tab)) { + items.add(newStack(new FluidStack(Fluids.WATER, 1))); + items.add(newStack(new FluidStack(Fluids.LAVA, 1))); + } + } + + @Override + @Nonnull + public ITextComponent getDisplayName(@Nonnull ItemStack stack) { + FluidStack fluid = getFluidStack(stack); + return new TranslationTextComponent(getTranslationKey(stack), + !fluid.isEmpty() ? fluid.getDisplayName() : "???" + ); + } + + @Override + public void addInformation(@Nonnull ItemStack stack, @Nullable World worldIn, @Nonnull List tooltip, @Nonnull ITooltipFlag flagIn) { + FluidStack fluid = getFluidStack(stack); + if (!fluid.isEmpty()) { + tooltip.add(new TranslationTextComponent("%s, 1 mB", fluid.getDisplayName()).mergeStyle(TextFormatting.GRAY)); + } else { + tooltip.add(new TranslationTextComponent(NameConst.TT_INVALID_FLUID).mergeStyle(TextFormatting.RED)); + } + } + + @Nonnull + public static FluidStack getFluidStack(ItemStack stack) { + if (stack.isEmpty() || stack.getItem() != FCItems.FLUID_DROP || !stack.hasTag()) { + return FluidStack.EMPTY; + } + CompoundNBT tag = Objects.requireNonNull(stack.getTag()); + if (!tag.contains("Fluid", Constants.NBT.TAG_STRING)) { + return FluidStack.EMPTY; + } + Fluid fluid = ForgeRegistries.FLUIDS.getValue(new ResourceLocation(tag.getString("Fluid"))); + if (fluid == null || fluid == Fluids.EMPTY) { + return FluidStack.EMPTY; + } + FluidStack fluidStack = new FluidStack(fluid, stack.getCount()); + if (tag.contains("FluidTag", Constants.NBT.TAG_COMPOUND)) { + fluidStack.setTag(tag.getCompound("FluidTag")); + } + return fluidStack; + } + + @Nullable + public static IAEFluidStack getAeFluidStack(@Nullable IAEItemStack stack) { + if (stack == null) { + return null; + } + IAEFluidStack fluidStack = AEFluidStack.fromFluidStack(getFluidStack(stack.getDefinition())); + if (fluidStack == null) { + return null; + } + fluidStack.setStackSize(stack.getStackSize()); + return fluidStack; + } + + @Nonnull + public static ItemStack newStack(@Nonnull FluidStack fluid) { + if (fluid.isEmpty()) { + return ItemStack.EMPTY; + } + ItemStack stack = new ItemStack(FCItems.FLUID_DROP, fluid.getAmount()); + CompoundNBT tag = new CompoundNBT(); + tag.putString("Fluid", String.valueOf(fluid.getFluid().getRegistryName())); + if (fluid.hasTag()) { + tag.put("FluidTag", fluid.getTag()); + } + stack.setTag(tag); + return stack; + } + + @Nullable + public static IAEItemStack newAeStack(@Nonnull FluidStack fluid) { + if (fluid.isEmpty()) { + return null; + } + IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid)); + if (stack == null) { + return null; + } + stack.setStackSize(fluid.getAmount()); + return stack; + } + + @Nullable + public static IAEItemStack newAeStack(@Nullable IAEFluidStack fluid) { + if (fluid == null || fluid.getStackSize() <= 0) { + return null; + } + IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid.getFluidStack())); + if (stack == null) { + return null; + } + stack.setStackSize(fluid.getStackSize()); + return stack; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java new file mode 100644 index 000000000..d1e1b7bfc --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java @@ -0,0 +1,206 @@ +package com.glodblock.github.common.item; + +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.storage.data.IAEItemStack; +import appeng.core.Api; +import appeng.core.localization.GuiText; +import appeng.items.misc.EncodedPatternItem; +import appeng.util.Platform; +import appeng.util.item.AEItemStack; +import com.glodblock.github.interfaces.HasCustomModel; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.FluidPatternDetails; +import com.glodblock.github.util.InvalidFCPatternHelper; +import com.glodblock.github.util.NameConst; +import com.google.common.base.Preconditions; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fluids.FluidStack; + +import java.util.Collection; +import java.util.List; + +import static com.glodblock.github.loader.FCItems.defaultProps; + +public class ItemFluidEncodedPattern extends EncodedPatternItem implements HasCustomModel { + + public ItemFluidEncodedPattern() { + super(defaultProps()); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void addInformation(ItemStack stack, World world, List lines, ITooltipFlag advancedTooltips) { + final ICraftingPatternDetails details = Api.instance().crafting().decodePattern(stack, world); + if (details == null) { + if (!stack.hasTag()) { + return; + } + stack.setDisplayName(GuiText.InvalidPattern.text().deepCopy().mergeStyle(TextFormatting.RED)); + InvalidFCPatternHelper invalid = new InvalidFCPatternHelper(stack); + final ITextComponent label = (GuiText.Creates.text()).deepCopy().appendString(": "); + final ITextComponent and = new StringTextComponent(" ").deepCopy().appendSibling(GuiText.And.text()) + .deepCopy() + .appendString(" "); + final ITextComponent with = GuiText.With.text().deepCopy().appendString(": "); + boolean first = true; + for (final InvalidFCPatternHelper.PatternIngredient output : invalid.getOutputs()) { + lines.add((first ? label : and).deepCopy().appendSibling(output.getFormattedToolTip())); + first = false; + } + + first = true; + for (final InvalidFCPatternHelper.PatternIngredient input : invalid.getInputs()) { + lines.add((first ? with : and).deepCopy().appendSibling(input.getFormattedToolTip())); + first = false; + } + return; + } + + if (stack.hasDisplayName()) { + stack.removeChildTag("display"); + } + + final boolean isCrafting = details.isCraftable(); + final boolean substitute = details.canSubstitute(); + + final Collection in = details.getInputs(); + final Collection out = details.getOutputs(); + + final ITextComponent label = (isCrafting ? GuiText.Crafts.text() : GuiText.Creates.text()).deepCopy() + .appendString(": "); + final ITextComponent and = new StringTextComponent(" ").deepCopy().appendSibling(GuiText.And.text()) + .appendString(" "); + final ITextComponent with = GuiText.With.text().deepCopy().appendString(": "); + + boolean first = true; + for (final IAEItemStack anOut : out) { + if (anOut == null) { + continue; + } + + lines.add((first ? label : and).deepCopy().appendString(anOut.getStackSize() + "x ") + .appendSibling(Platform.getItemDisplayName(anOut))); + first = false; + } + + first = true; + for (final IAEItemStack anIn : in) { + if (anIn == null) { + continue; + } + + lines.add((first ? with : and).deepCopy().appendString(anIn.getStackSize() + "x ") + .appendSibling(Platform.getItemDisplayName(anIn))); + first = false; + } + + if (isCrafting) { + final ITextComponent substitutionLabel = GuiText.Substitute.text().deepCopy().appendString(" "); + final ITextComponent canSubstitute = substitute ? GuiText.Yes.text() : GuiText.No.text(); + + lines.add(substitutionLabel.deepCopy().appendSibling(canSubstitute)); + } + } + + public ICraftingPatternDetails getDetails(ItemStack stack) { + return FluidPatternDetails.fromPattern(stack); + } + + @Override + public boolean isEncodedPattern(ItemStack itemStack) { + return itemStack != null && !itemStack.isEmpty() && itemStack.getItem() instanceof ItemFluidEncodedPattern && itemStack.getTag() != null && itemStack.getTag().contains("in") && itemStack.getTag().contains("out"); + } + + @Override + public ItemStack getOutput(ItemStack item) { + ICraftingPatternDetails d = getDetails(item); + if (d == null) { + return ItemStack.EMPTY; + } else { + return d.getOutputs().size() > 0 ? + d.getOutputs().get(0).createItemStack() : + ItemStack.EMPTY; + } + } + + @Override + public ResourceLocation getCraftingRecipeId(ItemStack itemStack) { + return null; + } + + @Override + public List getIngredients(ItemStack itemStack) { + ICraftingPatternDetails d = getDetails(itemStack); + Preconditions.checkArgument(d != null, "Invalid fluid pattern inputs!"); + return d.getInputs(); + } + + @Override + public List getProducts(ItemStack itemStack) { + ICraftingPatternDetails d = getDetails(itemStack); + Preconditions.checkArgument(d != null, "Invalid fluid pattern outputs!"); + return d.getOutputs(); + } + + @Override + public boolean allowsSubstitution(ItemStack itemStack) { + return false; + } + + @Override + public ResourceLocation getCustomModelPath() { + return NameConst.MODEL_DENSE_ENCODED_PATTERN; + } + + public ItemStack encodeStack(ItemStack[] inputs, ItemStack[] outputs) { + ItemStack pattern = new ItemStack(FCItems.DENSE_ENCODED_PATTERN); + ListNBT tagIn = new ListNBT(); + ListNBT tagOut = new ListNBT(); + CompoundNBT v = new CompoundNBT(); + for (ItemStack stack : inputs) { + tagIn.add(encodeNBT(stack)); + } + for (ItemStack stack : outputs) { + tagOut.add(encodeNBT(stack)); + } + v.put("in", tagIn); + v.put("out", tagOut); + pattern.setTag(v); + return pattern; + } + + private CompoundNBT encodeNBT(ItemStack stack) { + if (stack.isEmpty()) { + return new CompoundNBT(); + } else { + CompoundNBT tag = new CompoundNBT(); + if (stack.getItem() instanceof ItemFluidPacket) { + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + if (!fluid.isEmpty()) { + IAEItemStack drop = ItemFluidDrop.newAeStack(fluid); + if (drop != null) { + drop.writeToNBT(tag); + } + } + } else { + IAEItemStack aeStack = AEItemStack.fromItemStack(stack); + if (aeStack != null) { + aeStack.writeToNBT(tag); + } + } + return tag; + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java new file mode 100644 index 000000000..cca028ec7 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java @@ -0,0 +1,130 @@ +package com.glodblock.github.common.item; + +import appeng.api.storage.data.IAEItemStack; +import appeng.util.item.AEItemStack; +import com.glodblock.github.interfaces.HasCustomModel; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +import static com.glodblock.github.loader.FCItems.defaultProps; + +public class ItemFluidPacket extends Item implements HasCustomModel { + + public ItemFluidPacket() { + super(defaultProps().maxStackSize(1)); + } + + @Override + public void fillItemGroup(@Nonnull ItemGroup tab, @Nonnull NonNullList items) { + // NO-OP + } + + @Override + @Nonnull + public ITextComponent getDisplayName(@Nonnull ItemStack stack) { + FluidStack fluid = getFluidStack(stack); + boolean display = isDisplay(stack); + if (display) { + return !fluid.isEmpty() ? fluid.getDisplayName() : super.getDisplayName(stack); + } + return !fluid.isEmpty() ? + new TranslationTextComponent(NameConst.TT_FLUID_PACKET_INFO, fluid.getDisplayName(), String.format("%,d", fluid.getAmount())) + : super.getDisplayName(stack); + } + + @Override + public void addInformation(@Nonnull ItemStack stack, @Nullable World worldIn, @Nonnull List tooltip, @Nonnull ITooltipFlag flagIn) { + FluidStack fluid = getFluidStack(stack); + boolean display = isDisplay(stack); + if (display) return; + if (!fluid.isEmpty()) { + tooltip.add(new TranslationTextComponent(NameConst.TT_FLUID_PACKET).mergeStyle(TextFormatting.GRAY)); + } else { + tooltip.add(new TranslationTextComponent(NameConst.TT_INVALID_FLUID).mergeStyle(TextFormatting.RED)); + } + } + + @Nonnull + public static FluidStack getFluidStack(ItemStack stack) { + if (stack.isEmpty() || !stack.hasTag()) { + return FluidStack.EMPTY; + } + return FluidStack.loadFluidStackFromNBT(Objects.requireNonNull(stack.getTag()).getCompound("FluidStack")); + } + + public static boolean isDisplay(ItemStack stack) { + if (stack.isEmpty() || !stack.hasTag()) { + return false; + } + assert stack.getTag() != null; + return stack.getTag().getBoolean("DisplayOnly"); + } + + @Nonnull + public static FluidStack getFluidStack(@Nullable IAEItemStack stack) { + return stack != null ? getFluidStack(stack.getDefinition()) : FluidStack.EMPTY; + } + + @Nonnull + public static ItemStack newStack(@Nonnull FluidStack fluid) { + if (fluid.isEmpty()) { + return ItemStack.EMPTY; + } + ItemStack stack = new ItemStack(FCItems.FLUID_PACKET); + CompoundNBT tag = new CompoundNBT(); + CompoundNBT fluidTag = new CompoundNBT(); + fluid.writeToNBT(fluidTag); + tag.put("FluidStack", fluidTag); + stack.setTag(tag); + return stack; + } + + @Nonnull + public static ItemStack newDisplayStack(@Nonnull FluidStack fluid) { + if (fluid.isEmpty()) { + return ItemStack.EMPTY; + } + FluidStack copy = fluid.copy(); + copy.setAmount(1000); + ItemStack stack = new ItemStack(FCItems.FLUID_PACKET); + CompoundNBT tag = new CompoundNBT(); + CompoundNBT fluidTag = new CompoundNBT(); + copy.writeToNBT(fluidTag); + tag.put("FluidStack", fluidTag); + tag.putBoolean("DisplayOnly", true); + stack.setTag(tag); + return stack; + } + + @Nullable + public static IAEItemStack newAeStack(@Nonnull FluidStack fluid) { + return AEItemStack.fromItemStack(newStack(fluid)); + } + + @Override + public ResourceLocation getCustomModelPath() { + return NameConst.MODEL_FLUID_PACKET; + } + + public static boolean isFluidPacket(ItemStack is) { + return !is.isEmpty() && is.getItem() instanceof ItemFluidPacket; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java new file mode 100644 index 000000000..172548205 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java @@ -0,0 +1,41 @@ +package com.glodblock.github.common.item; + +import appeng.api.parts.IPartItem; +import appeng.core.Api; +import com.glodblock.github.common.part.PartDualInterface; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUseContext; +import net.minecraft.util.ActionResultType; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.glodblock.github.loader.FCItems.defaultProps; + +public class ItemPartDualInterface extends Item implements IPartItem { + + public ItemPartDualInterface() { + super(defaultProps()); + } + + @Nullable + @Override + public PartDualInterface createPart(ItemStack is) { + return new PartDualInterface(is); + } + + @Override + @Nonnull + public ActionResultType onItemUse(@Nonnull ItemUseContext context) { + return Api.instance().partHelper().placeBus( + context.getItem(), + context.getPos(), + context.getFace(), + context.getPlayer(), + context.getHand(), + context.getWorld() + ); + } + +} diff --git a/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java new file mode 100644 index 000000000..812b44c31 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java @@ -0,0 +1,41 @@ +package com.glodblock.github.common.item; + +import appeng.api.parts.IPartItem; +import appeng.core.Api; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUseContext; +import net.minecraft.util.ActionResultType; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.glodblock.github.loader.FCItems.defaultProps; + +public class ItemPartFluidPatternTerminal extends Item implements IPartItem { + + public ItemPartFluidPatternTerminal() { + super(defaultProps()); + } + + @Nullable + @Override + public PartFluidPatternTerminal createPart(ItemStack is) { + return new PartFluidPatternTerminal(is); + } + + @Override + @Nonnull + public ActionResultType onItemUse(@Nonnull ItemUseContext context) { + return Api.instance().partHelper().placeBus( + context.getItem(), + context.getPos(), + context.getFace(), + context.getPlayer(), + context.getHand(), + context.getWorld() + ); + } + +} diff --git a/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java b/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java new file mode 100644 index 000000000..fcdbc2f0c --- /dev/null +++ b/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java @@ -0,0 +1,204 @@ +package com.glodblock.github.common.me; + +import appeng.api.config.Actionable; +import appeng.api.config.Upgrades; +import appeng.api.networking.IGridNode; +import appeng.api.networking.crafting.ICraftingLink; +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.networking.crafting.ICraftingProviderHelper; +import appeng.api.networking.events.MENetworkChannelsChanged; +import appeng.api.networking.events.MENetworkPowerStatusChange; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.util.IConfigManager; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.fluids.util.AEFluidInventory; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.me.helpers.AENetworkProxy; +import appeng.util.SettingsFrom; +import appeng.util.inv.InvOperation; +import com.google.common.collect.ImmutableSet; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class DualityDualInterface implements ICapabilityProvider { + + private final DualityInterface itemDuality; + private final DualityFluidInterface fluidDuality; + + public DualityDualInterface(AENetworkProxy networkProxy, H host) { + this.itemDuality = new DualityInterface(networkProxy, host); + this.fluidDuality = new DualityFluidInterface(networkProxy, host); + } + + public DualityInterface getItemInterface() { + return itemDuality; + } + + public DualityFluidInterface getFluidInterface() { + return fluidDuality; + } + + public IConfigManager getConfigManager() { + return itemDuality.getConfigManager(); // fluid interface has no meaningful config, so this is fine + } + + public int getInstalledUpgrades(final Upgrades u) { + return itemDuality.getInstalledUpgrades(u) + fluidDuality.getInstalledUpgrades(u); + } + + public int getPriority() { + return itemDuality.getPriority(); // both interfaces should always have the same priority + } + + public void setPriority(final int newValue) { + itemDuality.setPriority(newValue); + fluidDuality.setPriority(newValue); + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability capability, @Nullable Direction facing) { + LazyOptional capInst = itemDuality.getCapability(capability, facing); + return capInst.isPresent() ? capInst : fluidDuality.getCapability(capability, facing); + } + + // dual behaviour + + public void initialize() { + itemDuality.initialize(); + } + + public TickingRequest getTickingRequest(IGridNode node) { + TickingRequest item = itemDuality.getTickingRequest(node), fluid = fluidDuality.getTickingRequest(node); + return new TickingRequest( + Math.min(item.minTickRate, fluid.minTickRate), + Math.max(item.maxTickRate, fluid.maxTickRate), + item.isSleeping && fluid.isSleeping, // might cause some unnecessary ticking, but oh well + true); + } + + public TickRateModulation onTick(IGridNode node, int ticksSinceLastCall) { + TickRateModulation item = itemDuality.tickingRequest(node, ticksSinceLastCall); + TickRateModulation fluid = fluidDuality.tickingRequest(node, ticksSinceLastCall); + if (item.ordinal() >= fluid.ordinal()) { // return whichever is most urgent + return item; + } else { + return fluid; + } + } + + public void onChannelStateChange(final MENetworkChannelsChanged c) { + itemDuality.notifyNeighbors(); + fluidDuality.notifyNeighbors(); + } + + public void onPowerStateChange(final MENetworkPowerStatusChange c) { + itemDuality.notifyNeighbors(); + fluidDuality.notifyNeighbors(); + } + + public void onGridChanged() { + itemDuality.gridChanged(); + fluidDuality.gridChanged(); + } + + public void addDrops(List drops) { + itemDuality.addDrops(drops); + } + + public boolean canInsertItem(ItemStack stack) { + return itemDuality.canInsert(stack); + } + + public IItemHandler getItemInventoryByName(String name) { + return itemDuality.getInventoryByName(name); + } + + public IItemHandler getInternalItemInventory() { + return itemDuality.getInternalInventory(); + } + + public void onItemInventoryChange(IItemHandler inv, int slot, InvOperation op, ItemStack removed, ItemStack added) { + itemDuality.onChangeInventory(inv, slot, op, removed, added); + } + + // autocrafting + + public boolean pushPattern(ICraftingPatternDetails patternDetails, CraftingInventory table) { + return itemDuality.pushPattern(patternDetails, table); + } + + public boolean isCraftingBusy() { + return itemDuality.isBusy(); + } + + public void provideCrafting(ICraftingProviderHelper craftingTracker) { + itemDuality.provideCrafting(craftingTracker); + } + + public ImmutableSet getRequestCraftingJobs() { + return itemDuality.getRequestedJobs(); + } + + public IAEItemStack injectCraftedItems(ICraftingLink link, IAEItemStack items, Actionable mode) { + return itemDuality.injectCraftedItems(link, items, mode); + } + + public void onCraftingJobStateChange(ICraftingLink link) { + itemDuality.jobStateChange(link); + } + + // serialization + + public void writeToNBT(final CompoundNBT data) { + CompoundNBT itemIfaceTag = new CompoundNBT(), fluidIfaceTag = new CompoundNBT(); + itemDuality.writeToNBT(itemIfaceTag); + fluidDuality.writeToNBT(fluidIfaceTag); + data.put("itemDuality", itemIfaceTag); + data.put("fluidDuality", fluidIfaceTag); + } + + public void readFromNBT(final CompoundNBT data) { + itemDuality.readFromNBT(data.getCompound("itemDuality")); + fluidDuality.readFromNBT(data.getCompound("fluidDuality")); + } + + public CompoundNBT downloadSettings(SettingsFrom from) { + CompoundNBT tag = new CompoundNBT(); + if (from == SettingsFrom.MEMORY_CARD) { + final IFluidHandler fluidInv = this.fluidDuality.getFluidInventoryByName("config"); + if (fluidInv instanceof AEFluidInventory) { + ((AEFluidInventory) fluidInv).writeToNBT(tag, "fluid_config"); + } + } + return tag; + } + + public void uploadSettings(SettingsFrom from, CompoundNBT compound) { + final IFluidHandler fluidInv = this.fluidDuality.getFluidInventoryByName("config"); + if (from == SettingsFrom.MEMORY_CARD && fluidInv instanceof AEFluidInventory) { + AEFluidInventory target = (AEFluidInventory) fluidInv; + AEFluidInventory tmp = new AEFluidInventory(null, target.getSlots()); + tmp.readFromNBT(compound, "fluid_config"); + for(int x = 0; x < tmp.getSlots(); x++) { + target.setFluidInSlot(x, tmp.getFluidInSlot(x)); + } + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/part/PartDualInterface.java b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java new file mode 100644 index 000000000..a407b7069 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java @@ -0,0 +1,261 @@ +package com.glodblock.github.common.part; + +import appeng.api.config.Actionable; +import appeng.api.config.Upgrades; +import appeng.api.networking.IGridNode; +import appeng.api.networking.crafting.ICraftingLink; +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.networking.crafting.ICraftingProviderHelper; +import appeng.api.networking.events.MENetworkChannelsChanged; +import appeng.api.networking.events.MENetworkEventSubscribe; +import appeng.api.networking.events.MENetworkPowerStatusChange; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.api.parts.IPartCollisionHelper; +import appeng.api.parts.IPartModel; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.util.AECableType; +import appeng.api.util.IConfigManager; +import appeng.container.ContainerLocator; +import appeng.container.ContainerOpener; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.helpers.IPriorityHost; +import appeng.items.parts.PartModels; +import appeng.parts.BasicStatePart; +import appeng.parts.PartModel; +import appeng.util.Platform; +import appeng.util.inv.IAEAppEngInventory; +import appeng.util.inv.IInventoryDestination; +import appeng.util.inv.InvOperation; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerItemDualInterface; +import com.glodblock.github.common.me.DualityDualInterface; +import com.glodblock.github.loader.FCItems; +import com.google.common.collect.ImmutableSet; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import java.util.EnumSet; +import java.util.List; + +public class PartDualInterface extends BasicStatePart + implements IGridTickable, IInventoryDestination, IInterfaceHost, IAEAppEngInventory, IPriorityHost, IFluidInterfaceHost { + + @PartModels + public static ResourceLocation[] MODELS = new ResourceLocation[] { + new ResourceLocation(FluidCraft.MODID, "part/interface_base"), + new ResourceLocation(FluidCraft.MODID, "part/interface_on"), + new ResourceLocation(FluidCraft.MODID, "part/interface_off"), + new ResourceLocation(FluidCraft.MODID, "part/interface_has_channel") + }; + + public static final PartModel MODELS_OFF = new PartModel(MODELS[0], MODELS[2]); + public static final PartModel MODELS_ON = new PartModel(MODELS[0], MODELS[1]); + public static final PartModel MODELS_HAS_CHANNEL = new PartModel(MODELS[0], MODELS[3]); + + private final DualityDualInterface duality = new DualityDualInterface<>(getProxy(), this); + + public PartDualInterface(final ItemStack is) { + super(is); + } + + @MENetworkEventSubscribe + public void stateChange(final MENetworkChannelsChanged c) { + duality.onChannelStateChange(c); + } + + @MENetworkEventSubscribe + public void stateChange(final MENetworkPowerStatusChange c) { + duality.onPowerStateChange(c); + } + + @Override + public void getBoxes(final IPartCollisionHelper bch) { + bch.addBox(2, 2, 14, 14, 14, 16); + bch.addBox(5, 5, 12, 11, 11, 14); + } + + @Override + public int getInstalledUpgrades(final Upgrades u) { + return duality.getInstalledUpgrades(u); + } + + @Override + public void gridChanged() { + duality.onGridChanged(); + } + + @Override + public void readFromNBT(final CompoundNBT data) { + super.readFromNBT(data); + duality.readFromNBT(data); + } + + @Override + public void writeToNBT(final CompoundNBT data) { + super.writeToNBT(data); + duality.writeToNBT(data); + } + + @Override + public void addToWorld() { + super.addToWorld(); + duality.initialize(); + } + + @Override + public void getDrops(final List drops, final boolean wrenched) { + duality.addDrops(drops); + } + + @Override + public float getCableConnectionLength(AECableType cable) { + return 4; + } + + @Override + public IConfigManager getConfigManager() { + return duality.getConfigManager(); + } + + @Override + public IItemHandler getInventoryByName(final String name) { + return duality.getItemInventoryByName(name); + } + + @Override + public boolean onPartActivate(PlayerEntity player, Hand hand, Vector3d pos) { + if (Platform.isServer()) { + ContainerOpener.openContainer(ContainerItemDualInterface.TYPE, player, ContainerLocator.forPart(this)); + } + return true; + } + + @Override + public boolean canInsert(final ItemStack stack) { + return duality.canInsertItem(stack); + } + + @Override + @Nonnull + public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { + return duality.getTickingRequest(node); + } + + @Override + @Nonnull + public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { + return duality.onTick(node, ticksSinceLastCall); + } + + @Override + public void onChangeInventory(final IItemHandler inv, final int slot, final InvOperation mc, + final ItemStack removedStack, final ItemStack newStack) { + duality.onItemInventoryChange(inv, slot, mc, removedStack, newStack); + } + + @Override + public DualityInterface getInterfaceDuality() { + return duality.getItemInterface(); + } + + @Override + public DualityFluidInterface getDualityFluidInterface() { + return duality.getFluidInterface(); + } + + @Override + public EnumSet getTargets() { + return EnumSet.of(this.getSide().getFacing()); + } + + @Override + public TileEntity getTileEntity() { + return super.getHost().getTile(); + } + + @Override + public boolean pushPattern(final ICraftingPatternDetails patternDetails, final CraftingInventory table) { + return duality.pushPattern(patternDetails, table); + } + + @Override + public boolean isBusy() { + return duality.isCraftingBusy(); + } + + @Override + public void provideCrafting(final ICraftingProviderHelper craftingTracker) { + duality.provideCrafting(craftingTracker); + } + + @Override + public ImmutableSet getRequestedJobs() { + return duality.getRequestCraftingJobs(); + } + + @Override + public IAEItemStack injectCraftedItems(final ICraftingLink link, final IAEItemStack items, final Actionable mode) { + return duality.injectCraftedItems(link, items, mode); + } + + @Override + public void jobStateChange(final ICraftingLink link) { + duality.onCraftingJobStateChange(link); + } + + @Override + public int getPriority() { + return duality.getPriority(); + } + + @Override + public void setPriority(final int newValue) { + duality.setPriority(newValue); + } + + @Nonnull + @Override + public LazyOptional getCapability(Capability capabilityClass) { + return duality.getCapability(capabilityClass, getSide().getFacing()); + } + + @Override + public ItemStack getItemStackRepresentation() { + return new ItemStack(FCItems.PART_DUAL_INTERFACE, 1); + } + + @Override + public ContainerType getContainerType() { + return ContainerItemDualInterface.TYPE; + } + + @Nonnull + @Override + public IPartModel getStaticModels() { + if (this.isActive() && this.isPowered()) { + return MODELS_HAS_CHANNEL; + } else if (this.isPowered()) { + return MODELS_ON; + } else { + return MODELS_OFF; + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java new file mode 100644 index 000000000..3caf53a89 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java @@ -0,0 +1,175 @@ +package com.glodblock.github.common.part; + +import appeng.api.config.SecurityPermissions; +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.parts.IPartModel; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; +import appeng.container.me.items.ItemTerminalContainer; +import appeng.items.parts.PartModels; +import appeng.parts.PartModel; +import appeng.parts.reporting.PatternTerminalPart; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.Platform; +import appeng.util.inv.InvOperation; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidEncodedPattern; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.util.FCUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; + +public class PartFluidPatternTerminal extends PatternTerminalPart { + + private boolean combine = false; + private boolean fluidFirst = false; + private final AppEngInternalInventory crafting; + private final AppEngInternalInventory output; + private final AppEngInternalInventory pattern; + + @PartModels + public static ResourceLocation[] MODELS = new ResourceLocation[] { + new ResourceLocation(FluidCraft.MODID, "part/f_pattern_term_on"), // 0 + new ResourceLocation(FluidCraft.MODID, "part/f_pattern_term_off"), // 1 + }; + + private static final IPartModel MODELS_ON = new PartModel(MODEL_BASE, MODELS[0], MODEL_STATUS_ON); + private static final IPartModel MODELS_OFF = new PartModel(MODEL_BASE, MODELS[1], MODEL_STATUS_OFF); + private static final IPartModel MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, MODELS[0], MODEL_STATUS_HAS_CHANNEL); + + public PartFluidPatternTerminal(ItemStack is) { + super(is); + this.crafting = (AppEngInternalInventory) getInventoryByName("crafting"); + this.output = (AppEngInternalInventory) getInventoryByName("output"); + this.pattern = (AppEngInternalInventory) getInventoryByName("pattern"); + } + + @Nonnull + @Override + public IPartModel getStaticModels() { + return this.selectModel(MODELS_OFF, MODELS_ON, MODELS_HAS_CHANNEL); + } + + @Override + public void readFromNBT(CompoundNBT data) { + super.readFromNBT(data); + combine = data.getBoolean("combineMode"); + fluidFirst = data.getBoolean("fluidFirst"); + } + + public void setCombineMode(boolean value) { + this.combine = value; + } + + public boolean getCombineMode() { + return this.combine; + } + + public void setFluidPlaceMode(boolean value) { + this.fluidFirst = value; + } + + public boolean getFluidPlaceMode() { + return this.fluidFirst; + } + + @Override + public void writeToNBT(CompoundNBT data) { + super.writeToNBT(data); + data.putBoolean("combineMode", combine); + data.putBoolean("fluidFirst", fluidFirst); + } + + @Override + public ContainerType getContainerType(PlayerEntity p) { + return Platform.checkPermissions(p, this, SecurityPermissions.CRAFT, false) ? + ContainerFluidPatternTerminal.TYPE : ItemTerminalContainer.TYPE; + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, + ItemStack newStack) { + if (slot == 1 && inv == this.pattern) { + final ItemStack is = inv.getStackInSlot(1); + if (!is.isEmpty() && is.getItem() instanceof ItemFluidEncodedPattern) { + final ItemFluidEncodedPattern pattern = (ItemFluidEncodedPattern) is.getItem(); + final ICraftingPatternDetails details = pattern.getDetails(is); + if(details != null) { + this.setCraftingRecipe(details.isCraftable()); + this.setSubstitution(details.canSubstitute()); + for(int x = 0; x < this.crafting.getSlots(); x ++) { + this.crafting.setStackInSlot(x, ItemStack.EMPTY); + } + for(int x = 0; x < this.output.getSlots(); x ++) { + this.output.setStackInSlot(x, ItemStack.EMPTY); + } + putPattern(details.getInputs().toArray(new IAEItemStack[0]), details.getOutputs().toArray(new IAEItemStack[0])); + } + this.getHost().markForSave(); + return; + } + } + super.onChangeInventory(inv, slot, mc, removedStack, newStack); + } + + public void onChangeCrafting(Int2ObjectMap inputs, ItemStack[] outputs, boolean combine) { + IItemHandler crafting = this.getInventoryByName("crafting"); + IItemHandler output = this.getInventoryByName("output"); + IItemList storageList = this.getInventory(FCUtil.ITEM) == null ? + null : this.getInventory(FCUtil.ITEM).getStorageList(); + if (crafting instanceof AppEngInternalInventory && output instanceof AppEngInternalInventory) { + FCUtil.clearItemInventory((IItemHandlerModifiable) crafting); + FCUtil.clearItemInventory((IItemHandlerModifiable) output); + ItemStack[] fuzzyFind = new ItemStack[FCUtil.findMax(inputs.keySet()) + 1]; + for (int index : inputs.keySet()) { + FCUtil.fuzzyTransferItems(index, inputs.get(index), fuzzyFind, storageList); + } + if (combine && !this.isCraftingRecipe()) { + fuzzyFind = FCUtil.compress(fuzzyFind); + } + int bound = Math.min(crafting.getSlots(), fuzzyFind.length); + for (int x = 0; x < bound; x++) { + final ItemStack item = fuzzyFind[x]; + ((AppEngInternalInventory) crafting).setStackInSlot(x, item == null ? ItemStack.EMPTY : item); + } + bound = Math.min(output.getSlots(), outputs.length); + for (int x = 0; x < bound; x++) { + final ItemStack item = outputs[x]; + ((AppEngInternalInventory) output).setStackInSlot(x, item == null ? ItemStack.EMPTY : item); + } + } + } + + public void putPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) { + for( int x = 0; x < this.getInventoryByName("crafting").getSlots() && x < inputs.length; x++ ) + { + final IAEItemStack item = inputs[x]; + if (item != null && item.getItem() instanceof ItemFluidDrop) { + ItemStack packet = ItemFluidPacket.newStack(ItemFluidDrop.getFluidStack(item.createItemStack())); + ((AppEngInternalInventory) this.getInventoryByName("crafting")).setStackInSlot(x, packet); + } + else ((AppEngInternalInventory) this.getInventoryByName("crafting")).setStackInSlot( x, item == null ? ItemStack.EMPTY : item.createItemStack() ); + } + + for( int x = 0; x < this.getInventoryByName("output").getSlots() && x < outputs.length; x++ ) + { + final IAEItemStack item = outputs[x]; + if (item != null && item.getItem() instanceof ItemFluidDrop) { + ItemStack packet = ItemFluidPacket.newStack(ItemFluidDrop.getFluidStack(item.createItemStack())); + ((AppEngInternalInventory) this.getInventoryByName("output")).setStackInSlot(x, packet); + } + else ((AppEngInternalInventory) this.getInventoryByName("output")).setStackInSlot( x, item == null ? ItemStack.EMPTY : item.createItemStack() ); + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java new file mode 100644 index 000000000..349eae179 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java @@ -0,0 +1,327 @@ +package com.glodblock.github.common.tile; + +import appeng.api.config.Actionable; +import appeng.api.config.Upgrades; +import appeng.api.networking.GridFlags; +import appeng.api.networking.IGridNode; +import appeng.api.networking.crafting.ICraftingLink; +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.networking.crafting.ICraftingProviderHelper; +import appeng.api.networking.events.MENetworkChannelsChanged; +import appeng.api.networking.events.MENetworkEventSubscribe; +import appeng.api.networking.events.MENetworkPowerStatusChange; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.util.AECableType; +import appeng.api.util.AEPartLocation; +import appeng.api.util.DimensionalCoord; +import appeng.api.util.IConfigManager; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.helpers.IPriorityHost; +import appeng.tile.grid.AENetworkInvTileEntity; +import appeng.util.Platform; +import appeng.util.SettingsFrom; +import appeng.util.inv.IInventoryDestination; +import appeng.util.inv.InvOperation; +import com.glodblock.github.client.container.ContainerItemDualInterface; +import com.glodblock.github.common.me.DualityDualInterface; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.FCUtil; +import com.google.common.collect.ImmutableSet; +import net.minecraft.block.BlockState; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.EnumSet; +import java.util.List; + +public class TileDualInterface extends AENetworkInvTileEntity + implements IGridTickable, IInventoryDestination, IInterfaceHost, IPriorityHost, IFluidInterfaceHost { + + public TileDualInterface() { + super(FCUtil.getTileType(TileDualInterface.class, FCBlocks.DUAL_INTERFACE)); + getProxy().setIdlePowerUsage(4D); + getProxy().setFlags(GridFlags.REQUIRE_CHANNEL); + } + + private final DualityDualInterface duality = new DualityDualInterface<>(getProxy(), this); + + // Indicates that this interface has no specific direction set + private boolean omniDirectional = true; + + @MENetworkEventSubscribe + public void stateChange(final MENetworkChannelsChanged c) { + duality.onChannelStateChange(c); + } + + @MENetworkEventSubscribe + public void stateChange(final MENetworkPowerStatusChange c) { + duality.onPowerStateChange(c); + } + + public void setSide(final Direction facing) { + if (Platform.isClient()) { + return; + } + + Direction newForward; + + if (!this.omniDirectional && this.getForward() == facing.getOpposite()) { + newForward = facing; + } else if (!this.omniDirectional + && (this.getForward() == facing || this.getForward() == facing.getOpposite())) { + newForward = facing; + this.omniDirectional = true; + } else if (this.omniDirectional) { + newForward = facing.getOpposite(); + this.omniDirectional = false; + } else { + newForward = Platform.rotateAround(this.getForward(), facing); + } + + if (this.omniDirectional) { + this.setOrientation(Direction.NORTH, Direction.UP); + } else { + Direction newUp = Direction.UP; + if (newForward == Direction.UP || newForward == Direction.DOWN) { + newUp = Direction.NORTH; + } + this.setOrientation(newForward, newUp); + } + + this.configureNodeSides(); + this.markForUpdate(); + this.saveChanges(); + } + + private void configureNodeSides() { + if (this.omniDirectional) { + this.getProxy().setValidSides(EnumSet.allOf(Direction.class)); + } else { + this.getProxy().setValidSides(EnumSet.complementOf(EnumSet.of(this.getForward()))); + } + } + + @Override + public void getDrops(final World w, final BlockPos pos, final List drops) { + duality.addDrops(drops); + } + + @Override + public void gridChanged() { + duality.onGridChanged(); + } + + @Override + public void onReady() { + this.configureNodeSides(); + super.onReady(); + duality.initialize(); + } + + @Override + public CompoundNBT write(final CompoundNBT data) { + super.write(data); + data.putBoolean("omniDirectional", this.omniDirectional); + duality.writeToNBT(data); + return data; + } + + @Override + public void read(BlockState blockState, CompoundNBT data) { + super.read(blockState, data); + this.omniDirectional = data.getBoolean("omniDirectional"); + duality.readFromNBT(data); + } + + @Override + protected boolean readFromStream(PacketBuffer data) throws IOException { + final boolean c = super.readFromStream(data); + boolean oldOmniDirectional = this.omniDirectional; + this.omniDirectional = data.readBoolean(); + return oldOmniDirectional != this.omniDirectional || c; + } + + @Override + protected void writeToStream(PacketBuffer data) throws IOException { + super.writeToStream(data); + data.writeBoolean(this.omniDirectional); + } + + @Override + @Nonnull + public AECableType getCableConnectionType(@Nonnull final AEPartLocation dir) { + return AECableType.SMART; + } + + @Override + public DimensionalCoord getLocation() { + return new DimensionalCoord(this.getTileEntity()); + } + + @Override + public boolean canInsert(final ItemStack stack) { + return duality.canInsertItem(stack); + } + + @Override + public IItemHandler getInventoryByName(final String name) { + return duality.getItemInventoryByName(name); + } + + @Override + @Nonnull + public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { + return duality.getTickingRequest(node); + } + + @Override + @Nonnull + public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { + return duality.onTick(node, ticksSinceLastCall); + } + + @Override + @Nonnull + public IItemHandler getInternalInventory() { + return duality.getInternalItemInventory(); + } + + @Override + public void onChangeInventory(final IItemHandler inv, final int slot, final InvOperation mc, + final ItemStack removed, final ItemStack added) { + duality.onItemInventoryChange(inv, slot, mc, removed, added); + } + + @Override + public DualityInterface getInterfaceDuality() { + return duality.getItemInterface(); + } + + @Override + public DualityFluidInterface getDualityFluidInterface() { + return duality.getFluidInterface(); + } + + @Override + public EnumSet getTargets() { + if (this.omniDirectional) { + return EnumSet.allOf(Direction.class); + } + return EnumSet.of(this.getForward()); + } + + @Override + public TileEntity getTileEntity() { + return this; + } + + @Override + public IConfigManager getConfigManager() { + return duality.getConfigManager(); + } + + @Override + public boolean pushPattern(final ICraftingPatternDetails patternDetails, final CraftingInventory table) { + return duality.pushPattern(patternDetails, table); + } + + @Override + public boolean isBusy() { + return duality.isCraftingBusy(); + } + + @Override + public void provideCrafting(final ICraftingProviderHelper craftingTracker) { + duality.provideCrafting(craftingTracker); + } + + @Override + public int getInstalledUpgrades(final Upgrades u) { + return duality.getInstalledUpgrades(u); + } + + @Override + public ImmutableSet getRequestedJobs() { + return duality.getRequestCraftingJobs(); + } + + @Override + public IAEItemStack injectCraftedItems(final ICraftingLink link, final IAEItemStack items, final Actionable mode) { + return duality.injectCraftedItems(link, items, mode); + } + + @Override + public void jobStateChange(final ICraftingLink link) { + duality.onCraftingJobStateChange(link); + } + + @Override + public int getPriority() { + return duality.getPriority(); + } + + @Override + public void setPriority(final int newValue) { + duality.setPriority(newValue); + } + + /** + * @return True if this interface is omni-directional. + */ + public boolean isOmniDirectional() { + return this.omniDirectional; + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability capability, @Nullable Direction facing) { + LazyOptional capInst = duality.getCapability(capability, facing); + return capInst.isPresent() ? capInst : super.getCapability(capability, facing); + } + + @Override + public ItemStack getItemStackRepresentation() { + return new ItemStack(FCBlocks.DUAL_INTERFACE); + } + + @Override + public ContainerType getContainerType() { + return ContainerItemDualInterface.TYPE; + } + + @Override + public CompoundNBT downloadSettings(SettingsFrom from) { + CompoundNBT pre = super.downloadSettings(from); + CompoundNBT tag = pre == null ? new CompoundNBT() : pre; + tag.put("pattern", this.duality.downloadSettings(from)); + return tag.isEmpty() ? null : tag; + } + + @Override + public void uploadSettings(SettingsFrom from, CompoundNBT compound) { + super.uploadSettings(from, compound); + if (compound.contains("pattern")) { + this.duality.uploadSettings(from, compound.getCompound("pattern")); + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java new file mode 100644 index 000000000..c3949b740 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java @@ -0,0 +1,288 @@ +package com.glodblock.github.common.tile; + +import appeng.api.config.Actionable; +import appeng.api.networking.GridFlags; +import appeng.api.networking.crafting.ICraftingGrid; +import appeng.api.networking.energy.IEnergyGrid; +import appeng.api.networking.events.*; +import appeng.api.networking.security.IActionSource; +import appeng.api.networking.storage.IBaseMonitor; +import appeng.api.networking.storage.IStorageGrid; +import appeng.api.storage.*; +import appeng.api.storage.cells.ICellContainer; +import appeng.api.storage.cells.ICellInventory; +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; +import appeng.me.GridAccessException; +import appeng.me.cache.CraftingGridCache; +import appeng.me.helpers.MachineSource; +import appeng.me.storage.MEInventoryHandler; +import appeng.tile.grid.AENetworkTileEntity; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.FCUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +public class TileFluidDiscretizer extends AENetworkTileEntity implements ICellContainer { + + private final FluidDiscretizingInventory fluidDropInv = new FluidDiscretizingInventory(); + private final FluidCraftingInventory fluidCraftInv = new FluidCraftingInventory(); + private final IActionSource ownActionSource = new MachineSource(this); + private boolean prevActiveState = false; + + public TileFluidDiscretizer() { + super(FCUtil.getTileType(TileFluidDiscretizer.class, FCBlocks.FLUID_DISCRETIZER)); + getProxy().setIdlePowerUsage(3D); + getProxy().setFlags(GridFlags.REQUIRE_CHANNEL); + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Override + public int getPriority() { + return Integer.MAX_VALUE; + } + + @SuppressWarnings("rawtypes") + @Override + public List getCellArray(IStorageChannel channel) { + if (getProxy().isActive()) { + if (channel == FCUtil.ITEM) { + return Collections.singletonList(fluidDropInv.invHandler); + } else if (channel == FCUtil.FLUID) { + return Collections.singletonList(fluidCraftInv.invHandler); + } + } + return Collections.emptyList(); + } + + @Override + public void saveChanges(@Nullable ICellInventory cellInventory) { + if (world != null) { + world.markChunkDirty(pos, this);// optimization, i guess? + } + } + + @Override + public void gridChanged() { + IMEMonitor fluidGrid = getFluidGrid(); + if (fluidGrid != null) { + fluidGrid.addListener(fluidDropInv, fluidGrid); + } + } + + @MENetworkEventSubscribe + public void onPowerUpdate(MENetworkPowerStatusChange event) { + updateState(); + } + + @MENetworkEventSubscribe + public void onChannelUpdate(MENetworkChannelsChanged event) { + updateState(); + } + + @MENetworkEventSubscribe + public void onStorageUpdate(MENetworkStorageEvent event) { + updateState(); + } + + private void updateState() { + boolean isActive = getProxy().isActive(); + if (isActive != prevActiveState) { + prevActiveState = isActive; + try { + getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate()); + } catch (GridAccessException e) { + // NO-OP + } + } + } + + @Override + public void blinkCell(int slot) { + // NO-OP + } + + @Nullable + private IEnergyGrid getEnergyGrid() { + try { + return getProxy().getGrid().getCache(IEnergyGrid.class); + } catch (GridAccessException e) { + return null; + } + } + + @Nullable + private IMEMonitor getFluidGrid() { + try { + return getProxy().getGrid().getCache(IStorageGrid.class) + .getInventory(FCUtil.FLUID); + } catch (GridAccessException e) { + return null; + } + } + + private class FluidDiscretizingInventory implements IMEInventory, IMEMonitorHandlerReceiver { + + private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel()); + @Nullable + private ObjectArrayList itemCache = null; + + FluidDiscretizingInventory() { + invHandler.setPriority(Integer.MAX_VALUE); + } + + @SuppressWarnings("DuplicatedCode") + @Nullable + @Override + public IAEItemStack extractItems(IAEItemStack request, Actionable mode, IActionSource src) { + IAEFluidStack fluidStack = ItemFluidDrop.getAeFluidStack(request); + if (fluidStack == null) { + return null; + } + IMEMonitor fluidGrid = getFluidGrid(); + if (fluidGrid == null) { + return null; + } + IEnergyGrid energyGrid = getEnergyGrid(); + if (energyGrid == null) { + return null; + } + return ItemFluidDrop.newAeStack(fluidGrid.extractItems(fluidStack, mode, ownActionSource)); + } + + @SuppressWarnings("DuplicatedCode") + @Nullable + @Override + public IAEItemStack injectItems(IAEItemStack input, Actionable type, IActionSource src) { + IAEFluidStack fluidStack = ItemFluidDrop.getAeFluidStack(input); + if (fluidStack == null) { + return input; + } + IMEMonitor fluidGrid = getFluidGrid(); + if (fluidGrid == null) { + return input; + } + IEnergyGrid energyGrid = getEnergyGrid(); + if (energyGrid == null) { + return input; + } + return ItemFluidDrop.newAeStack(fluidGrid.injectItems(fluidStack, type, ownActionSource)); + } + + @Override + public IItemList getAvailableItems(IItemList out) { + if (itemCache == null) { + itemCache = new ObjectArrayList<>(); + IMEMonitor fluidGrid = getFluidGrid(); + if (fluidGrid != null) { + for (IAEFluidStack fluid : fluidGrid.getStorageList()) { + IAEItemStack stack = ItemFluidDrop.newAeStack(fluid); + if (stack != null) { + itemCache.add(stack); + } + } + } + } + for (IAEItemStack stack : itemCache) { + out.addStorage(stack); + } + return out; + } + + @Override + public boolean isValid(Object verificationToken) { + IMEMonitor fluidGrid = getFluidGrid(); + return fluidGrid != null && fluidGrid == verificationToken; + } + + @Override + public void postChange(IBaseMonitor monitor, Iterable change, IActionSource actionSource) { + itemCache = null; + try { + ObjectArrayList mappedChanges = new ObjectArrayList<>(); + for (IAEFluidStack fluidStack : change) { + boolean isNg = false; + if (fluidStack.getStackSize() < 0) { + isNg = true; + fluidStack.setStackSize( - fluidStack.getStackSize() ); + } + IAEItemStack itemStack = ItemFluidDrop.newAeStack(fluidStack); + if (itemStack != null) { + if (isNg) itemStack.setStackSize( - itemStack.getStackSize() ); + mappedChanges.add(itemStack); + } + } + getProxy().getGrid().getCache(IStorageGrid.class) + .postAlterationOfStoredItems(getChannel(), mappedChanges, ownActionSource); + } catch (GridAccessException e) { + // NO-OP + } + } + + @Override + public void onListUpdate() { + // NO-OP + } + + @Override + public IStorageChannel getChannel() { + return FCUtil.ITEM; + } + + } + + private class FluidCraftingInventory implements IMEInventory { + + private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel()); + + FluidCraftingInventory() { + invHandler.setPriority(Integer.MAX_VALUE); + } + + @Nullable + @Override + public IAEFluidStack injectItems(IAEFluidStack input, Actionable type, IActionSource src) { + ICraftingGrid craftingGrid; + try { + craftingGrid = getProxy().getGrid().getCache(ICraftingGrid.class); + } catch (GridAccessException e) { + return null; + } + if (craftingGrid instanceof CraftingGridCache) { + IAEItemStack remaining = ((CraftingGridCache)craftingGrid).injectItems( + ItemFluidDrop.newAeStack(input), type, ownActionSource); + if (remaining != null) { + return ItemFluidDrop.getAeFluidStack(remaining); + } + } + return null; + } + + @Nullable + @Override + public IAEFluidStack extractItems(IAEFluidStack request, Actionable mode, IActionSource src) { + return null; + } + + @Override + public IItemList getAvailableItems(IItemList out) { + return out; + } + + @Override + public IStorageChannel getChannel() { + return FCUtil.FLUID; + } + + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java new file mode 100644 index 000000000..99fa7e24c --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java @@ -0,0 +1,125 @@ +package com.glodblock.github.common.tile; + +import appeng.api.networking.GridFlags; +import appeng.api.networking.IGridNode; +import appeng.api.networking.energy.IEnergyGrid; +import appeng.api.networking.security.IActionSource; +import appeng.api.networking.storage.IStorageGrid; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.api.storage.IMEMonitor; +import appeng.api.storage.data.IAEFluidStack; +import appeng.fluids.util.AEFluidStack; +import appeng.me.GridAccessException; +import appeng.me.helpers.MachineSource; +import appeng.tile.grid.AENetworkTileEntity; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.Platform; +import appeng.util.inv.IAEAppEngInventory; +import appeng.util.inv.InvOperation; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.FCUtil; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; + +public class TileFluidPacketDecoder extends AENetworkTileEntity implements IGridTickable, IAEAppEngInventory { + + private final AppEngInternalInventory inventory = new AppEngInternalInventory(this, 1); + private final IActionSource ownActionSource = new MachineSource(this); + + public TileFluidPacketDecoder() { + super(FCUtil.getTileType(TileFluidPacketDecoder.class, FCBlocks.FLUID_PACKET_DECODER)); + getProxy().setIdlePowerUsage(1.5D); + getProxy().setFlags(GridFlags.REQUIRE_CHANNEL); + } + + public IItemHandlerModifiable getInventory() { + return inventory; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Nonnull + @SuppressWarnings("unchecked") + @Override + public LazyOptional getCapability(@Nonnull Capability capability, Direction facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return LazyOptional.of(() -> (T)inventory); + } else { + return LazyOptional.empty(); + } + } + + @Override + @Nonnull + public TickingRequest getTickingRequest(@Nonnull IGridNode node) { + return new TickingRequest(5, 120, false, true); + } + + @Override + @Nonnull + public TickRateModulation tickingRequest(@Nonnull IGridNode node, int ticksSinceLastCall) { + ItemStack stack = inventory.getStackInSlot(0); + if (stack.isEmpty() || !(stack.getItem() instanceof ItemFluidPacket)) { + return TickRateModulation.SLEEP; + } + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + if (fluid.isEmpty()) { + inventory.setStackInSlot(0, ItemStack.EMPTY); + return TickRateModulation.SLEEP; + } + IAEFluidStack aeFluid = AEFluidStack.fromFluidStack(fluid); + IEnergyGrid energyGrid = node.getGrid().getCache(IEnergyGrid.class); + IMEMonitor fluidGrid = node.getGrid().getCache(IStorageGrid.class) + .getInventory(FCUtil.FLUID); + IAEFluidStack remaining = Platform.poweredInsert(energyGrid, fluidGrid, aeFluid, ownActionSource); + if (remaining != null) { + if (remaining.getStackSize() == aeFluid.getStackSize()) { + return TickRateModulation.SLOWER; + } + inventory.setStackInSlot(0, ItemFluidPacket.newStack(remaining.getFluidStack())); + return TickRateModulation.FASTER; + } else { + inventory.setStackInSlot(0, ItemStack.EMPTY); + return TickRateModulation.SLEEP; + } + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, ItemStack newStack) { + try { + getProxy().getTick().alertDevice(getProxy().getNode()); + } catch (GridAccessException e) { + // NO-OP + } + } + + @Override + public CompoundNBT write(CompoundNBT data) { + super.write(data); + inventory.writeToNBT(data, "Inventory"); + return data; + } + + @Override + public void read(BlockState blockState, CompoundNBT data) { + super.read(blockState, data); + inventory.readFromNBT(data, "Inventory"); + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java b/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java new file mode 100644 index 000000000..fbb87203e --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java @@ -0,0 +1,102 @@ +package com.glodblock.github.common.tile; + +import appeng.fluids.util.AEFluidInventory; +import appeng.fluids.util.IAEFluidInventory; +import appeng.fluids.util.IAEFluidTank; +import appeng.tile.AEBaseInvTileEntity; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.inv.InvOperation; +import com.glodblock.github.util.FCUtil; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import java.io.IOException; + +public abstract class TileSimpleBuffer extends AEBaseInvTileEntity implements IAEFluidInventory { + + private final AppEngInternalInventory invItems = createItemBuffer(); + private final AEFluidInventory invFluids = createFluidBuffer(); + + public TileSimpleBuffer(TileEntityType type) { + super(type); + } + + abstract protected AppEngInternalInventory createItemBuffer(); + + abstract protected AEFluidInventory createFluidBuffer(); + + @Nonnull + @Override + public IItemHandler getInternalInventory() { + return invItems; + } + + public IAEFluidTank getFluidInventory() { + return invFluids; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Nonnull + @SuppressWarnings("unchecked") + @Override + public LazyOptional getCapability(@Nonnull Capability capability, Direction facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return LazyOptional.of(() -> (T)invItems); + } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return LazyOptional.of(() -> (T)invFluids); + } + return super.getCapability(capability, facing); + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removed, ItemStack added) { + markForUpdate(); + } + + @Override + public void onFluidInventoryChanged(IAEFluidTank inv, int slot) { + saveChanges(); + markForUpdate(); + } + + @Override + protected void writeToStream(PacketBuffer data) throws IOException { + super.writeToStream(data); + FCUtil.writeFluidInventoryToBuffer(invFluids, data); + } + + @Override + protected boolean readFromStream(PacketBuffer data) throws IOException { + boolean changed = super.readFromStream(data); + changed |= FCUtil.readFluidInventoryToBuffer(invFluids, data); + return changed; + } + + @Override + public void read(BlockState blockState, CompoundNBT data) { + super.read(blockState, data); + invFluids.readFromNBT(data, "FluidInv"); + } + + @Override + public CompoundNBT write(CompoundNBT data) { + super.write(data); + invFluids.writeToNBT(data, "FluidInv"); + return data; + } + +} diff --git a/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java b/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java new file mode 100644 index 000000000..a13ed3612 --- /dev/null +++ b/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java @@ -0,0 +1,29 @@ +package com.glodblock.github.coreutil; + +public interface ExtendedInterface { + + default boolean getFluidPacketMode() { + return false; + } + + default void setFluidPacketMode(boolean value) { + + } + + default boolean getSplittingMode() { + return false; + } + + default void setSplittingMode(boolean value) { + + } + + default int getExtendedBlockMode() { + return 0; + } + + default void setExtendedBlockMode(int value) { + + } + +} diff --git a/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java new file mode 100644 index 000000000..8ba35a870 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java @@ -0,0 +1,49 @@ +package com.glodblock.github.handler; + +import com.glodblock.github.interfaces.TankDumpable; +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketDumpTank; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +public class ButtonMouseHandler implements MouseRegionManager.Handler { + + @Nullable + private final String tooltipKey; + private final Runnable callback; + + public ButtonMouseHandler(@Nullable String tooltipKey, Runnable callback) { + this.tooltipKey = tooltipKey; + this.callback = callback; + } + + @Nullable + @Override + public List getTooltip() { + return tooltipKey != null ? Collections.singletonList(new TranslationTextComponent(tooltipKey)) : null; + } + + @Override + public boolean onClick(int button) { + if (button == 0) { + callback.run(); + return true; + } + return false; + } + + public static ButtonMouseHandler dumpTank(TankDumpable host, int index) { + return new ButtonMouseHandler(NameConst.TT_DUMP_TANK, () -> { + if (host.canDumpTank(index)) { + NetworkManager.netHandler.sendToServer(new CPacketDumpTank(index)); + } + }); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java new file mode 100644 index 000000000..67e9188d9 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java @@ -0,0 +1,66 @@ +package com.glodblock.github.handler; + +import appeng.core.Api; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.*; +import com.glodblock.github.client.container.*; +import com.glodblock.github.client.model.FluidEncodedPatternModel; +import com.glodblock.github.client.model.FluidPacketModel; +import com.glodblock.github.common.part.PartDualInterface; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.interfaces.HasCustomModel; +import com.glodblock.github.loader.FCBlocks; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.RenderTypeLookup; +import net.minecraft.client.renderer.model.ModelResourceLocation; +import net.minecraft.item.Item; +import net.minecraftforge.client.event.ModelRegistryEvent; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.apache.commons.lang3.tuple.Pair; + +public class ClientRegistryHandler extends RegistryHandler { + + public static final ClientRegistryHandler INSTANCE = new ClientRegistryHandler(); + + @Override + public void onInit() { + RenderTypeLookup.setRenderLayer(FCBlocks.INGREDIENT_BUFFER, RenderType.getCutout()); + RenderTypeLookup.setRenderLayer(FCBlocks.LARGE_INGREDIENT_BUFFER, RenderType.getCutout()); + Ae2ReflectClient.registerAEGui(ContainerIngredientBuffer.TYPE, GuiIngredientBuffer::new, "/screens/ingredient_buffer.json"); + Ae2ReflectClient.registerAEGui(ContainerLargeIngredientBuffer.TYPE, GuiLargeIngredientBuffer::new, "/screens/large_ingredient_buffer.json"); + Ae2ReflectClient.registerAEGui(ContainerItemDualInterface.TYPE, GuiItemDualInterface::new, "/screens/dual_item_interface.json"); + Ae2ReflectClient.registerAEGui(ContainerFluidDualInterface.TYPE, GuiFluidDualInterface::new, "/screens/dual_fluid_interface.json"); + Ae2ReflectClient.registerAEGui(ContainerFluidPatternTerminal.TYPE, GuiFluidPatternTerminal::new, "/screens/fluid_pattern_terminal.json"); + Ae2ReflectClient.registerAEGui(ContainerFluidPacketDecoder.TYPE, GuiFluidPacketDecoder::new, "/screens/fluid_packet_decoder.json"); + Ae2ReflectClient.registerAEGui(ContainerFCPriority.TYPE, GuiFCPriority::new, "/screens/fc_priority.json"); + } + + @SubscribeEvent + public void onRegisterModels(ModelRegistryEvent event) { + ModelLoaderRegistry.registerLoader(FluidCraft.resource("fluid_encoded_pattern"), new FluidEncodedPatternModel.Loader()); + //ModelLoaderRegistry.registerLoader(new DenseCraftEncodedPatternModel.Loader()); + ModelLoaderRegistry.registerLoader(FluidCraft.resource("fluid_packet"), new FluidPacketModel.Loader()); + for (Pair entry : blocks) { + registerModel(entry.getLeft(), entry.getRight().asItem()); + } + for (Pair entry : items) { + registerModel(entry.getLeft(), entry.getRight()); + } + + Api.instance().registries().partModels().registerModels(PartDualInterface.MODELS); + Api.instance().registries().partModels().registerModels(PartFluidPatternTerminal.MODELS); + //AEApi.instance().registries().partModels().registerModels(PartExtendedFluidPatternTerminal.MODELS); + } + + private static void registerModel(String key, Item item) { + ModelLoader.addSpecialModel(new ModelResourceLocation( + item instanceof HasCustomModel ? + ((HasCustomModel)item).getCustomModelPath() : FluidCraft.resource(key), + "inventory")); + } + +} diff --git a/src/main/java/com/glodblock/github/handler/RegistryHandler.java b/src/main/java/com/glodblock/github/handler/RegistryHandler.java new file mode 100644 index 000000000..18a49c4e1 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/RegistryHandler.java @@ -0,0 +1,106 @@ +package com.glodblock.github.handler; + +import appeng.block.AEBaseBlockItem; +import appeng.block.AEBaseTileBlock; +import appeng.core.features.ActivityState; +import appeng.core.features.BlockStackSrc; +import appeng.tile.AEBaseTileEntity; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.*; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.FCUtil; +import net.minecraft.block.Block; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.registries.ForgeRegistries; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; + +public class RegistryHandler { + + public static final RegistryHandler INSTANCE = new RegistryHandler(); + protected final List> blocks = new ArrayList<>(); + protected final List> items = new ArrayList<>(); + protected final List>> tiles = new ArrayList<>(); + + public void block(String name, Block block) { + blocks.add(Pair.of(name, block)); + if (block instanceof AEBaseTileBlock) { + AEBaseTileBlock tileBlock = (AEBaseTileBlock) block; + tile(name, tileBlock.getTileEntityClass(), block); + } + } + + public void item(String name, Item item) { + items.add(Pair.of(name, item)); + } + + public void tile(String name, Class clazz, Block block) { + tiles.add(Pair.of(name, FCUtil.getTileType(clazz, block))); + } + + @SubscribeEvent + public void onRegisterBlocks(RegistryEvent.Register event) { + for (Pair entry : blocks) { + String key = entry.getLeft(); + Block block = entry.getRight(); + block.setRegistryName(key); + event.getRegistry().register(block); + } + } + + @SubscribeEvent + public void onRegisterItems(RegistryEvent.Register event) { + for (Pair entry : blocks) { + event.getRegistry().register(initItem(entry.getLeft(), new AEBaseBlockItem(entry.getRight(), new Item.Properties().group(FCItems.TAB_AE2FC)))); + } + for (Pair entry : items) { + event.getRegistry().register(initItem(entry.getLeft(), entry.getRight())); + } + } + + @SubscribeEvent + public void onRegisterTileEntities(RegistryEvent.Register> event) { + for (Pair> entry : tiles) { + String key = entry.getLeft(); + TileEntityType tile = entry.getRight(); + tile.setRegistryName(key); + event.getRegistry().register(tile); + } + } + + @SubscribeEvent + public void onRegisterContainerTypes(RegistryEvent.Register> event) { + event.getRegistry().register(ContainerIngredientBuffer.TYPE); + event.getRegistry().register(ContainerLargeIngredientBuffer.TYPE); + event.getRegistry().register(ContainerItemDualInterface.TYPE); + event.getRegistry().register(ContainerFluidPatternTerminal.TYPE); + event.getRegistry().register(ContainerFluidDualInterface.TYPE); + event.getRegistry().register(ContainerFluidPacketDecoder.TYPE); + event.getRegistry().register(ContainerFCPriority.TYPE); + } + + private static Item initItem(String key, Item item) { + item.setRegistryName(key); + return item; + } + + public void onInit() { + for (Pair entry : blocks) { + Block block = ForgeRegistries.BLOCKS.getValue(FluidCraft.resource(entry.getKey())); + if (block instanceof AEBaseTileBlock) { + AEBaseTileEntity.registerTileItem( + ((AEBaseTileBlock) block).getTileEntityClass(), + new BlockStackSrc(block, ActivityState.Enabled) + ); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/handler/TankMouseHandler.java b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java new file mode 100644 index 000000000..42c322398 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java @@ -0,0 +1,39 @@ +package com.glodblock.github.handler; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +public class TankMouseHandler implements MouseRegionManager.Handler { + + private final IAEFluidTank tank; + private final int index; + + public TankMouseHandler(IAEFluidTank tank, int index) { + this.tank = tank; + this.index = index; + } + + @Nullable + @Override + public List getTooltip() { + IAEFluidStack fluid = tank.getFluidInSlot(index); + return Arrays.asList( + new TranslationTextComponent(fluid != null ? fluid.getFluidStack().getTranslationKey() : NameConst.TT_EMPTY), + new StringTextComponent( + TextFormatting.GRAY + String.format("%,d / %,d mB", + fluid != null ? fluid.getStackSize() : 0L, tank.getTankCapacity(index))) + ); + } + +} diff --git a/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java b/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java new file mode 100644 index 000000000..ae55ed36b --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java @@ -0,0 +1,138 @@ +package com.glodblock.github.integration.builder; + +import com.glodblock.github.common.item.ItemFluidPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.gui.ingredient.IGuiIngredient; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class RecipeTransferBuilder { + + private static final int MAX_ITEMS = 16; + + private final Int2ObjectArrayMap in; + private final int bound; + private final ItemStack[] out; + private final IRecipeLayout recipe; + private List itemsIn; + private List fluidIn; + private List itemOut; + private List fluidOut; + private boolean noNull = true; + private boolean fluidFirst = false; + + public RecipeTransferBuilder(int maxInput, int maxOutput, IRecipeLayout recipe) { + this.in = new Int2ObjectArrayMap<>(); + this.bound = maxInput; + this.out = new ItemStack[maxOutput]; + this.recipe = recipe; + this.itemsIn = new ArrayList<>(); + this.itemOut = new ArrayList<>(); + this.fluidIn = new ArrayList<>(); + this.fluidOut = new ArrayList<>(); + this.split(); + } + + private void split() { + for (int index = 0; index < this.recipe.getItemStacks().getGuiIngredients().size(); index ++) { + IGuiIngredient ing = this.recipe.getItemStacks().getGuiIngredients().get(index); + if (ing.isInput()) { + List holder; + if (ing.getAllIngredients().size() < MAX_ITEMS - 1) { + holder = ing.getAllIngredients(); + } else { + holder = ing.getAllIngredients().subList(0, MAX_ITEMS - 1); + } + // Put displayed item at first check + if (ing.getDisplayedIngredient() != null) { + holder.add(0, ing.getDisplayedIngredient()); + } + this.itemsIn.add(holder.toArray(new ItemStack[0])); + } else { + this.itemOut.add(ing.getDisplayedIngredient()); + } + } + for (int index = 0; index < this.recipe.getFluidStacks().getGuiIngredients().size(); index ++) { + IGuiIngredient ing = this.recipe.getFluidStacks().getGuiIngredients().get(index); + if (ing.isInput()) { + this.fluidIn.add(ing.getDisplayedIngredient()); + } else { + this.fluidOut.add(ing.getDisplayedIngredient()); + } + } + } + + private void setItemIn(int offset) { + int bound = Math.min(this.bound, this.itemsIn.size() + offset); + for (int index = offset; index < bound; index ++) { + int i = index - offset; + if (this.itemsIn.get(i) != null && this.itemsIn.get(i).length > 0) { + this.in.put(index, this.itemsIn.get(i)); + } + } + } + + private void setFluidIn(int offset) { + int bound = Math.min(this.bound, this.fluidIn.size() + offset); + for (int index = offset; index < bound; index ++) { + int i = index - offset; + if (this.fluidIn.get(i) != null) { + this.in.put(index, new ItemStack[] {ItemFluidPacket.newStack(this.fluidIn.get(i))}); + } + } + } + + private void setOutputs() { + for (int index = 0; index < this.out.length; index ++) { + if (index < this.itemOut.size()) { + this.out[index] = this.itemOut.get(index); + } else if (index - this.itemOut.size() < this.fluidOut.size()) { + this.out[index] = ItemFluidPacket.newStack(this.fluidOut.get(index - this.itemOut.size())); + } + } + } + + public RecipeTransferBuilder clearEmptySlot(boolean val) { + this.noNull = val; + return this; + } + + public RecipeTransferBuilder putFluidFirst(boolean val) { + this.fluidFirst = val; + return this; + } + + public RecipeTransferBuilder build() { + if (this.noNull) { + this.itemsIn = this.itemsIn.stream().filter(o -> o != null && o.length > 0).collect(Collectors.toList()); + this.itemOut = this.itemOut.stream().filter(Objects::nonNull).collect(Collectors.toList()); + this.fluidIn = this.fluidIn.stream().filter(Objects::nonNull).collect(Collectors.toList()); + this.fluidOut = this.fluidOut.stream().filter(Objects::nonNull).collect(Collectors.toList()); + } + if (this.fluidFirst) { + this.setFluidIn(0); + this.setItemIn(this.fluidIn.size()); + } else { + this.setItemIn(0); + this.setFluidIn(this.itemsIn.size()); + } + this.setOutputs(); + return this; + } + + public ItemStack[] getOutput() { + return this.out; + } + + public Int2ObjectMap getInput() { + return this.in; + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java new file mode 100644 index 000000000..12bd09271 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java @@ -0,0 +1,26 @@ +package com.glodblock.github.integration.jei; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.integration.jei.handlers.FluidPatternTerminalRecipeTransferHandler; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.registration.IRecipeTransferRegistration; +import net.minecraft.util.ResourceLocation; + +import javax.annotation.Nonnull; + +@JeiPlugin +public class FCJeiPlugin implements IModPlugin { + + @Nonnull + @Override + public ResourceLocation getPluginUid() { + return FluidCraft.resource("jei"); + } + + @Override + public void registerRecipeTransferHandlers(@Nonnull IRecipeTransferRegistration registration) { + registration.addUniversalRecipeTransferHandler(new FluidPatternTerminalRecipeTransferHandler()); + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java new file mode 100644 index 000000000..2d8da22fc --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java @@ -0,0 +1,50 @@ +package com.glodblock.github.integration.jei.handlers; + +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.integration.builder.RecipeTransferBuilder; +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketFluidCraftBtns; +import com.glodblock.github.network.packets.CPacketLoadPattern; +import mezz.jei.api.constants.VanillaRecipeCategoryUid; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import net.minecraft.entity.player.PlayerEntity; + +import javax.annotation.Nonnull; + +public class FluidPatternTerminalRecipeTransferHandler implements IRecipeTransferHandler { + + @Nonnull + @Override + public Class getContainerClass() { + return ContainerFluidPatternTerminal.class; + } + + @Override + public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternTerminal container, @Nonnull Object recipe, @Nonnull IRecipeLayout recipeLayout, @Nonnull PlayerEntity player, boolean maxTransfer, boolean doTransfer) { + if (doTransfer) { + boolean craftMode = container.craftingMode; + if (container.isCraftingMode() && !recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", false)); + craftMode = false; + } + else if (!container.isCraftingMode() && recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", true)); + craftMode = true; + } + PartFluidPatternTerminal tile = container.getPart(); + RecipeTransferBuilder transfer = new RecipeTransferBuilder( + tile.getInventoryByName("crafting").getSlots(), + tile.getInventoryByName("output").getSlots(), + recipeLayout) + .clearEmptySlot(!craftMode) + .putFluidFirst(container.fluidFirst) + .build(); + NetworkManager.netHandler.sendToServer(new CPacketLoadPattern(transfer.getInput(), transfer.getOutput(), container.combine)); + } + return null; + } + +} diff --git a/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java b/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java new file mode 100644 index 000000000..bc8b01af8 --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java @@ -0,0 +1,19 @@ +package com.glodblock.github.interfaces; + +import appeng.api.storage.data.IAEStack; + +import javax.annotation.Nullable; +import java.util.stream.Stream; + +public interface AeStackInventory > extends Iterable { + + int getSlotCount(); + + @Nullable + T getStack(int slot); + + void setStack(int slot, @Nullable T stack); + + Stream stream(); + +} diff --git a/src/main/java/com/glodblock/github/interfaces/ConfigData.java b/src/main/java/com/glodblock/github/interfaces/ConfigData.java new file mode 100644 index 000000000..5dda1ddd4 --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/ConfigData.java @@ -0,0 +1,9 @@ +package com.glodblock.github.interfaces; + +public interface ConfigData { + + void set(String id, Object value); + + Object get(String id); + +} diff --git a/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java b/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java new file mode 100644 index 000000000..6fab7cb3c --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java @@ -0,0 +1,9 @@ +package com.glodblock.github.interfaces; + +import net.minecraft.util.ResourceLocation; + +public interface HasCustomModel { + + ResourceLocation getCustomModelPath(); + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java new file mode 100644 index 000000000..89fce380a --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java @@ -0,0 +1,10 @@ +package com.glodblock.github.interfaces; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.item.ItemStack; + +public interface PatternConsumer { + + void acceptPattern(Int2ObjectMap inputs, ItemStack[] outputs, boolean compress); + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/interfaces/SlotFluid.java b/src/main/java/com/glodblock/github/interfaces/SlotFluid.java new file mode 100644 index 000000000..506ad2b24 --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/SlotFluid.java @@ -0,0 +1,14 @@ +package com.glodblock.github.interfaces; + +import appeng.api.storage.data.IAEItemStack; + +import javax.annotation.Nullable; + +public interface SlotFluid { + + @Nullable + IAEItemStack getAeStack(); + + void setAeStack(@Nullable IAEItemStack stack, boolean sync); + +} diff --git a/src/main/java/com/glodblock/github/interfaces/TankDumpable.java b/src/main/java/com/glodblock/github/interfaces/TankDumpable.java new file mode 100644 index 000000000..97361d8d0 --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/TankDumpable.java @@ -0,0 +1,9 @@ +package com.glodblock.github.interfaces; + +public interface TankDumpable { + + boolean canDumpTank(int index); + + void dumpTank(int index); + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java new file mode 100644 index 000000000..5878fc1ec --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java @@ -0,0 +1,369 @@ +package com.glodblock.github.inventory; + +import appeng.api.config.FuzzyMode; +import appeng.api.parts.IPart; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.me.GridAccessException; +import appeng.me.helpers.AENetworkProxy; +import appeng.tile.misc.InterfaceTileEntity; +import appeng.tile.networking.CableBusTileEntity; +import appeng.util.InventoryAdaptor; +import appeng.util.inv.AdaptorItemHandler; +import appeng.util.inv.IInventoryDestination; +import appeng.util.inv.ItemSlot; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.tile.TileDualInterface; +import com.glodblock.github.coreutil.ExtendedInterface; +import com.glodblock.github.util.Ae2Reflect; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +public class FluidConvertingInventoryAdaptor extends InventoryAdaptor { + + public static InventoryAdaptor wrap(ICapabilityProvider capProvider, Direction face) { + TileEntity cap = (TileEntity) capProvider; + TileEntity inter = Objects.requireNonNull(cap.getWorld()).getTileEntity(cap.getPos().add(face.getDirectionVec())); + DualityInterface dualInterface = getInterfaceTE(inter, face) == null ? + null : Objects.requireNonNull(getInterfaceTE(inter, face)).getInterfaceDuality(); + boolean onmi = false; + if (inter instanceof InterfaceTileEntity) { + onmi = ((InterfaceTileEntity) inter).getTargets().size() > 1; + } else if (inter instanceof TileDualInterface) { + onmi = ((TileDualInterface) inter).getTargets().size() > 1; + } + + if (dualInterface == null || !((ExtendedInterface) dualInterface).getFluidPacketMode()) { + return new FluidConvertingInventoryAdaptor( + capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face).resolve().orElse(null), + capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face).resolve().orElse(null), + inter, + onmi, + dualInterface); + } + return InventoryAdaptor.getAdaptor(cap, face); + } + + @Nullable + private final InventoryAdaptor invItems; + @Nullable + private final IFluidHandler invFluids; + private final boolean onmi; + @Nullable + private final TileEntity posInterface; + @Nullable + private final DualityInterface self; + + public FluidConvertingInventoryAdaptor(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids, + @Nullable TileEntity pos, boolean isOnmi, @Nullable DualityInterface interSelf) { + this.invItems = invItems != null ? new AdaptorItemHandler(invItems) : null; + this.invFluids = invFluids; + this.posInterface = pos; + this.onmi = isOnmi; + this.self = interSelf; + } + + @Override + public ItemStack addItems(@Nonnull ItemStack toBeAdded) { + if (toBeAdded.getItem() instanceof ItemFluidPacket || toBeAdded.getItem() instanceof ItemFluidDrop) { + if (onmi) { + FluidStack fluid; + if (toBeAdded.getItem() instanceof ItemFluidPacket) { + fluid = ItemFluidPacket.getFluidStack(toBeAdded); + } else { + fluid = ItemFluidDrop.getFluidStack(toBeAdded); + } + + // First try to output to the same side + if (invFluids != null) { + if (!fluid.isEmpty()) { + int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.EXECUTE); + if (filled > 0) { + fluid.shrink(filled); + return ItemFluidPacket.newStack(fluid); + } + } + } + + if (!fluid.isEmpty() && posInterface != null && posInterface.getWorld() != null + && self != null && ((ExtendedInterface) self).getSplittingMode()) { + for (Direction dir : Direction.values()) { + TileEntity te = posInterface.getWorld().getTileEntity(posInterface.getPos().add(dir.getDirectionVec())); + if (te != null) { + IInterfaceHost interTE = getInterfaceTE(te, dir); + if (interTE != null && isSameGrid(interTE)) { + continue; + } + IFluidInterfaceHost interFTE = getFluidInterfaceTE(te, dir); + if (interFTE != null && isSameGrid(interFTE)) { + continue; + } + IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite()) + .resolve().orElse(null); + if (fh != null) { + int filled = fh.fill(fluid, IFluidHandler.FluidAction.SIMULATE); + if (filled == fluid.getAmount()) { + fh.fill(fluid, IFluidHandler.FluidAction.EXECUTE); + return ItemStack.EMPTY; + } + } + } + } + } + return ItemFluidPacket.newStack(fluid); + } + if (invFluids != null) { + FluidStack fluid; + if(toBeAdded.getItem() instanceof ItemFluidPacket) + fluid = ItemFluidPacket.getFluidStack(toBeAdded); + else + fluid = ItemFluidDrop.getFluidStack(toBeAdded); + if (!fluid.isEmpty()) { + int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.EXECUTE); + if (filled > 0) { + fluid.shrink(filled); + return ItemFluidPacket.newStack(fluid); + } + } + } + return toBeAdded; + } + return invItems != null ? invItems.addItems(toBeAdded) : toBeAdded; + } + + @Override + public ItemStack simulateAdd(ItemStack toBeSimulated) { + if (toBeSimulated.getItem() instanceof ItemFluidPacket || toBeSimulated.getItem() instanceof ItemFluidDrop) { + if (onmi) { + boolean sus = false; + FluidStack fluid; + if (toBeSimulated.getItem() instanceof ItemFluidPacket) { + fluid = ItemFluidPacket.getFluidStack(toBeSimulated); + } else { + fluid = ItemFluidDrop.getFluidStack(toBeSimulated); + } + + // First try to output to the same side + if (invFluids != null) { + if (!fluid.isEmpty()) { + int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.SIMULATE); + if (filled > 0) { + fluid.shrink(filled); + return ItemFluidPacket.newStack(fluid); + } + } + } + + if (!fluid.isEmpty() && posInterface != null && posInterface.getWorld() != null && self != null) { + if (((ExtendedInterface) self).getSplittingMode()) { + for (Direction dir : Direction.values()) { + TileEntity te = posInterface.getWorld().getTileEntity(posInterface.getPos().add(dir.getDirectionVec())); + if (te != null) { + IInterfaceHost interTE = getInterfaceTE(te, dir); + if (interTE != null && isSameGrid(interTE)) { + continue; + } + IFluidInterfaceHost interFTE = getFluidInterfaceTE(te, dir); + if (interFTE != null && isSameGrid(interFTE)) { + continue; + } + IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite()) + .resolve().orElse(null); + if (fh != null) { + int filled = fh.fill(fluid, IFluidHandler.FluidAction.SIMULATE); + if (filled == fluid.getAmount()) { + sus = true; + break; + } + } + } + } + } + } else { + sus = true; + } + return sus ? ItemStack.EMPTY : toBeSimulated; + } + if (invFluids != null) { + FluidStack fluid; + if(toBeSimulated.getItem() instanceof ItemFluidPacket) + fluid = ItemFluidPacket.getFluidStack(toBeSimulated); + else + fluid = ItemFluidDrop.getFluidStack(toBeSimulated); + if (!fluid.isEmpty()) { + int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.SIMULATE); + if (filled > 0) { + fluid.shrink(filled); + return ItemFluidPacket.newStack(fluid); + } + } + } + return toBeSimulated; + } + return invItems != null ? invItems.simulateAdd(toBeSimulated) : toBeSimulated; + } + + @Override + public ItemStack removeItems(int amount, ItemStack filter, IInventoryDestination destination) { + return invItems != null ? invItems.removeItems(amount, filter, destination) : ItemStack.EMPTY; + } + + @Override + public ItemStack simulateRemove(int amount, ItemStack filter, IInventoryDestination destination) { + return invItems != null ? invItems.simulateRemove(amount, filter, destination) : ItemStack.EMPTY; + } + + @Override + public ItemStack removeSimilarItems(int amount, ItemStack filter, FuzzyMode fuzzyMode, IInventoryDestination destination) { + return invItems != null ? invItems.removeSimilarItems(amount, filter, fuzzyMode, destination) : ItemStack.EMPTY; + } + + @Override + public ItemStack simulateSimilarRemove(int amount, ItemStack filter, FuzzyMode fuzzyMode, IInventoryDestination destination) { + return invItems != null ? invItems.simulateSimilarRemove(amount, filter, fuzzyMode, destination) : ItemStack.EMPTY; + } + + @Override + public boolean containsItems() { + int blockMode = 0; + if (this.self != null) { + blockMode = ((ExtendedInterface) this.self).getExtendedBlockMode(); + } + boolean checkFluid = blockMode != 1; + boolean checkItem = blockMode != 2; + if (invFluids != null && checkFluid) { + for (int i = 0; i < invFluids.getTanks(); i ++) { + FluidStack fluid = invFluids.getFluidInTank(i); + if (!fluid.isEmpty()) { + return true; + } + } + } + if (invItems != null && checkItem) { + return invItems.containsItems(); + } + return false; + } + + @Override + public boolean hasSlots() { + return (invFluids != null && invFluids.getTanks() > 0) + || (invItems != null && invItems.hasSlots()); + } + + @Nullable + protected static IInterfaceHost getInterfaceTE(TileEntity te, Direction face) { + if (te instanceof IInterfaceHost) { + return (IInterfaceHost) te; + } else if (te instanceof CableBusTileEntity) { + IPart part = ((CableBusTileEntity) te).getPart(face.getOpposite()); + if (part instanceof IInterfaceHost) { + return (IInterfaceHost) part; + } + } + return null; + } + + @Nullable + protected static IFluidInterfaceHost getFluidInterfaceTE(TileEntity te, Direction face) { + if (te instanceof IFluidInterfaceHost) { + return (IFluidInterfaceHost) te; + } else if (te instanceof CableBusTileEntity) { + IPart part = ((CableBusTileEntity) te).getPart(face.getOpposite()); + if (part instanceof IFluidInterfaceHost) { + return (IFluidInterfaceHost) part; + } + } + return null; + } + + private boolean isSameGrid(IInterfaceHost target) { + if (this.self != null && target != null) { + DualityInterface other = target.getInterfaceDuality(); + try { + AENetworkProxy proxy1 = Ae2Reflect.getInterfaceProxy(other); + AENetworkProxy proxy2 = Ae2Reflect.getInterfaceProxy(this.self); + if (proxy1.getGrid() == proxy2.getGrid()) { + return true; + } + } catch (GridAccessException e) { + return false; + } + } + return false; + } + + private boolean isSameGrid(IFluidInterfaceHost target) { + if (this.self != null && target != null) { + DualityFluidInterface other = target.getDualityFluidInterface(); + try { + AENetworkProxy proxy1 = Ae2Reflect.getInterfaceProxy(other); + AENetworkProxy proxy2 = Ae2Reflect.getInterfaceProxy(this.self); + if (proxy1.getGrid() == proxy2.getGrid()) { + return true; + } + } catch (GridAccessException e) { + return false; + } + } + return false; + } + + @Override + @Nonnull + public Iterator iterator() { + return new SlotIterator( + invFluids, invItems != null ? invItems.iterator() : Collections.emptyIterator()); + } + + private static class SlotIterator implements Iterator { + + private final IFluidHandler tanks; + private final Iterator itemSlots; + private int nextSlotIndex = 0; + + SlotIterator(IFluidHandler tanks, Iterator itemSlots) { + this.tanks = tanks; + this.itemSlots = itemSlots; + } + + @Override + public boolean hasNext() { + return nextSlotIndex < tanks.getTanks() || itemSlots.hasNext(); + } + + @Override + public ItemSlot next() { + if (nextSlotIndex < tanks.getTanks()) { + FluidStack fluid = tanks.getFluidInTank(nextSlotIndex); + ItemSlot slot = new ItemSlot(); + slot.setSlot(nextSlotIndex++); + slot.setItemStack(!fluid.isEmpty() ? ItemFluidPacket.newStack(fluid) : ItemStack.EMPTY); + Ae2Reflect.setItemSlotExtractable(slot, false); + return slot; + } else { + ItemSlot slot = itemSlots.next(); + slot.setSlot(nextSlotIndex++); + return slot; + } + } + + } + +} diff --git a/src/main/java/com/glodblock/github/loader/ChannelLoader.java b/src/main/java/com/glodblock/github/loader/ChannelLoader.java new file mode 100644 index 000000000..3a3978ed5 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/ChannelLoader.java @@ -0,0 +1,44 @@ +package com.glodblock.github.loader; + +import com.glodblock.github.network.NetworkManager; +import com.glodblock.github.network.packets.CPacketDumpTank; +import com.glodblock.github.network.packets.CPacketFluidCraftBtns; +import com.glodblock.github.network.packets.CPacketLoadPattern; +import com.glodblock.github.network.packets.IMessage; +import com.glodblock.github.util.ModAndClassUtil; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class ChannelLoader { + + static int id = 0; + + public static void load() { + registerPacket(new CPacketDumpTank()); + registerPacket(new CPacketFluidCraftBtns()); + if (ModAndClassUtil.JEI || ModAndClassUtil.REI) { + registerPacket(new CPacketLoadPattern()); + } + } + + private static > void registerPacket(MSG msg) { + NetworkManager.netHandler.registerMessage( + id ++, + msg.getPacketClass(), + ChannelLoader::encoder, + msg::fromBytes, + ChannelLoader::handle + ); + } + + public static > void encoder(MSG msg, PacketBuffer p) { + msg.toBytes(p); + } + + public static > void handle(MSG msg, Supplier ctx) { + msg.onMessage(ctx); + } + +} diff --git a/src/main/java/com/glodblock/github/loader/FCBlocks.java b/src/main/java/com/glodblock/github/loader/FCBlocks.java new file mode 100644 index 000000000..aca4f28c6 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/FCBlocks.java @@ -0,0 +1,37 @@ +package com.glodblock.github.loader; + +import com.glodblock.github.common.block.*; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.util.NameConst; + +public class FCBlocks { + + public static BlockFluidDiscretizer FLUID_DISCRETIZER; + //public static BlockFluidPatternEncoder FLUID_PATTERN_ENCODER; + public static BlockFluidPacketDecoder FLUID_PACKET_DECODER; + public static BlockIngredientBuffer INGREDIENT_BUFFER; + public static BlockLargeIngredientBuffer LARGE_INGREDIENT_BUFFER; + //public static BlockBurette BURETTE; + public static BlockDualInterface DUAL_INTERFACE; + //public static BlockFluidAssembler FLUID_ASSEMBLER; + + public static void init(RegistryHandler regHandler) { + FLUID_DISCRETIZER = new BlockFluidDiscretizer(); + //FLUID_PATTERN_ENCODER = new BlockFluidPatternEncoder(); + FLUID_PACKET_DECODER = new BlockFluidPacketDecoder(); + INGREDIENT_BUFFER = new BlockIngredientBuffer(); + LARGE_INGREDIENT_BUFFER = new BlockLargeIngredientBuffer(); + //BURETTE = new BlockBurette(); + DUAL_INTERFACE = new BlockDualInterface(); + //FLUID_ASSEMBLER = new BlockFluidAssembler(); + regHandler.block(NameConst.BLOCK_FLUID_DISCRETIZER, FLUID_DISCRETIZER); + //regHandler.block(NameConst.BLOCK_FLUID_PATTERN_ENCODER, FLUID_PATTERN_ENCODER); + regHandler.block(NameConst.BLOCK_FLUID_PACKET_DECODER, FLUID_PACKET_DECODER); + regHandler.block(NameConst.BLOCK_INGREDIENT_BUFFER, INGREDIENT_BUFFER); + regHandler.block(NameConst.BLOCK_LARGE_INGREDIENT_BUFFER, LARGE_INGREDIENT_BUFFER); + //regHandler.block(NameConst.BLOCK_BURETTE, BURETTE); + regHandler.block(NameConst.BLOCK_DUAL_INTERFACE, DUAL_INTERFACE); + //regHandler.block(NameConst.BLOCK_FLUID_ASSEMBLER, FLUID_ASSEMBLER); + } + +} diff --git a/src/main/java/com/glodblock/github/loader/FCItems.java b/src/main/java/com/glodblock/github/loader/FCItems.java new file mode 100644 index 000000000..30aecccd0 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/FCItems.java @@ -0,0 +1,49 @@ +package com.glodblock.github.loader; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.item.*; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.util.NameConst; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public class FCItems { + + public static final ItemGroup TAB_AE2FC = new ItemGroup(FluidCraft.MODID) { + @Nonnull + @Override + public ItemStack createIcon() { + return new ItemStack(FCBlocks.INGREDIENT_BUFFER); + } + }; + + public static ItemFluidDrop FLUID_DROP; + public static ItemFluidPacket FLUID_PACKET; + public static ItemFluidEncodedPattern DENSE_ENCODED_PATTERN; + //public static ItemFluidCraftEncodedPattern DENSE_CRAFT_ENCODED_PATTERN; + public static ItemPartDualInterface PART_DUAL_INTERFACE; + public static ItemPartFluidPatternTerminal PART_FLUID_PATTERN_TERMINAL; + + public static void init(RegistryHandler regHandler) { + FLUID_DROP = new ItemFluidDrop(); + FLUID_PACKET = new ItemFluidPacket(); + DENSE_ENCODED_PATTERN = new ItemFluidEncodedPattern(); + //DENSE_CRAFT_ENCODED_PATTERN = new ItemFluidCraftEncodedPattern(); + PART_DUAL_INTERFACE = new ItemPartDualInterface(); + PART_FLUID_PATTERN_TERMINAL = new ItemPartFluidPatternTerminal(); + regHandler.item(NameConst.ITEM_FLUID_DROP, FLUID_DROP); + regHandler.item(NameConst.ITEM_FLUID_PACKET, FLUID_PACKET); + regHandler.item(NameConst.ITEM_DENSE_ENCODED_PATTERN, DENSE_ENCODED_PATTERN); + //regHandler.item(NameConst.ITEM_DENSE_CRAFT_ENCODED_PATTERN, DENSE_CRAFT_ENCODED_PATTERN); + regHandler.item(NameConst.ITEM_PART_DUAL_INTERFACE, PART_DUAL_INTERFACE); + regHandler.item(NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL, PART_FLUID_PATTERN_TERMINAL); + } + + public static Item.Properties defaultProps() { + return new Item.Properties().group(TAB_AE2FC); + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java b/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java new file mode 100644 index 000000000..b12b24928 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java @@ -0,0 +1,40 @@ +package com.glodblock.github.mixins; + +import appeng.client.gui.implementations.AESubScreen; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.loader.FCItems; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(AESubScreen.class) +public abstract class AESubScreenMixin { + + @Final + @Mutable + @Shadow(remap = false) + private ContainerType previousContainerType; + @Final + @Mutable + @Shadow(remap = false) + private ItemStack previousContainerIcon; + + @Inject( + method = "", + at = @At("RETURN") + ) + private void addExtendedGUI(Object containerHost, CallbackInfo ci) { + if (containerHost instanceof PartFluidPatternTerminal) { + previousContainerIcon = new ItemStack(FCItems.PART_FLUID_PATTERN_TERMINAL); + previousContainerType = ContainerFluidPatternTerminal.TYPE; + } + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java b/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java new file mode 100644 index 000000000..2c80ee073 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java @@ -0,0 +1,33 @@ +package com.glodblock.github.mixins; + +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.core.api.ApiCrafting; +import com.glodblock.github.common.item.ItemFluidEncodedPattern; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ApiCrafting.class) +public abstract class ApiCraftingMixin { + + @Inject( + method = "decodePattern", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private void injectDecodePattern(ItemStack is, World world, boolean autoRecovery, CallbackInfoReturnable cir) { + if (is != null && is.getItem() instanceof ItemFluidEncodedPattern) { + ItemFluidEncodedPattern pattern = (ItemFluidEncodedPattern) is.getItem(); + if (pattern.isEncodedPattern(is)) { + ICraftingPatternDetails d = pattern.getDetails(is); + cir.setReturnValue(d); + } + cir.cancel(); + } + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java b/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java new file mode 100644 index 000000000..2a43627d7 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java @@ -0,0 +1,35 @@ +package com.glodblock.github.mixins; + +import appeng.api.networking.security.IActionHost; +import appeng.container.AEBaseContainer; +import appeng.container.me.crafting.CraftConfirmContainer; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(CraftConfirmContainer.class) +public abstract class CraftConfirmContainerMixin extends AEBaseContainer { + + public CraftConfirmContainerMixin(ContainerType containerType, int id, PlayerInventory playerInventory, Object host) { + super(containerType, id, playerInventory, host); + } + + @ModifyVariable( + method = "startJob", + at = @At(value = "STORE", ordinal = 0), + ordinal = 0, + remap = false + ) + private ContainerType addExtendedGUI(ContainerType ct) { + IActionHost ah = this.getActionHost(); + if (ah instanceof PartFluidPatternTerminal) { + return ContainerFluidPatternTerminal.TYPE; + } + return null; + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java b/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java new file mode 100644 index 000000000..b7a5ae92f --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java @@ -0,0 +1,43 @@ +package com.glodblock.github.mixins; + +import appeng.client.gui.me.crafting.CraftConfirmTableRenderer; +import appeng.container.me.crafting.CraftingPlanSummaryEntry; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CraftConfirmTableRenderer.class) +public abstract class CraftConfirmTableRendererMixin { + + /** + * @author GlodBlock + * @reason Render drop as fluid + */ + @Overwrite( + remap = false + ) + protected ItemStack getEntryItem(CraftingPlanSummaryEntry entry) { + if (entry.getItem() != null && entry.getItem().getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(entry.getItem()); + if (!fluid.isEmpty()) { + return ItemFluidPacket.newDisplayStack(fluid); + } + } + return entry.getItem(); + } + + @Redirect( + method = "getEntryTooltip*", + at = @At(value = "INVOKE", target = "Lappeng/container/me/crafting/CraftingPlanSummaryEntry;getItem()Lnet/minecraft/item/ItemStack;"), + remap = false + ) + private ItemStack changeItem(CraftingPlanSummaryEntry entry) { + return getEntryItem(entry); + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java new file mode 100644 index 000000000..37299a6d8 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java @@ -0,0 +1,136 @@ +package com.glodblock.github.mixins; + +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.networking.security.IActionSource; +import appeng.api.networking.storage.IStorageGrid; +import appeng.api.storage.IMEInventory; +import appeng.api.storage.channels.IFluidStorageChannel; +import appeng.api.storage.channels.IItemStorageChannel; +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; +import appeng.core.Api; +import appeng.crafting.MECraftingInventory; +import appeng.me.cluster.implementations.CraftingCPUCluster; +import appeng.me.helpers.MachineSource; +import appeng.util.item.AEItemStack; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.loader.FCItems; +import com.google.common.base.Preconditions; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CraftingCPUCluster.class) +public abstract class CraftingCpuMixin { + + @Shadow(remap = false) + private boolean isComplete; + @Shadow(remap = false) + private MECraftingInventory inventory; + @Shadow(remap = false) + private MachineSource machineSrc; + @Shadow(remap = false) + protected abstract IGrid getGrid(); + @Shadow(remap = false) + protected abstract void postChange(IAEItemStack diff, IActionSource src); + @Shadow(remap = false) + protected abstract void markDirty(); + + @Redirect( + method = "executeCrafting", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", ordinal = 0, remap = false), + remap = false + ) + private long getFluidStackSize(IAEItemStack packet) { + if (packet.getDefinition() != null && !packet.getDefinition().isEmpty() && packet.getDefinition().getItem() instanceof ItemFluidDrop) { + return (long) Math.max(packet.getStackSize() / 1000D, 1); + } else return packet.getStackSize(); + } + + @Redirect( + method = "executeCrafting", + at = @At(value = "INVOKE", target = "Lnet/minecraft/inventory/CraftingInventory;getStackInSlot(I)Lnet/minecraft/item/ItemStack;"), + remap = false + ) + private ItemStack removeFluidPackets(CraftingInventory inv, int index) { + ItemStack stack = inv.getStackInSlot(index); + if (stack != ItemStack.EMPTY && stack.getItem() instanceof ItemFluidPacket) { + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + return ItemFluidDrop.newStack(fluid); + } + else { + return stack; + } + } + + @Redirect( + method = "executeCrafting", + at = @At(value = "INVOKE", target = "Lappeng/util/item/AEItemStack;fromItemStack(Lnet/minecraft/item/ItemStack;)Lappeng/util/item/AEItemStack;", ordinal = 0, remap = false), + remap = false + ) + private AEItemStack wrapFluidPacketStack(ItemStack stack) { + if (stack.getItem() == FCItems.FLUID_PACKET) { + IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack)); + if (dropStack != null) { + return (AEItemStack) dropStack; + } + } + return AEItemStack.fromItemStack(stack); + } + + /** + * @author GlodBlock + * @reason Fix fluid storage + */ + @Overwrite( + remap = false + ) + private void storeItems() { + Preconditions.checkState(this.isComplete, "CPU should be complete to prevent re-insertion when dumping items"); + final IGrid g = this.getGrid(); + + if (g == null) { + return; + } + + final IStorageGrid sg = g.getCache( IStorageGrid.class ); + final IMEInventory ii = sg.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class)); + final IMEInventory jj = sg.getInventory(Api.instance().storage().getStorageChannel(IFluidStorageChannel.class)); + + for (IAEItemStack is : this.inventory.getItemList()) { + this.postChange(is, this.machineSrc); + + if (is.getItem() instanceof ItemFluidDrop ) { + IAEFluidStack drop = ItemFluidDrop.getAeFluidStack(is); + IAEFluidStack fluidRemainder = jj.injectItems(drop, Actionable.MODULATE, this.machineSrc); + if (fluidRemainder != null) { + is.setStackSize(fluidRemainder.getStackSize()); + } else { + is.reset(); + } + } else { + IAEItemStack remainder = ii.injectItems(is.copy(), Actionable.MODULATE, this.machineSrc); + if (remainder != null) { + is.setStackSize(remainder.getStackSize()); + } else { + is.reset(); + } + } + } + + if (this.inventory.getItemList().isEmpty()) { + this.inventory = new MECraftingInventory(); + } + + this.markDirty(); + } + + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java new file mode 100644 index 000000000..1f9d5a797 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java @@ -0,0 +1,20 @@ +package com.glodblock.github.mixins; + +import appeng.me.cache.CraftingGridCache; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CraftingGridCache.class) +public abstract class CraftingGridCacheMixin { + + @Redirect( + method = "addCraftingOption", + at = @At(value = "INVOKE", target = "Lcom/google/common/base/Preconditions;checkArgument(ZLjava/lang/Object;)V", remap = false), + remap = false + ) + private void removeDetailRestriction(boolean expression, Object errorMessage) { + // NO-OP + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java new file mode 100644 index 000000000..6df95e218 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java @@ -0,0 +1,44 @@ +package com.glodblock.github.mixins; + +import appeng.client.gui.me.crafting.CraftingStatusTableRenderer; +import appeng.container.me.crafting.CraftingPlanSummaryEntry; +import appeng.container.me.crafting.CraftingStatusEntry; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CraftingStatusTableRenderer.class) +public abstract class CraftingStatusTableRendererMixin { + + /** + * @author GlodBlock + * @reason Render drop as fluid + */ + @Overwrite( + remap = false + ) + protected ItemStack getEntryItem(CraftingStatusEntry entry) { + if (entry.getItem() != null && entry.getItem().getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(entry.getItem()); + if (!fluid.isEmpty()) { + return ItemFluidPacket.newDisplayStack(fluid); + } + } + return entry.getItem(); + } + + @Redirect( + method = "getEntryTooltip*", + at = @At(value = "INVOKE", target = "Lappeng/container/me/crafting/CraftingStatusEntry;getItem()Lnet/minecraft/item/ItemStack;", remap = false), + remap = false + ) + private ItemStack changeItem(CraftingStatusEntry entry) { + return getEntryItem(entry); + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java new file mode 100644 index 000000000..395b251d3 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java @@ -0,0 +1,87 @@ +package com.glodblock.github.mixins; + +import appeng.api.storage.data.IAEItemStack; +import appeng.crafting.CraftingTreeNode; +import com.glodblock.github.common.item.ItemFluidDrop; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; + +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.PUTFIELD; + +@Mixin(CraftingTreeNode.class) +public abstract class CraftingTreeNodeMixin { + + @Redirect( + method = "request", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false), + slice = @Slice( + from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD), + to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD) + ), + remap = false + ) + private long getCraftingByteCost(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + + @Redirect( + method = "request", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false), + slice = @Slice( + from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 1), + to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 1) + ), + remap = false + ) + private long getCraftingByteCost2(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + + @Redirect( + method = "request", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false), + slice = @Slice( + from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 2), + to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 2) + ), + remap = false + ) + private long getCraftingByteCost3(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + + @Redirect( + method = "request", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false), + slice = @Slice( + from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 3), + to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 3) + ), + remap = false + ) + private long getCraftingByteCost4(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + + @Redirect( + method = "request", + at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false), + slice = @Slice( + from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 4), + to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 4) + ), + remap = false + ) + private long getCraftingByteCost5(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java b/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java new file mode 100644 index 000000000..bbc35d5e3 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java @@ -0,0 +1,84 @@ +package com.glodblock.github.mixins; + +import appeng.helpers.DualityInterface; +import appeng.util.InventoryAdaptor; +import com.glodblock.github.coreutil.ExtendedInterface; +import com.glodblock.github.inventory.FluidConvertingInventoryAdaptor; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(DualityInterface.class) +public abstract class DualityInterfaceMixin implements ExtendedInterface { + + private boolean fluidPacket; + private boolean allowSplitting; + private int blockModeEx; + + @Inject( + method = "writeToNBT", + at = @At("HEAD"), + remap = false + ) + public void writeToNBT(CompoundNBT data, CallbackInfo ci) { + data.putBoolean("fluidPacket", fluidPacket); + data.putBoolean("allowSplitting", allowSplitting); + data.putInt("blockModeEx", blockModeEx); + } + + @Inject( + method = "readFromNBT", + at = @At("HEAD"), + remap = false + ) + public void readFromNBT(CompoundNBT data, CallbackInfo ci) { + fluidPacket = data.getBoolean("fluidPacket"); + allowSplitting = data.getBoolean("allowSplitting"); + blockModeEx = data.getInt("blockModeEx");; + } + + @Redirect( + method = {"pushItemsOut", "pushPattern", "isBusy"}, + at = @At(value = "INVOKE", target = "Lappeng/util/InventoryAdaptor;getAdaptor(Lnet/minecraft/tileentity/TileEntity;Lnet/minecraft/util/Direction;)Lappeng/util/InventoryAdaptor;", remap = false), + remap = false + ) + private InventoryAdaptor wrapInventoryAdaptor(TileEntity cap, Direction te) { + return cap != null ? FluidConvertingInventoryAdaptor.wrap(cap, te) : null; + } + + @Override + public boolean getFluidPacketMode() { + return fluidPacket; + } + + @Override + public void setFluidPacketMode(boolean value) { + this.fluidPacket = value; + } + + @Override + public boolean getSplittingMode() { + return allowSplitting; + } + + @Override + public void setSplittingMode(boolean value) { + this.allowSplitting = value; + } + + @Override + public int getExtendedBlockMode() { + return blockModeEx; + } + + @Override + public void setExtendedBlockMode(int value) { + this.blockModeEx = value; + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java b/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java new file mode 100644 index 000000000..7c518f5b8 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java @@ -0,0 +1,31 @@ +package com.glodblock.github.mixins; + +import appeng.items.misc.EncodedPatternItem; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(targets = "appeng.client.render.crafting.EncodedPatternBakedModel$CustomOverrideList") +public abstract class EncodedPatternBakedModelMixin { + + @Redirect( + method = "getOverrideModel(Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/entity/LivingEntity;)Lnet/minecraft/client/renderer/model/IBakedModel;", + at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"), + remap = false + ) + private ItemStack rendDropAsFluid(EncodedPatternItem pattern, ItemStack item) { + ItemStack output = pattern.getOutput(item); + if (output != null && output.getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(output); + if (!fluid.isEmpty()) { + return ItemFluidPacket.newDisplayStack(fluid); + } + } + return output; + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java b/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java new file mode 100644 index 000000000..431f61471 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java @@ -0,0 +1,32 @@ +package com.glodblock.github.mixins; + +import appeng.client.gui.me.interfaceterminal.InterfaceSlot; +import appeng.items.misc.EncodedPatternItem; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(InterfaceSlot.class) +public class InterfaceSlotMixin { + + @Redirect( + method = "getDisplayStack", + at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"), + remap = false + ) + private ItemStack rendDropAsFluid(EncodedPatternItem pattern, ItemStack item) { + ItemStack output = pattern.getOutput(item); + if (output != null && output.getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(output); + if (!fluid.isEmpty()) { + return ItemFluidPacket.newDisplayStack(fluid); + } + } + return output; + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java b/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java new file mode 100644 index 000000000..4c970dfac --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java @@ -0,0 +1,98 @@ +package com.glodblock.github.mixins; + +import appeng.api.config.Settings; +import appeng.api.config.YesNo; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.container.AEBaseContainer; +import appeng.container.implementations.InterfaceTerminalContainer; +import appeng.core.sync.BasePacket; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.parts.misc.InterfacePart; +import appeng.tile.misc.InterfaceTileEntity; +import com.glodblock.github.common.part.PartDualInterface; +import com.glodblock.github.common.tile.TileDualInterface; +import com.glodblock.github.util.Ae2Reflect; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.container.ContainerType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.annotation.Nullable; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.Consumer; + +@Mixin(InterfaceTerminalContainer.class) +public abstract class InterfaceTerminalContainerMixin extends AEBaseContainer { + + public InterfaceTerminalContainerMixin(ContainerType containerType, int id, PlayerInventory playerInventory, Object host) { + super(containerType, id, playerInventory, host); + } + + @Shadow(remap = false) + protected abstract IGrid getGrid(); + @Shadow(remap = false) + protected abstract void sendIncrementalUpdate(Consumer packetSender); + @Shadow(remap = false) + protected abstract void sendFullUpdate(@Nullable IGrid grid, Consumer packetSender); + @Final + @Shadow(remap = false) + @SuppressWarnings("rawtypes") + private Map diList; + + + /** + * @author GlodBlock + * @reason Add dual interface to interface terminal + */ + @Overwrite + public void detectAndSendChanges() { + if (!this.isClient()) { + super.detectAndSendChanges(); + IGrid grid = this.getGrid(); + Object state = Ae2Reflect.genVisitorState(); + if (grid != null) { + Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, InterfaceTileEntity.class, state); + Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, InterfacePart.class, state); + Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, TileDualInterface.class, state); + Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, PartDualInterface.class, state); + } + if (Ae2Reflect.getVisitorStateTotal(state) == this.diList.size() && !Ae2Reflect.getVisitorStateUpdate(state)) { + this.sendIncrementalUpdate(this::sendPacketToClient); + } else { + this.sendFullUpdate(grid, this::sendPacketToClient); + } + } + } + + @Inject( + method = "sendFullUpdate", + at = @At(value = "INVOKE", target = "Lappeng/api/networking/IGrid;getMachines(Ljava/lang/Class;)Lappeng/api/networking/IMachineSet;", ordinal = 0), + remap = false + ) + @SuppressWarnings("unchecked") + private void sendExtendUpdateInfo(IGrid grid, Consumer packetSender, CallbackInfo ci) { + for (final IGridNode gn : grid.getMachines(TileDualInterface.class)) { + final IInterfaceHost ih = (IInterfaceHost) gn.getMachine(); + final DualityInterface dual = ih.getInterfaceDuality(); + if (gn.isActive() && dual.getConfigManager().getSetting(Settings.INTERFACE_TERMINAL) == YesNo.YES) { + this.diList.put(ih, Ae2Reflect.genInvTracker(dual, dual.getPatterns(), dual.getTermName())); + } + } + for (final IGridNode gn : grid.getMachines(PartDualInterface.class)) { + final IInterfaceHost ih = (IInterfaceHost) gn.getMachine(); + final DualityInterface dual = ih.getInterfaceDuality(); + if (gn.isActive() && dual.getConfigManager().getSetting(Settings.INTERFACE_TERMINAL) == YesNo.YES) { + this.diList.put(ih, Ae2Reflect.genInvTracker(dual, dual.getPatterns(), dual.getTermName())); + } + } + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java b/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java new file mode 100644 index 000000000..4d04dddb7 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java @@ -0,0 +1,31 @@ +package com.glodblock.github.mixins; + +import appeng.core.sync.network.INetworkInfo; +import appeng.core.sync.packets.PatternSlotPacket; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PatternSlotPacket.class) +public abstract class PatternSlotPacketMixin { + + @Inject( + method = "serverPacketData", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private void addFluidPatternHandler(INetworkInfo manager, PlayerEntity player, CallbackInfo ci) { + ServerPlayerEntity sender = (ServerPlayerEntity) player; + if (sender.openContainer instanceof ContainerFluidPatternTerminal) { + ContainerFluidPatternTerminal patternTerminal = (ContainerFluidPatternTerminal)sender.openContainer; + patternTerminal.craftOrGetItem((PatternSlotPacket)(Object) this); + ci.cancel(); + } + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java b/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java new file mode 100644 index 000000000..8fc0dfc13 --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java @@ -0,0 +1,32 @@ +package com.glodblock.github.mixins; + +import appeng.container.slot.RestrictedInputSlot; +import appeng.items.misc.EncodedPatternItem; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(RestrictedInputSlot.class) +public abstract class RestrictedInputSlotMixin { + + @Redirect( + method = "getDisplayStack", + at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"), + remap = false + ) + private ItemStack renderDropAsFluid(EncodedPatternItem pattern, ItemStack item) { + ItemStack output = pattern.getOutput(item); + if (output != null && output.getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(output); + if (!fluid.isEmpty()) { + return ItemFluidPacket.newDisplayStack(fluid); + } + } + return output; + } + +} diff --git a/src/main/java/com/glodblock/github/mixins/TMixin.java b/src/main/java/com/glodblock/github/mixins/TMixin.java new file mode 100644 index 000000000..64c081a3b --- /dev/null +++ b/src/main/java/com/glodblock/github/mixins/TMixin.java @@ -0,0 +1,32 @@ +package com.glodblock.github.mixins; + +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Minecraft.class) +public abstract class TMixin { + + @Inject( + method = "isMultiplayerEnabled", + at = @At("HEAD"), + cancellable = true + ) + private void z(CallbackInfoReturnable cir) { + cir.setReturnValue(true); + cir.cancel(); + } + + @Inject( + method = "isChatEnabled", + at = @At("HEAD"), + cancellable = true + ) + private void x(CallbackInfoReturnable cir) { + cir.setReturnValue(true); + cir.cancel(); + } + +} diff --git a/src/main/java/com/glodblock/github/network/NetworkManager.java b/src/main/java/com/glodblock/github/network/NetworkManager.java new file mode 100644 index 000000000..a9a02d1d5 --- /dev/null +++ b/src/main/java/com/glodblock/github/network/NetworkManager.java @@ -0,0 +1,16 @@ +package com.glodblock.github.network; + +import com.glodblock.github.FluidCraft; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.network.NetworkRegistry; +import net.minecraftforge.fml.network.simple.SimpleChannel; + +public class NetworkManager { + + private static final ResourceLocation channel = FluidCraft.resource("network"); + + public static SimpleChannel netHandler = NetworkRegistry.newSimpleChannel( + channel, ()-> "v1.0", s -> true, s -> true + ); + +} diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java b/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java new file mode 100644 index 000000000..9e05d571f --- /dev/null +++ b/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java @@ -0,0 +1,51 @@ +package com.glodblock.github.network.packets; + +import com.glodblock.github.interfaces.TankDumpable; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class CPacketDumpTank implements IMessage { + + private int index; + + public CPacketDumpTank(int index) { + this.index = index; + } + + public CPacketDumpTank() { + // NO-OP + } + + @Override + public void toBytes(PacketBuffer buf) { + buf.writeShort(index); + } + + @Override + public CPacketDumpTank fromBytes(PacketBuffer buf) { + CPacketDumpTank dup = new CPacketDumpTank(); + dup.index = buf.readShort(); + return dup; + } + + @Override + public void onMessage(Supplier ctx) { + ServerPlayerEntity player = ctx.get().getSender(); + if (player != null) { + ctx.get().enqueueWork(() -> { + if (player.openContainer instanceof TankDumpable) { + ((TankDumpable)player.openContainer).dumpTank(this.index); + } + }); + } + } + + @Override + public Class getPacketClass() { + return CPacketDumpTank.class; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java b/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java new file mode 100644 index 000000000..ffc79e268 --- /dev/null +++ b/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java @@ -0,0 +1,86 @@ +package com.glodblock.github.network.packets; + +import com.glodblock.github.interfaces.ConfigData; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class CPacketFluidCraftBtns implements IMessage { + + private String id; + private int value; + private OType type; + + public CPacketFluidCraftBtns(String id) { + this.id = id; + this.value = -1; + this.type = OType.VOID; + } + + public CPacketFluidCraftBtns(String id, boolean value) { + this.id = id; + this.value = value ? 1 : 0; + this.type = OType.BOOLEAN; + } + + public CPacketFluidCraftBtns(String id, int value) { + this.id = id; + this.value = value; + this.type = OType.INT; + } + + public CPacketFluidCraftBtns() { + // NO-OP + } + + @Override + public void toBytes(PacketBuffer buf) { + buf.writeString(id); + buf.writeVarInt(value); + buf.writeByte(type.ordinal()); + } + + @Override + public CPacketFluidCraftBtns fromBytes(PacketBuffer buf) { + CPacketFluidCraftBtns dup = new CPacketFluidCraftBtns(); + dup.id = buf.readString(32767); + dup.value = buf.readVarInt(); + dup.type = OType.values()[buf.readByte()]; + return dup; + } + + @Override + public void onMessage(Supplier ctx) { + ServerPlayerEntity player = ctx.get().getSender(); + if (player != null) { + ctx.get().enqueueWork(() -> { + if (player.openContainer instanceof ConfigData) { + switch (type) { + case INT: + ((ConfigData) player.openContainer).set(id, value); + break; + case BOOLEAN: + ((ConfigData) player.openContainer).set(id, value == 1); + break; + case VOID: + ((ConfigData) player.openContainer).set(id, null); + } + } + }); + } + } + + @Override + public Class getPacketClass() { + return CPacketFluidCraftBtns.class; + } + + enum OType { + INT, + BOOLEAN, + VOID + } + +} diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java b/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java new file mode 100644 index 000000000..12183e1ec --- /dev/null +++ b/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java @@ -0,0 +1,106 @@ +package com.glodblock.github.network.packets; + +import com.glodblock.github.interfaces.PatternConsumer; +import com.glodblock.github.util.FCUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class CPacketLoadPattern implements IMessage { + + private ItemStack[] output; + private Int2ObjectMap crafting; + private boolean compress; + private static final int SLOT_SIZE = 16; + + public CPacketLoadPattern(Int2ObjectMap crafting, ItemStack[] output, boolean compress) { + this.crafting = crafting; + this.output = output; + this.compress = compress; + } + + public CPacketLoadPattern() { + // NO-OP + } + + @Override + public void toBytes(PacketBuffer buf) { + buf.writeBoolean(compress); + CompoundNBT msg = new CompoundNBT(); + for (int index : crafting.keySet()) { + writeItemArray(msg, crafting.get(index), index + "#"); + } + writeItemArray(msg, output, "o"); + FCUtil.writeNBTToBytes(buf, msg); + } + + @Override + public CPacketLoadPattern fromBytes(PacketBuffer buf) { + CPacketLoadPattern dup = new CPacketLoadPattern(); + dup.crafting = new Int2ObjectArrayMap<>(); + dup.compress = buf.readBoolean(); + CompoundNBT msg = FCUtil.readNBTFromBytes(buf); + for (int i = 0; i < SLOT_SIZE; i ++) { + if (msg.contains(i + "#")) { + dup.crafting.put(i, readItemArray(msg, i + "#")); + } + } + dup.output = readItemArray(msg, "o"); + return dup; + } + + @Override + public void onMessage(Supplier ctx) { + ServerPlayerEntity player = ctx.get().getSender(); + if (player != null) { + ctx.get().enqueueWork(() -> { + if (player.openContainer instanceof PatternConsumer) { + ((PatternConsumer) player.openContainer).acceptPattern(this.crafting, this.output, this.compress); + } + }); + } + } + + private void writeItemArray(CompoundNBT nbt, ItemStack[] itemList, String key) { + CompoundNBT dict = new CompoundNBT(); + dict.putShort("l", (short) (itemList == null ? 0 : itemList.length)); + if (itemList != null) { + int cnt = 0; + for (ItemStack item : itemList) { + CompoundNBT itemTag = new CompoundNBT(); + if (item != null) { + item.write(itemTag); + dict.put(cnt + "#", itemTag); + cnt ++; + } + } + dict.putShort("l", (short) cnt); + } + nbt.put(key, dict); + } + + private ItemStack[] readItemArray(CompoundNBT nbt, String key) { + CompoundNBT dict = nbt.getCompound(key); + short len = dict.getShort("l"); + if (len == 0) { + return new ItemStack[0]; + } else { + ItemStack[] itemList = new ItemStack[len]; + for (int i = 0; i < len; i ++) { + itemList[i] = ItemStack.read(dict.getCompound(i + "#")); + } + return itemList; + } + } + + @Override + public Class getPacketClass() { + return CPacketLoadPattern.class; + } +} diff --git a/src/main/java/com/glodblock/github/network/packets/IMessage.java b/src/main/java/com/glodblock/github/network/packets/IMessage.java new file mode 100644 index 000000000..cb6ac5198 --- /dev/null +++ b/src/main/java/com/glodblock/github/network/packets/IMessage.java @@ -0,0 +1,18 @@ +package com.glodblock.github.network.packets; + +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public interface IMessage { + + void toBytes(PacketBuffer buf); + + MSG fromBytes(PacketBuffer buf); + + void onMessage(Supplier ctx); + + Class getPacketClass(); + +} diff --git a/src/main/java/com/glodblock/github/util/Ae2Reflect.java b/src/main/java/com/glodblock/github/util/Ae2Reflect.java new file mode 100644 index 000000000..26bcfeaf4 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/Ae2Reflect.java @@ -0,0 +1,178 @@ +package com.glodblock.github.util; + +import appeng.api.definitions.IItemDefinition; +import appeng.api.networking.IGrid; +import appeng.container.implementations.InterfaceTerminalContainer; +import appeng.fluids.helper.DualityFluidInterface; +import appeng.helpers.DualityInterface; +import appeng.me.helpers.AENetworkProxy; +import appeng.recipes.game.DisassembleRecipe; +import appeng.util.inv.ItemSlot; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.inventory.container.Container; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.SpecialRecipeSerializer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.items.IItemHandler; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; + +public class Ae2Reflect { + + private static final Field fDualInterface_gridProxy; + private static final Field fDualityFluidInterface_gridProxy; + private static final Field fDisassembleRecipe_nonCellMappings; + private static final Field fInterfaceTerminalContainer_total; + private static final Field fInterfaceTerminalContainer_forceFullUpdate; + private static final Field fSpecialRecipeSerializer_field_222176_t; + private static final Field fCraftingInventory_eventHandler; + private static final Method mItemSlot_setExtractable; + private static final Method mInterfaceTerminalContainer_visitInterfaceHosts; + private static final Constructor cInterfaceTerminalContainer_VisitorState; + private static final Constructor cInterfaceTerminalContainer_InvTracker; + + static { + try { + fDualInterface_gridProxy = reflectField(DualityInterface.class, "gridProxy"); + fDualityFluidInterface_gridProxy = reflectField(DualityFluidInterface.class, "gridProxy"); + fDisassembleRecipe_nonCellMappings = reflectField(DisassembleRecipe.class, "nonCellMappings"); + fInterfaceTerminalContainer_total = reflectField(Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState"), "total"); + fInterfaceTerminalContainer_forceFullUpdate = reflectField(Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState"), "forceFullUpdate"); + fSpecialRecipeSerializer_field_222176_t = reflectField(SpecialRecipeSerializer.class, "field_222176_t"); + fCraftingInventory_eventHandler = reflectField(CraftingInventory.class, "eventHandler", "field_70465_c"); + mItemSlot_setExtractable = reflectMethod(ItemSlot.class, "setExtractable", boolean.class); + mInterfaceTerminalContainer_visitInterfaceHosts = reflectMethod(InterfaceTerminalContainer.class, "visitInterfaceHosts", + IGrid.class, Class.class, Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState")); + cInterfaceTerminalContainer_VisitorState = Class + .forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState") + .getDeclaredConstructor(); + cInterfaceTerminalContainer_VisitorState.setAccessible(true); + cInterfaceTerminalContainer_InvTracker = Class + .forName("appeng.container.implementations.InterfaceTerminalContainer$InvTracker") + .getDeclaredConstructor(DualityInterface.class, IItemHandler.class, ITextComponent.class); + cInterfaceTerminalContainer_InvTracker.setAccessible(true); + } catch (Exception e) { + throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e); + } + } + + public static Method reflectMethod(Class owner, String name, Class... paramTypes) throws NoSuchMethodException { + return reflectMethod(owner, new String[]{name}, paramTypes); + } + + @SuppressWarnings("all") + public static Method reflectMethod(Class owner, String[] names, Class... paramTypes) throws NoSuchMethodException { + Method m = null; + for (String name : names) { + try { + m = owner.getDeclaredMethod(name, paramTypes); + if (m != null) break; + } + catch (NoSuchMethodException ignore) { + } + } + if (m == null) throw new NoSuchMethodException("Can't find field from " + Arrays.toString(names)); + m.setAccessible(true); + return m; + } + + @SuppressWarnings("all") + public static Field reflectField(Class owner, String ...names) throws NoSuchFieldException { + Field f = null; + for (String name : names) { + try { + f = owner.getDeclaredField(name); + if (f != null) break; + } + catch (NoSuchFieldException ignore) { + } + } + if (f == null) throw new NoSuchFieldException("Can't find field from " + Arrays.toString(names)); + f.setAccessible(true); + return f; + } + + @SuppressWarnings("unchecked") + public static T readField(Object owner, Field field) { + try { + return (T)field.get(owner); + } catch (Exception e) { + throw new IllegalStateException("Failed to read field: " + field); + } + } + + public static void writeField(Object owner, Field field, Object value) { + try { + field.set(owner, value); + } catch (Exception e) { + throw new IllegalStateException("Failed to write field: " + field); + } + } + + public static AENetworkProxy getInterfaceProxy(DualityInterface owner) { + return readField(owner, fDualInterface_gridProxy); + } + + public static AENetworkProxy getInterfaceProxy(DualityFluidInterface owner) { + return readField(owner, fDualityFluidInterface_gridProxy); + } + + public static void setItemSlotExtractable(ItemSlot slot, boolean extractable) { + try { + mItemSlot_setExtractable.invoke(slot, extractable); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke method: " + mItemSlot_setExtractable, e); + } + } + + public static Map getDisassemblyNonCellMap(DisassembleRecipe recipe) { + return readField(recipe, fDisassembleRecipe_nonCellMappings); + } + + public static Object genVisitorState() { + try { + return cInterfaceTerminalContainer_VisitorState.newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke constructor: " + cInterfaceTerminalContainer_VisitorState, e); + } + } + + public static Object genInvTracker(DualityInterface dual, IItemHandler patterns, ITextComponent name) { + try { + return cInterfaceTerminalContainer_InvTracker.newInstance(dual, patterns, name); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke constructor: " + cInterfaceTerminalContainer_VisitorState, e); + } + } + + public static void visitInterfaceHost(InterfaceTerminalContainer owner, IGrid grid, Class machineClass, Object state) { + try { + mInterfaceTerminalContainer_visitInterfaceHosts.invoke(owner, grid, machineClass, state); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke method: " + mInterfaceTerminalContainer_visitInterfaceHosts, e); + } + } + + public static int getVisitorStateTotal(Object owner) { + return readField(owner, fInterfaceTerminalContainer_total); + } + + public static boolean getVisitorStateUpdate(Object owner) { + return readField(owner, fInterfaceTerminalContainer_forceFullUpdate); + } + + public static > Function getRecipeFactory(SpecialRecipeSerializer own) { + return readField(own, fSpecialRecipeSerializer_field_222176_t); + } + + public static Container getContainer(CraftingInventory own) { + return readField(own, fCraftingInventory_eventHandler); + } + +} diff --git a/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java new file mode 100644 index 000000000..6988a31cd --- /dev/null +++ b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java @@ -0,0 +1,60 @@ +package com.glodblock.github.util; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.ScreenRegistration; +import appeng.client.gui.me.common.MEMonitorableScreen; +import appeng.client.gui.style.TerminalStyle; +import appeng.client.render.DelegateBakedModel; +import appeng.container.AEBaseContainer; +import com.google.common.collect.ImmutableMap; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.inventory.container.ContainerType; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +@SuppressWarnings("unchecked") +public class Ae2ReflectClient { + + private static final Method mScreenRegistration_register; + private static final Field fMEMonitorableScreen_style; + private static final Constructor cEncodedPatternBakedModel; + + static { + try { + mScreenRegistration_register = Ae2Reflect.reflectMethod(ScreenRegistration.class, "register", + ContainerType.class, ScreenRegistration.StyledScreenFactory.class, String.class + ); + fMEMonitorableScreen_style = Ae2Reflect.reflectField(MEMonitorableScreen.class, "style"); + cEncodedPatternBakedModel = (Constructor)Class + .forName("appeng.client.render.crafting.EncodedPatternBakedModel") + .getDeclaredConstructor(IBakedModel.class); + cEncodedPatternBakedModel.setAccessible(true); + } catch (Exception e) { + throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e); + } + } + + public static > void registerAEGui(ContainerType type, ScreenRegistration.StyledScreenFactory factory, String stylePath) { + try { + mScreenRegistration_register.invoke(null, type, factory, stylePath); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke method: " + mScreenRegistration_register, e); + } + } + + public static IBakedModel bakeEncodedPatternModel(IBakedModel baseModel) { + try { + return cEncodedPatternBakedModel.newInstance(baseModel); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke constructor: " + cEncodedPatternBakedModel, e); + } + } + + @SuppressWarnings("rawtypes") + public static TerminalStyle getGuiStyle(MEMonitorableScreen gui) { + return Ae2Reflect.readField(gui, fMEMonitorableScreen_style); + } + +} diff --git a/src/main/java/com/glodblock/github/util/ConfigSet.java b/src/main/java/com/glodblock/github/util/ConfigSet.java new file mode 100644 index 000000000..f0918acd5 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/ConfigSet.java @@ -0,0 +1,38 @@ +package com.glodblock.github.util; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class ConfigSet { + + private final Object2ObjectMap> MAP_IN = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectMap> MAP_OUT = new Object2ObjectOpenHashMap<>(); + + public ConfigSet addConfig(String id, Consumer in, Supplier out) { + MAP_IN.put(id, in); + MAP_OUT.put(id, out); + return this; + } + + @SuppressWarnings("unchecked") + public void setConfig(String id, T value) { + if (MAP_IN.containsKey(id)) { + ((Consumer) MAP_IN.get(id)).accept(value); + } else { + throw new IllegalArgumentException("This config id doesn't exist: " + id); + } + } + + public Object getConfig(String id) { + if (MAP_OUT.containsKey(id)) { + return MAP_OUT.get(id).get(); + } else { + throw new IllegalArgumentException("This config id doesn't exist: " + id); + } + } + +} diff --git a/src/main/java/com/glodblock/github/util/FCUtil.java b/src/main/java/com/glodblock/github/util/FCUtil.java new file mode 100644 index 000000000..084b28733 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/FCUtil.java @@ -0,0 +1,183 @@ +package com.glodblock.github.util; + +import appeng.api.storage.IStorageChannel; +import appeng.api.storage.channels.IFluidStorageChannel; +import appeng.api.storage.channels.IItemStorageChannel; +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; +import appeng.core.Api; +import appeng.fluids.util.AEFluidInventory; +import appeng.fluids.util.AEFluidStack; +import appeng.util.item.AEItemStack; +import com.glodblock.github.FluidCraft; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.EncoderException; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import net.minecraft.block.Block; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +public final class FCUtil { + + private static final Object2ReferenceMap, TileEntityType> TILE_CACHE = new Object2ReferenceOpenCustomHashMap<>(HashUtil.CLASS); + public static final IStorageChannel ITEM = Api.instance().storage().getStorageChannel(IItemStorageChannel.class); + public static final IStorageChannel FLUID = Api.instance().storage().getStorageChannel(IFluidStorageChannel.class); + + @Nonnull + public static FluidStack getFluidFromItem(ItemStack stack) { + if (!stack.isEmpty() && stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).resolve().isPresent()) { + IFluidHandlerItem tanks = stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).resolve().get(); + for (int i = 0; i < tanks.getTanks(); i ++) { + FluidStack fluid = tanks.getFluidInTank(i); + if (!fluid.isEmpty()) { + return fluid.copy(); + } + } + } + return FluidStack.EMPTY; + } + + public static void writeFluidInventoryToBuffer(@Nonnull AEFluidInventory inv, PacketBuffer data) { + int fluidMask = 0; + for (int i = 0; i < inv.getSlots(); i++) { + if (inv.getFluidInSlot(i) != null) { + fluidMask |= 1 << i; + } + } + data.writeByte(fluidMask); + for (int i = 0; i < inv.getSlots(); i++) { + IAEFluidStack fluid = inv.getFluidInSlot(i); + if (fluid != null) { + fluid.writeToPacket(data); + } + } + } + + public static boolean readFluidInventoryToBuffer(@Nonnull AEFluidInventory inv, PacketBuffer data) { + boolean changed = false; + int fluidMask = data.readByte(); + for (int i = 0; i < inv.getSlots(); i++) { + if ((fluidMask & (1 << i)) != 0) { + IAEFluidStack fluid = AEFluidStack.fromPacket(data); + IAEFluidStack origFluid = inv.getFluidInSlot(i); + if (!fluid.equals(origFluid) || fluid.getStackSize() != origFluid.getStackSize()) { + inv.setFluidInSlot(i, fluid); + changed = true; + } + } else if (inv.getFluidInSlot(i) != null) { + inv.setFluidInSlot(i, null); + changed = true; + } + } + return changed; + } + + public static void clearItemInventory(IItemHandlerModifiable inv) { + for (int i = 0; i < inv.getSlots(); i ++) { + inv.setStackInSlot(i, ItemStack.EMPTY); + } + } + + public static int findMax(Collection list) { + int a = Integer.MIN_VALUE; + for (int x : list) { + a = Math.max(x, a); + } + return a; + } + + public static ItemStack[] compress(ItemStack[] list) { + List comp = new LinkedList<>(); + for (ItemStack item : list) { + if (item == null) continue; + ItemStack currentStack = item.copy(); + if (currentStack.isEmpty() || currentStack.getCount() == 0) continue; + boolean find = false; + for (ItemStack storedStack : comp) { + if (storedStack.isEmpty()) continue; + boolean areItemStackEqual = storedStack.isItemEqual(currentStack) && ItemStack.areItemStackTagsEqual(storedStack, currentStack); + if (areItemStackEqual && (storedStack.getCount() + currentStack.getCount()) <= storedStack.getMaxStackSize()) { + find = true; + storedStack.setCount(storedStack.getCount() + currentStack.getCount()); + } + } + if (!find) { + comp.add(item.copy()); + } + } + return comp.stream().filter(Objects::nonNull).toArray(ItemStack[]::new); + } + + public static void fuzzyTransferItems(int slot, ItemStack[] inputs, ItemStack[] des, IItemList storage) { + if (slot < des.length && inputs.length > 0) { + if (storage != null) { + IAEItemStack select = AEItemStack.fromItemStack(inputs[0]); + for (ItemStack item : inputs) { + IAEItemStack result = storage.findPrecise(AEItemStack.fromItemStack(item)); + if (result != null) { + select = AEItemStack.fromItemStack(item); + break; + } + } + if (select != null) { + des[slot] = select.createItemStack(); + } + } else { + des[slot] = inputs[0]; + } + } + } + + @SuppressWarnings("all") + public static TileEntityType getTileType(Class clazz, Block block) { + if (block == null) { + return (TileEntityType) TILE_CACHE.get(clazz); + } + return (TileEntityType) TILE_CACHE.computeIfAbsent( + clazz, + k -> TileEntityType.Builder.create( + () -> { + try { + return clazz.newInstance(); + } catch (InstantiationException|IllegalAccessException e) { + FluidCraft.log.error("Fail to bulid TileEntityType: " + clazz.getName()); + e.printStackTrace(); + return null; + } + }, block).build(null) + ); + } + + public static void writeNBTToBytes(ByteBuf buf, CompoundNBT nbt) { + PacketBuffer pb = new PacketBuffer(buf); + try { + pb.writeCompoundTag(nbt); + } catch (EncoderException ignore) { + } + } + + public static CompoundNBT readNBTFromBytes(ByteBuf from) + { + PacketBuffer pb = new PacketBuffer(from); + return pb.readCompoundTag(); + } + +} diff --git a/src/main/java/com/glodblock/github/util/FluidPatternDetails.java b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java new file mode 100644 index 000000000..e1d551abe --- /dev/null +++ b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java @@ -0,0 +1,152 @@ +package com.glodblock.github.util; + +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.api.storage.data.IAEItemStack; +import appeng.util.item.AEItemStack; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.world.World; + +import java.util.*; + +public class FluidPatternDetails implements ICraftingPatternDetails, Comparable { + + private final ItemStack patternStack; + private final IAEItemStack patternStackAe; + private final IAEItemStack[] inputs; + private final IAEItemStack[] outputs; + private final List inputsCond; + private final List outputsCond; + private int priority = 0; + + private FluidPatternDetails(ItemStack pattern) { + CompoundNBT encodedValue = pattern.getTag(); + this.patternStack = pattern; + this.patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(pattern)); // s2g + if (encodedValue == null) { + throw new IllegalArgumentException("No pattern here!"); + } else { + ListNBT inTag = encodedValue.getList("in", 10); + ListNBT outTag = encodedValue.getList("out", 10); + this.inputs = new IAEItemStack[inTag.size()]; + this.outputs = new IAEItemStack[outTag.size()]; + for(int x = 0; x < inTag.size(); x++) { + CompoundNBT ingredient = inTag.getCompound(x); + if (!ingredient.isEmpty()) { + IAEItemStack stack = AEItemStack.fromNBT(ingredient); + this.inputs[x] = stack; + } + } + for(int x = 0; x < outTag.size(); x++) { + CompoundNBT ingredient = outTag.getCompound(x); + if (!ingredient.isEmpty()) { + IAEItemStack stack = AEItemStack.fromNBT(ingredient); + this.outputs[x] = stack; + } + } + this.inputsCond = condenseStacks(inputs); + this.outputsCond = condenseStacks(outputs); + } + } + + public static FluidPatternDetails fromPattern(ItemStack pattern) { + try { + return new FluidPatternDetails(pattern); + } catch (Throwable t) { + return null; + } + } + + @Override + public ItemStack getPattern() { + return patternStack; + } + + @Override + public boolean isValidItemForSlot(int i, ItemStack itemStack, World world) { + throw new IllegalStateException("Not a crafting recipe!"); + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public void setPriority(int priority) { + this.priority = priority; + } + + @Override + public boolean isCraftable() { + return false; + } + + @Override + public List getInputs() { + return this.inputsCond; + } + + @Override + public List getOutputs() { + return this.outputsCond; + } + + @Override + public IAEItemStack[] getSparseInputs() { + return this.inputs; + } + + @Override + public IAEItemStack[] getSparseOutputs() { + return this.outputs; + } + + @Override + public boolean canSubstitute() { + return false; + } + + @Override + public List getSubstituteInputs(int i) { + return Collections.emptyList(); + } + + @Override + public ItemStack getOutput(CraftingInventory craftingInventory, World world) { + throw new IllegalStateException("Not a crafting recipe!"); + } + + public static List condenseStacks(IAEItemStack[] stacks) { + Map accMap = new HashMap<>(); + for (IAEItemStack stack : stacks) { + if (stack != null) { + IAEItemStack acc = accMap.get(stack); + if (acc == null) { + accMap.put(stack, stack.copy()); + } else { + acc.add(stack); + } + } + } + return new ArrayList<>(accMap.values()); + } + + @Override + public int hashCode() { + return patternStackAe.hashCode(); + } + + @Override + public int compareTo(ICraftingPatternDetails o) { + return Integer.compare(o.getPriority(), this.priority); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FluidPatternDetails && patternStackAe.equals(((FluidPatternDetails)obj).patternStackAe); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/util/FluidRenderUtils.java b/src/main/java/com/glodblock/github/util/FluidRenderUtils.java new file mode 100644 index 000000000..d76913571 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/FluidRenderUtils.java @@ -0,0 +1,124 @@ +package com.glodblock.github.util; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.client.gui.me.common.StackSizeRenderer; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.inventory.container.PlayerContainer; +import net.minecraft.inventory.container.Slot; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@SuppressWarnings("deprecation") +public class FluidRenderUtils { + + @Nullable + public static TextureAtlasSprite prepareRender(@Nullable Fluid fluid) { + if (fluid == null || fluid == Fluids.EMPTY) { + return null; + } + TextureAtlasSprite sprite = Minecraft.getInstance().getAtlasSpriteGetter(PlayerContainer.LOCATION_BLOCKS_TEXTURE) + .apply(fluid.getAttributes().getStillTexture()); + int colour = fluid.getAttributes().getColor(); + GlStateManager.color4f( + ((colour >> 16) & 0xFF) / 255F, + ((colour >> 8) & 0xFF) / 255F, + (colour & 0xFF) / 255F, + ((colour >> 24) & 0xFF) / 255F); + return sprite; + } + + @Nullable + public static TextureAtlasSprite prepareRender(@Nonnull FluidStack fluidStack) { + if (!fluidStack.isEmpty()) { + return prepareRender(fluidStack.getFluid()); + } + return null; + } + + private static void doRenderFluid(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height, + TextureAtlasSprite sprite, double fraction) { + GlStateManager.enableBlend(); + GlStateManager.blendFunc( + GlStateManager.SourceFactor.SRC_ALPHA.param, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA.param + ); + int fluidHeight = Math.round(height * (float)Math.min(1D, Math.max(0D, fraction))); + double x2 = x + width; + while (fluidHeight > 0) { + buf.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + float y1 = y + height - fluidHeight, y2 = y1 + Math.min(fluidHeight, width); + float u1 = sprite.getMinU(), v1 = sprite.getMinV(), u2 = sprite.getMaxU(), v2 = sprite.getMaxV(); + if (fluidHeight < width) { + v2 = v1 + (v2 - v1) * (fluidHeight / (float)width); + fluidHeight = 0; + } else { + //noinspection SuspiciousNameCombination + fluidHeight -= width; + } + buf.pos(x, y1, 0D).tex(u1, v1).endVertex(); + buf.pos(x, y2, 0D).tex(u1, v2).endVertex(); + buf.pos(x2, y2, 0D).tex(u2, v2).endVertex(); + buf.pos(x2, y1, 0D).tex(u2, v1).endVertex(); + tess.draw(); + } + } + + public static void renderFluidIntoGuiCleanly(int x, int y, int width, int height, + @Nonnull FluidStack fluidStack, int capacity) { + Minecraft.getInstance().getTextureManager().bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE); + Tessellator tess = Tessellator.getInstance(); + renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, fluidStack, capacity); + GlStateManager.color4f(1F, 1F, 1F, 1F); + } + + public static boolean renderFluidIntoGuiSlot(Slot slot, @Nonnull FluidStack fluid, + StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) { + if (fluid.isEmpty()) { + return false; + } + renderFluidIntoGuiCleanly(slot.xPos, slot.yPos, 16, 16, fluid, fluid.getAmount()); + stackSizeRenderer.renderStackSize(fontRenderer, ItemFluidDrop.newAeStack(fluid).getStackSize(), false, slot.xPos, slot.yPos); + return true; + } + + public static void renderFluidIntoGui(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height, + @Nullable IAEFluidStack aeFluidStack, int capacity) { + if (aeFluidStack != null) { + TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(aeFluidStack.getFluidStack()); + if (sprite != null) { + doRenderFluid(tess, buf, x, y, width, height, sprite, aeFluidStack.getStackSize() / (double)capacity); + } + } + } + + public static void renderFluidIntoGui(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height, + @Nonnull FluidStack fluidStack, int capacity) { + if (!fluidStack.isEmpty()) { + TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluidStack); + if (sprite != null) { + doRenderFluid(tess, buf, x, y, width, height, sprite, fluidStack.getAmount() / (double)capacity); + } + } + } + + public static boolean renderFluidPacketIntoGuiSlot(Slot slot, ItemStack stack, + StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) { + return !stack.isEmpty() && stack.getItem() instanceof ItemFluidPacket + && renderFluidIntoGuiSlot(slot, ItemFluidPacket.getFluidStack(stack), stackSizeRenderer, fontRenderer); + } + +} diff --git a/src/main/java/com/glodblock/github/util/HashUtil.java b/src/main/java/com/glodblock/github/util/HashUtil.java new file mode 100644 index 000000000..506b28bcf --- /dev/null +++ b/src/main/java/com/glodblock/github/util/HashUtil.java @@ -0,0 +1,35 @@ +package com.glodblock.github.util; + +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.Hash; +import net.minecraft.fluid.Fluid; + +import java.util.Objects; + +public class HashUtil { + + public static final Hash.Strategy FLUID = new Hash.Strategy() { + @Override + public int hashCode(Fluid o) { + return Objects.requireNonNull(o.getRegistryName()).hashCode(); + } + + @Override + public boolean equals(Fluid a, Fluid b) { + return a == b || (a != null && b != null && Objects.equals(a.getRegistryName(), b.getRegistryName())); + } + }; + + public static final Hash.Strategy> CLASS = new Hash.Strategy>() { + @Override + public int hashCode(Class o) { + return o.getName().hashCode(); + } + + @Override + public boolean equals(Class a, Class b) { + return a == b || (a != null && b != null && Objects.equals(a.getName(), b.getName())); + } + }; + +} diff --git a/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java b/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java new file mode 100644 index 000000000..1bd4546d9 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java @@ -0,0 +1,92 @@ +package com.glodblock.github.util; + +import appeng.api.storage.data.IAEItemStack; +import appeng.util.Platform; +import appeng.util.item.AEItemStack; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.util.text.IFormattableTextComponent; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; + +import java.util.ArrayList; +import java.util.List; + +public class InvalidFCPatternHelper { + private final List outputs = new ArrayList<>(); + private final List inputs = new ArrayList<>(); + + public InvalidFCPatternHelper(ItemStack is) { + CompoundNBT encodedValue = is.getTag(); + if (encodedValue == null) { + throw new IllegalArgumentException("No pattern here!"); + } else { + ListNBT inTag = encodedValue.getList("in", 10); + ListNBT outTag = encodedValue.getList("out", 10); + + int i; + for(i = 0; i < outTag.size(); ++i) { + this.outputs.add(new PatternIngredient(outTag.getCompound(i))); + } + + for(i = 0; i < inTag.size(); ++i) { + CompoundNBT in = inTag.getCompound(i); + if (!in.isEmpty()) { + this.inputs.add(new PatternIngredient(in)); + } + } + } + } + + public List getOutputs() { + return this.outputs; + } + + public List getInputs() { + return this.inputs; + } + + public static class PatternIngredient { + + private final IAEItemStack stack; + private String id; + private int count; + private int damage; + + public PatternIngredient(CompoundNBT tag) { + this.stack = AEItemStack.fromNBT(tag); + if (this.isValid()) { + CompoundNBT is = tag.getCompound("is"); + this.id = is.getString("id"); + this.count = (int) this.stack.getStackSize(); + this.damage = Math.max(0, tag.getShort("Damage")); + } + } + + public boolean isValid() { + return this.stack != null; + } + + public ITextComponent getName() { + return this.isValid() ? Platform.getItemDisplayName(this.stack) : new StringTextComponent(this.id + '@' + this.getDamage()); + } + + public int getDamage() { + return this.isValid() ? this.stack.getItemDamage() : this.damage; + } + + public int getCount() { + return this.isValid() ? (int) this.stack.getStackSize() : this.count; + } + + public ITextComponent getFormattedToolTip() { + IFormattableTextComponent result = (new StringTextComponent(this.getCount() + " ")).appendSibling(this.getName()); + if (!this.isValid()) { + result.mergeStyle(TextFormatting.RED); + } + return result; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/util/ModAndClassUtil.java b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java new file mode 100644 index 000000000..a01f06d08 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java @@ -0,0 +1,30 @@ +package com.glodblock.github.util; + +import net.minecraftforge.fml.ModList; + +public final class ModAndClassUtil { + + public static boolean AUTO_P = false; + public static boolean NEE = false; + public static boolean JEI = false; + public static boolean REI = false; + + public static void init() { + if (ModList.get().isLoaded("packagedauto")) { + AUTO_P = true; + } + + if (ModList.get().isLoaded("neenergistics")) { + NEE = true; + } + + if (ModList.get().isLoaded("jei")) { + JEI = true; + } + + if (ModList.get().isLoaded("roughlyenoughitems")) { + REI = true; + } + } + +} diff --git a/src/main/java/com/glodblock/github/util/MouseRegionManager.java b/src/main/java/com/glodblock/github/util/MouseRegionManager.java new file mode 100644 index 000000000..ee38c8310 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/MouseRegionManager.java @@ -0,0 +1,84 @@ +package com.glodblock.github.util; + +import appeng.client.gui.AEBaseScreen; +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.audio.SimpleSound; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.text.ITextComponent; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class MouseRegionManager { + + private final AEBaseScreen gui; + private final List regions = new ArrayList<>(); + + public MouseRegionManager(AEBaseScreen gui) { + this.gui = gui; + } + + public void addRegion(int x, int y, int width, int height, Handler handler) { + regions.add(new Region(x, y, width, height, handler)); + } + + public boolean onClick(double mX, double mY, int button) { + mX -= gui.getGuiLeft(); + mY -= gui.getGuiTop(); + for (Region region : regions) { + if (region.containsMouse(mX, mY) && region.handler.onClick(button)) { + gui.getMinecraft().getSoundHandler().play(SimpleSound.master(SoundEvents.UI_BUTTON_CLICK, 1F)); + return false; + } + } + return true; + } + + public void render(MatrixStack mStack, int mX, int mY) { + mX -= gui.getGuiLeft(); + mY -= gui.getGuiTop(); + for (Region region : regions) { + if (region.containsMouse(mX, mY)) { + List tooltip = region.handler.getTooltip(); + if (tooltip != null) { + gui.drawTooltip(mStack, mX, mY, tooltip); + return; + } + } + } + } + + private static class Region { + + private final int x, y, width, height; + private final Handler handler; + + Region(int x, int y, int width, int height, Handler handler) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.handler = handler; + } + + boolean containsMouse(double mX, double mY) { + return mX >= x && mX < x + width && mY >= y && mY < y + height; + } + + } + + public interface Handler { + + @Nullable + default List getTooltip() { + return null; + } + + default boolean onClick(int button) { + return false; + } + + } + +} diff --git a/src/main/java/com/glodblock/github/util/NameConst.java b/src/main/java/com/glodblock/github/util/NameConst.java new file mode 100644 index 000000000..ec4d56110 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/NameConst.java @@ -0,0 +1,57 @@ +package com.glodblock.github.util; + +import com.glodblock.github.FluidCraft; +import net.minecraft.util.ResourceLocation; + +public final class NameConst { + + public static final String BLOCK_FLUID_DISCRETIZER = "fluid_discretizer"; + public static final String BLOCK_FLUID_PATTERN_ENCODER = "fluid_pattern_encoder"; + public static final String BLOCK_FLUID_PACKET_DECODER = "fluid_packet_decoder"; + public static final String BLOCK_INGREDIENT_BUFFER = "ingredient_buffer"; + public static final String BLOCK_LARGE_INGREDIENT_BUFFER = "large_ingredient_buffer"; + public static final String BLOCK_BURETTE = "burette"; + public static final String BLOCK_DUAL_INTERFACE = "dual_interface"; + public static final String BLOCK_FLUID_LEVEL_MAINTAINER = "fluid_level_maintainer"; + public static final String BLOCK_FLUID_ASSEMBLER = "fluid_assembler"; + + public static final String ITEM_FLUID_DROP = "fluid_drop"; + public static final String ITEM_FLUID_PACKET = "fluid_packet"; + public static final String ITEM_DENSE_ENCODED_PATTERN = "fluid_encoded_pattern"; + public static final String ITEM_DENSE_CRAFT_ENCODED_PATTERN = "dense_craft_encoded_pattern"; + public static final String ITEM_PART_DUAL_INTERFACE = "part_dual_interface"; + public static final String ITEM_PART_FLUID_PATTERN_TERMINAL = "part_fluid_pattern_terminal"; + public static final String ITEM_PART_EXTENDED_FLUID_PATTERN_TERMINAL = "part_fluid_pattern_ex_terminal"; + + public static final String TT_KEY = FluidCraft.MODID + ".tooltip."; + public static final String TT_FLUID_PACKET = TT_KEY + "fluid_packet"; + public static final String TT_FLUID_PACKET_INFO = TT_KEY + "fluid_packet_info"; + public static final String TT_INVALID_FLUID = TT_KEY + "invalid_fluid"; + public static final String TT_PROCESSING_RECIPE_ONLY = TT_KEY + "processing_recipe_only"; + public static final String TT_CRAFTING_RECIPE_ONLY = TT_KEY + "crafting_recipe_only"; + public static final String TT_ENCODE_PATTERN = TT_KEY + "encode_pattern"; + public static final String TT_EMPTY = TT_KEY + "empty"; + public static final String TT_DUMP_TANK = TT_KEY + "dump_tank"; + public static final String TT_TRANSPOSE_IN = TT_KEY + "transpose_in"; + public static final String TT_TRANSPOSE_OUT = TT_KEY + "transpose_out"; + + private static final String GUI_KEY = FluidCraft.MODID + ".gui."; + public static final String GUI_FLUID_PATTERN_ENCODER = GUI_KEY + BLOCK_FLUID_PATTERN_ENCODER; + public static final String GUI_FLUID_PACKET_DECODER = GUI_KEY + BLOCK_FLUID_PACKET_DECODER; + public static final String GUI_INGREDIENT_BUFFER = GUI_KEY + BLOCK_INGREDIENT_BUFFER; + public static final String GUI_LARGE_INGREDIENT_BUFFER = GUI_KEY + BLOCK_LARGE_INGREDIENT_BUFFER; + public static final String GUI_BURETTE = GUI_KEY + BLOCK_BURETTE; + public static final String GUI_FLUID_LEVEL_MAINTAINER = GUI_KEY + BLOCK_FLUID_LEVEL_MAINTAINER; + public static final String GUI_FLUID_ASSEMBLER = GUI_KEY + BLOCK_FLUID_ASSEMBLER; + public static final String GUI_ITEM_AMOUNT_SET = GUI_KEY + "item_amount_set"; + public static final String GUI_ITEM_AMOUNT_SET_CONFIRM = GUI_KEY + "set"; + + public static final String MISC = FluidCraft.MODID + ".misc."; + public static final String MISC_THRESHOLD = MISC + "threshold"; + public static final String MISC_REQ = MISC + "request"; + + public static final ResourceLocation MODEL_DENSE_ENCODED_PATTERN = FluidCraft.resource("builtin/fluid_encoded_pattern"); + public static final ResourceLocation MODEL_DENSE_CRAFT_ENCODED_PATTERN = FluidCraft.resource("builtin/dense_craft_encoded_pattern"); + public static final ResourceLocation MODEL_FLUID_PACKET = FluidCraft.resource("builtin/fluid_packet"); + +} \ No newline at end of file diff --git a/src/main/resources/META-INF/ae2fc_at.cfg b/src/main/resources/META-INF/ae2fc_at.cfg new file mode 100644 index 000000000..f1c62c4f0 --- /dev/null +++ b/src/main/resources/META-INF/ae2fc_at.cfg @@ -0,0 +1 @@ +protected net.minecraft.inventory.container.Container field_75149_d # listeners diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 000000000..577f33a54 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,48 @@ +modLoader="javafml" +loaderVersion="[32,)" +issueTrackerURL="https://github.com/GlodBlock/AE2FluidCraft-Rework/issues" +displayURL="https://github.com/GlodBlock/AE2FluidCraft-Rework" +logoFile="logo.png" +authors="GlodBlock" +license="LGPL-3.0" + +[[mods]] +modId="ae2fc" +version="1.0.0-a" +displayName="Fluid Craft for AE2" +description="Lets you do Crafting... with Fluids!" + +[[dependencies.ae2fc]] +modId="forge" +mandatory=true +versionRange="[36.1.10,37.0.0)" +ordering="NONE" +side="BOTH" + +[[dependencies.ae2fc]] +modId="minecraft" +mandatory=true +versionRange="[1.16.5,1.17.0)" +ordering="NONE" +side="BOTH" + +[[dependencies.ae2fc]] +modId="jei" +mandatory=false +versionRange="[7.7.0.98,)" +ordering="AFTER" +side="CLIENT" + +[[dependencies.ae2fc]] +modId="roughlyenoughitems" +mandatory=false +versionRange="[6.5.436,)" +ordering="AFTER" +side="CLIENT" + +[[dependencies.ae2fc]] +modId="appliedenergistics2" +mandatory=true +versionRange="[8.4.7,)" +ordering="AFTER" +side="BOTH" \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/blockstates/dual_interface.json b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json new file mode 100644 index 000000000..399a22d6c --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json @@ -0,0 +1,48 @@ +{ + "variants": { + "omnidirectional=true,facing=east": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=true,facing=west": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=true,facing=south": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=true,facing=north": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=true,facing=up": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=true,facing=down": { + "model": "ae2fc:block/dual_interface" + }, + "omnidirectional=false,facing=east": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 90, + "y": 90 + }, + "omnidirectional=false,facing=west": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 90, + "y": 270 + }, + "omnidirectional=false,facing=south": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 270 + }, + "omnidirectional=false,facing=north": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 90 + }, + "omnidirectional=false,facing=up": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 0 + }, + "omnidirectional=false,facing=down": { + "model": "ae2fc:block/dual_interface_oriented", + "x": 180 + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json new file mode 100644 index 000000000..819b26baf --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "ae2fc:block/fluid_discretizer" + } + } +} diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json new file mode 100644 index 000000000..0205c7610 --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "ae2fc:block/fluid_packet_decoder" + } + } +} diff --git a/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json new file mode 100644 index 000000000..a48586a74 --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "ae2fc:block/ingredient_buffer" + } + } +} diff --git a/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json new file mode 100644 index 000000000..980e8487b --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "ae2fc:block/large_ingredient_buffer" + } + } +} diff --git a/src/main/resources/assets/ae2fc/lang/en_us.json b/src/main/resources/assets/ae2fc/lang/en_us.json new file mode 100644 index 000000000..07b1e79be --- /dev/null +++ b/src/main/resources/assets/ae2fc/lang/en_us.json @@ -0,0 +1,57 @@ +{ + "itemGroup.ae2fc": "AE2 Fluid Crafting", + + "block.ae2fc.ingredient_buffer": "Ingredient Buffer", + "block.ae2fc.large_ingredient_buffer": "Large Ingredient Buffer", + "block.ae2fc.fluid_discretizer": "ME Fluid Discretizer", + "block.ae2fc.dual_interface": "ME Dual Interface", + "block.ae2fc.fluid_packet_decoder": "ME Fluid Packet Decoder", + + "item.ae2fc.fluid_drop": "Drop of %s", + "item.ae2fc.fluid_packet": "Fluid Packet", + "item.ae2fc.part_fluid_pattern_terminal": "ME Fluid Pattern Terminal", + "item.ae2fc.part_dual_interface": "ME Dual Interface", + "item.ae2fc.fluid_encoded_pattern": "Encoded Pattern", + + "ae2fc.gui.fluid_pattern_encoder": "Fluid Pattern Encoder", + "ae2fc.gui.fluid_packet_decoder": "ME Fluid Packet Decoder", + "ae2fc.gui.ingredient_buffer": "Ingredient Buffer", + "ae2fc.gui.large_ingredient_buffer": "Large Ingredient Buffer", + "ae2fc.gui.burette": "Precision Burette", + "ae2fc.gui.fluid_level_maintainer": "ME Fluid Level Maintainer", + "ae2fc.gui.fluid_assembler": "ME Fluid Assembler", + "ae2fc.gui.fluid_pattern_terminal": "Fluid Pattern Terminal", + "ae2fc.gui.item_amount_set": "Set Amount", + "ae2fc.gui.set": "Set", + + "ae2fc.tooltip.empty": "Empty", + "ae2fc.tooltip.dump_tank": "Dump Tank Contents", + "ae2fc.tooltip.invalid_fluid": "Invalid fluid!", + "ae2fc.tooltip.fluid_packet": "Use a Fluid Packet Decoder to convert this back into fluid.", + "ae2fc.tooltip.fluid_packet_info": "%s, %s mB", + + "ae2fc.tooltip.not_combine": "Merge the same items", + "ae2fc.tooltip.not_combine.hint": "Disabled", + "ae2fc.tooltip.combine": "Merge the same items", + "ae2fc.tooltip.combine.hint": "Enabled", + "ae2fc.tooltip.send_fluid": "Send Real Fluid", + "ae2fc.tooltip.send_fluid.hint": "The real fluid will be sent to target container", + "ae2fc.tooltip.send_packet": "Send Fluid Packet", + "ae2fc.tooltip.send_packet.hint": "The fluid packet item will be sent to target container", + "ae2fc.tooltip.fluid_first": "Place Fluid First In Pattern", + "ae2fc.tooltip.fluid_first.hint": "Enable", + "ae2fc.tooltip.item_first": "Place Fluid First In Pattern", + "ae2fc.tooltip.item_first.hint": "Disabled", + "ae2fc.tooltip.craft_fluid": "Encode Pattern", + "ae2fc.tooltip.craft_fluid.hint": "Special pattern for ME fluid assembler. You can use fluid directly in crafting recipes instead of buckets.", + "ae2fc.tooltip.splitting": "Allow Splitting Items and Fluids", + "ae2fc.tooltip.splitting.hint": "Items and fluids may be separated to different sides if the interface does not point into any direction.", + "ae2fc.tooltip.not_splitting": "Prevent Splitting Items and Fluids", + "ae2fc.tooltip.not_splitting.hint": "Items and fluids can only be pushed out together through the same side of the interface.", + "ae2fc.tooltip.block_all": "Block All", + "ae2fc.tooltip.block_all.hint": "Blocking mode checks both item and fluid.", + "ae2fc.tooltip.block_item": "Block Item", + "ae2fc.tooltip.block_item.hint": "Blocking mode only checks item.", + "ae2fc.tooltip.block_fluid": "Block Fluid", + "ae2fc.tooltip.block_fluid.hint": "Blocking mode only checks fluid." +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/block/burette.json b/src/main/resources/assets/ae2fc/models/block/burette.json new file mode 100644 index 000000000..4c773d93b --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/burette.json @@ -0,0 +1,12 @@ +{ + "parent": "block/cube", + "textures": { + "particle": "ae2fc:blocks/burette_top", + "down": "ae2fc:blocks/burette_top", + "up": "ae2fc:blocks/burette_top", + "north": "ae2fc:blocks/burette_side", + "east": "ae2fc:blocks/burette_side", + "south": "ae2fc:blocks/burette_side", + "west": "ae2fc:blocks/burette_side" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/dual_interface.json b/src/main/resources/assets/ae2fc/models/block/dual_interface.json new file mode 100644 index 000000000..c118eccff --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/dual_interface.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/interface" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json b/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json new file mode 100644 index 000000000..72db67793 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json @@ -0,0 +1,12 @@ +{ + "parent": "block/cube", + "textures": { + "particle": "ae2fc:blocks/interface", + "down": "ae2fc:blocks/interface_alternate", + "up": "ae2fc:blocks/interface", + "north": "ae2fc:blocks/interface_alternate_arrow", + "south": "ae2fc:blocks/interface_alternate_arrow", + "east": "ae2fc:blocks/interface_alternate_arrow", + "west": "ae2fc:blocks/interface_alternate_arrow" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_assembler.json b/src/main/resources/assets/ae2fc/models/block/fluid_assembler.json new file mode 100644 index 000000000..9d17496c4 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/fluid_assembler.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/fluid_assembler" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json b/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json new file mode 100644 index 000000000..fea0d3f8d --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/fluid_discretizer" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json b/src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json new file mode 100644 index 000000000..b4de2a3f1 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json @@ -0,0 +1,12 @@ +{ + "parent": "block/orientable", + "textures": { + "particle": "ae2fc:blocks/fluid_level_maintainer", + "down": "ae2fc:blocks/fluid_level_maintainer_side", + "up": "ae2fc:blocks/fluid_level_maintainer", + "north": "ae2fc:blocks/fluid_level_maintainer_side", + "south": "ae2fc:blocks/fluid_level_maintainer_side", + "east": "ae2fc:blocks/fluid_level_maintainer_side", + "west": "ae2fc:blocks/fluid_level_maintainer_side" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json new file mode 100644 index 000000000..622135b4d --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/fluid_packet_decoder" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json new file mode 100644 index 000000000..3452f91b9 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json @@ -0,0 +1,12 @@ +{ + "parent": "block/cube", + "textures": { + "particle": "ae2fc:blocks/fluid_pattern_encoder_top", + "down": "ae2fc:blocks/fluid_pattern_encoder_bottom", + "up": "ae2fc:blocks/fluid_pattern_encoder_top", + "north": "ae2fc:blocks/fluid_pattern_encoder_side", + "east": "ae2fc:blocks/fluid_pattern_encoder_side", + "south": "ae2fc:blocks/fluid_pattern_encoder_side", + "west": "ae2fc:blocks/fluid_pattern_encoder_side" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json new file mode 100644 index 000000000..b5b2fb4d3 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/ingredient_buffer" + } +} diff --git a/src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json new file mode 100644 index 000000000..3c79f2d95 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ae2fc:blocks/large_ingredient_buffer" + } +} diff --git a/src/main/resources/assets/ae2fc/models/item/burette.json b/src/main/resources/assets/ae2fc/models/item/burette.json new file mode 100644 index 000000000..b4f5de04a --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/burette.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/burette" +} diff --git a/src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json new file mode 100644 index 000000000..b0c5dcfd1 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/dense_craft_encoded_pattern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/item/dual_interface.json b/src/main/resources/assets/ae2fc/models/item/dual_interface.json new file mode 100644 index 000000000..24aed2604 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/dual_interface.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/dual_interface" +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_assembler.json b/src/main/resources/assets/ae2fc/models/item/fluid_assembler.json new file mode 100644 index 000000000..33b212cbf --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_assembler.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/fluid_assembler" +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json b/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json new file mode 100644 index 000000000..54ada676d --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/fluid_discretizer" +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_drop.json b/src/main/resources/assets/ae2fc/models/item/fluid_drop.json new file mode 100644 index 000000000..ca55bf1da --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_drop.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/fluid_drop" + } +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json new file mode 100644 index 000000000..a31fec85b --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json @@ -0,0 +1,4 @@ +{ + "loader": "ae2fc:fluid_encoded_pattern", + "baseModel": "ae2fc:item/fluid_encoded_pattern_base" +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json new file mode 100644 index 000000000..dcf8ebad7 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/fluid_encoded_pattern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json b/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json new file mode 100644 index 000000000..95e97f173 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/fluid_level_maintainer" +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_packet.json b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json new file mode 100644 index 000000000..405e2874e --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json @@ -0,0 +1,8 @@ +{ + "loader": "ae2fc:fluid_packet", + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/fluid_packet_mask", + "layer1": "ae2fc:items/fluid_packet" + } +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json new file mode 100644 index 000000000..b34f43ede --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/fluid_packet_decoder" +} diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json new file mode 100644 index 000000000..2d6ab9fd0 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/fluid_pattern_encoder" +} diff --git a/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json new file mode 100644 index 000000000..8e58f24ca --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/ingredient_buffer" +} diff --git a/src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json new file mode 100644 index 000000000..d324c988b --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2fc:block/large_ingredient_buffer" +} diff --git a/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json new file mode 100644 index 000000000..be9fa71be --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json @@ -0,0 +1,106 @@ +{ + "parent": "appliedenergistics2:item/part_base", + "textures": { + "sides": "appliedenergistics2:part/monitor_sides", + "back": "appliedenergistics2:part/monitor_back", + "front": "ae2fc:blocks/interface" + }, + "elements": [ + { + "from": [ + 2, + 2, + 7 + ], + "to": [ + 14, + 14, + 9 + ], + "faces": { + "north": { + "texture": "#front" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sides" + }, + "west": { + "texture": "#sides" + }, + "up": { + "texture": "#sides" + }, + "down": { + "texture": "#sides" + } + } + }, + { + "from": [ + 5, + 5, + 10 + ], + "to": [ + 11, + 11, + 11 + ], + "faces": { + "north": { + "texture": "#front" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sides" + }, + "west": { + "texture": "#sides" + }, + "up": { + "texture": "#sides" + }, + "down": { + "texture": "#sides" + } + } + }, + { + "from": [ + 5, + 5, + 9 + ], + "to": [ + 11, + 11, + 10 + ], + "faces": { + "north": { + "texture": "#front" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sides" + }, + "west": { + "texture": "#sides" + }, + "up": { + "texture": "#sides" + }, + "down": { + "texture": "#sides" + } + } + } + ] +} diff --git a/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json new file mode 100644 index 000000000..dced00638 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json @@ -0,0 +1,9 @@ +{ + "parent": "appliedenergistics2:item/part/display", + "textures": { + "front": "appliedenergistics2:items/part/expanded_processing_pattern_terminal", + "front_bright": "ae2fc:parts/pattern_terminal_ex_bright", + "front_medium": "ae2fc:parts/pattern_terminal_ex_medium", + "front_dark": "ae2fc:parts/pattern_terminal_ex_dark" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json new file mode 100644 index 000000000..6c08834b5 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json @@ -0,0 +1,9 @@ +{ + "parent": "appliedenergistics2:item/display_base", + "textures": { + "front": "appliedenergistics2:part/pattern_terminal", + "front_bright": "ae2fc:parts/pattern_terminal_bright", + "front_medium": "ae2fc:parts/pattern_terminal_medium", + "front_dark": "ae2fc:parts/pattern_terminal_dark" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json new file mode 100644 index 000000000..64da5c376 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json @@ -0,0 +1,8 @@ +{ + "parent": "appliedenergistics2:part/display_off", + "textures": { + "lightsBright": "ae2fc:parts/pattern_terminal_ex_bright", + "lightsMedium": "ae2fc:parts/pattern_terminal_ex_medium", + "lightsDark": "ae2fc:parts/pattern_terminal_ex_dark" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json new file mode 100644 index 000000000..7f91dad83 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json @@ -0,0 +1,76 @@ +{ + "ae2_uvl_marker": true, + "textures": { + "lightsBright": "ae2fc:parts/pattern_terminal_ex_bright", + "lightsMedium": "ae2fc:parts/pattern_terminal_ex_medium", + "lightsDark": "ae2fc:parts/pattern_terminal_ex_dark" + }, + "elements": [ + { + "from": [ + 2, + 2, + 0 + ], + "to": [ + 14, + 14, + 2 + ], + "faces": { + "north": { + "texture": "#lightsBright", + "tintindex": 3, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + } + } + }, + { + "from": [ + 2, + 2, + 0 + ], + "to": [ + 14, + 14, + 2 + ], + "faces": { + "north": { + "texture": "#lightsMedium", + "tintindex": 2, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + } + } + }, + { + "from": [ + 2, + 2, + 0 + ], + "to": [ + 14, + 14, + 2 + ], + "faces": { + "north": { + "texture": "#lightsDark", + "tintindex": 1, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json new file mode 100644 index 000000000..217560bcb --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json @@ -0,0 +1,8 @@ +{ + "parent": "appliedenergistics2:part/display_off", + "textures": { + "lightsBright": "ae2fc:parts/pattern_terminal_bright", + "lightsMedium": "ae2fc:parts/pattern_terminal_medium", + "lightsDark": "ae2fc:parts/pattern_terminal_dark" + } +} diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json new file mode 100644 index 000000000..f23c08f8b --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json @@ -0,0 +1,8 @@ +{ + "parent": "appliedenergistics2:part/pattern_terminal_on", + "textures": { + "lightsBright": "ae2fc:parts/pattern_terminal_bright", + "lightsMedium": "ae2fc:parts/pattern_terminal_medium", + "lightsDark": "ae2fc:parts/pattern_terminal_dark" + } +} diff --git a/src/main/resources/assets/ae2fc/models/part/interface_base.json b/src/main/resources/assets/ae2fc/models/part/interface_base.json new file mode 100644 index 000000000..c3eb02ab5 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_base.json @@ -0,0 +1,104 @@ +{ + "textures": { + "sides": "appliedenergistics2:part/export_bus_sides", + "sidesStatus": "appliedenergistics2:part/monitor_sides_status", + "back": "appliedenergistics2:part/monitor_back", + "front": "ae2fc:blocks/interface", + "particle": "appliedenergistics2:part/monitor_back" + }, + "elements": [ + { + "from": [ + 2, + 2, + 0 + ], + "to": [ + 14, + 14, + 2 + ], + "faces": { + "down": { + "texture": "#sides" + }, + "up": { + "texture": "#sides" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sides" + }, + "north": { + "texture": "#front" + }, + "west": { + "texture": "#sides" + } + } + }, + { + "from": [ + 5, + 5, + 3 + ], + "to": [ + 11, + 11, + 4 + ], + "faces": { + "down": { + "texture": "#sides" + }, + "up": { + "texture": "#sides" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sides" + }, + "north": { + "texture": "#front" + }, + "west": { + "texture": "#sides" + } + } + }, + { + "from": [ + 5, + 5, + 2 + ], + "to": [ + 11, + 11, + 3 + ], + "faces": { + "down": { + "texture": "#sidesStatus" + }, + "up": { + "texture": "#sidesStatus" + }, + "south": { + "texture": "#back" + }, + "east": { + "texture": "#sidesStatus" + }, + "west": { + "texture": "#sidesStatus" + } + } + } + ] +} diff --git a/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json b/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json new file mode 100644 index 000000000..77bbb93c2 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json @@ -0,0 +1,54 @@ +{ + "ae2_uvl_marker": true, + "textures": { + "indicator": "appliedenergistics2:part/monitor_sides_status_has_channel" + }, + "elements": [ + { + "from": [ + 5, + 5, + 2 + ], + "to": [ + 11, + 11, + 3 + ], + "faces": { + "down": { + "texture": "#indicator", + "tintindex": 1, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "up": { + "texture": "#indicator", + "tintindex": 1, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "east": { + "texture": "#indicator", + "tintindex": 1, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "west": { + "texture": "#indicator", + "tintindex": 1, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + } + } + } + ] +} diff --git a/src/main/resources/assets/ae2fc/models/part/interface_off.json b/src/main/resources/assets/ae2fc/models/part/interface_off.json new file mode 100644 index 000000000..433a4a233 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_off.json @@ -0,0 +1,33 @@ +{ + "textures": { + "indicator": "appliedenergistics2:part/monitor_sides_status_off" + }, + "elements": [ + { + "from": [ + 5, + 5, + 2 + ], + "to": [ + 11, + 11, + 3 + ], + "faces": { + "down": { + "texture": "#indicator" + }, + "up": { + "texture": "#indicator" + }, + "east": { + "texture": "#indicator" + }, + "west": { + "texture": "#indicator" + } + } + } + ] +} diff --git a/src/main/resources/assets/ae2fc/models/part/interface_on.json b/src/main/resources/assets/ae2fc/models/part/interface_on.json new file mode 100644 index 000000000..187259ceb --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_on.json @@ -0,0 +1,54 @@ +{ + "ae2_uvl_marker": true, + "textures": { + "indicator": "appliedenergistics2:part/monitor_sides_status_on" + }, + "elements": [ + { + "from": [ + 5, + 5, + 2 + ], + "to": [ + 11, + 11, + 3 + ], + "faces": { + "down": { + "texture": "#indicator", + "tintindex": 3, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "up": { + "texture": "#indicator", + "tintindex": 3, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "east": { + "texture": "#indicator", + "tintindex": 3, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + }, + "west": { + "texture": "#indicator", + "tintindex": 3, + "uvlightmap": { + "sky": 0.007, + "block": 0.007 + } + } + } + } + ] +} diff --git a/src/main/resources/assets/ae2fc/textures/blocks/burette_side.png b/src/main/resources/assets/ae2fc/textures/blocks/burette_side.png new file mode 100644 index 0000000000000000000000000000000000000000..f633ef09a27bdb4b135d4e6dce686d79f3bba026 GIT binary patch literal 933 zcmV;W16urvP)EX>4Tx04R}tkv&MmKpe$iQ?()$K_iGbWT;LSL`4J_sbUcs^>vAkg~6=8oa8#qAw;o=7~+tipo|R^VIxedPKt#D?MFQPgRVbLE`?kh zVC0xb2^wVA5B>+gyR~u?3_KaKDZ7%NrjW}4?`QN)8KCbL=vuA!*4W4C1CXMwmTrK9 zLtrFN*=ru}?riPt-!qN=egMDLa>lft54HdR00v@9M??UN0E+;NsDG3-00009a7bBm z001r{001r{0eGc9b^rhX2XskIMF->t6cht8>F>|80000PbVXQnLvL+uWo~o;Lvm$d zbY)~9cWHEJAV*0}P*;Ht7XSbOdr3q=R5;6}lRb`_F%X6y|A9b)1JH2-5-Gb?(xynd zG+cw~qu>OHQ_!bKoh~cwN>PgB1o=+mj;lteq{_(kJ_q#_v2c^V%#H3*i~d z11y(IhQlGwIRKnqxkW!-W zd-}eAB(T=vy~lg60=wO=L0zxcDwK03r38Qlnk?n=;c(#dkMEpLCyvJ>+wE2oVe(v+Ym8)Ni5pU(*)5JKSFpWiB6VoF?7LM=9(P8p3xeEs#4cdu@q zBxnd~ywWgZ43o)(_cyo17};z#)X5rwdUT~mfWcru2;m8S9b=1e%2zkdImf4m`^H?} z`^IQmf{2iFZYJ^Z?h7AYzfl9#eJV+OnQ2N%cM+lMI^Mo`2|(9%I`{vbsRG5C1r1=e zS|K7_uUE||BBYemi5?)v$mMb&#@Lvo9#;{l-voep?sNVNVd2WavbQ0-00000NkvXX Hu0mjf*G-)J literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/burette_top.png b/src/main/resources/assets/ae2fc/textures/blocks/burette_top.png new file mode 100644 index 0000000000000000000000000000000000000000..0cff59c3deff978bbecffb7b7982fbce302ca8a3 GIT binary patch literal 2128 zcmV-W2(R~vP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Y=mMkX-{_iTX1VIs9j?Z(lgDgK-C3d$pnas^i z+ocpJARlO%{@0(w{2@OCAB`uDA%$S&=aWyEg^2e4<*NtluYSE)B|i9O3s6{2Mt8r) zu=rj0DD!dg>(Y~jPj8A+UxM-}$V(B%Wk4-1Lk?~Owk<_@DRQ_h`}@5t%OOR6-$K(J z#KnpCV`k?aW1s-+rT1u=LlQco^(=8~)6r2e&2?06J?Fd8sVF{Sci^n_=$4o6ezH74 zdZA?=I4?B0B)xOtimx>SnYbXHrxn|7cQij@vr+l_XMVD+l@1?pZ)&B1c?ga)2z~M0 zfnM0-#W$w_=xewyOy4|WzYXJuM?G&F_8r#wjM3s4{oY;nLAD&$rgSG@E+|JUGYPI` zK^n_LLi5#F1zWnS0Re!1~L^vlz}Yy*Jge)>AN<@tR(=mY6fnAah9RD zF_D|SOcO+4tf0~rU~QiXxqx*7AzfjPvcYW2t)dXTa?4#nJDEo^-o*{v?)6iV7uzJx zfD;I807nqOG9m*=Afo`3!A5i*iy#0**u+WnQfPvW6oHsFvWDTBi-b2{PDw$6q)b_` zk`LHB_##rkWcz*QAbHXxw5; zEqCaWO5JxdJ#_A|r=Ex8+Hk`U8e#B=BaOUKTc|$TFQ5hsHN23T^LV2sT;_W;SH6gn ziJ2gV9t3fo2uMQX#LPzU9w%}WGn-JVf>4~KiA@8WA)q(TcR(7jv3nu+OSqZfzr_uI zikz9y{U&l3Zl=>4ZeO6*=Cxff#O^AbF||&rZ(z?DMx(^YV8Wj2BxbD;`|VP6|q|=Uu>)!C|eqLmIie(tBOEpQt(}C zGO2~9?cTm~ES-)O^U|BovuaokF+P>SQH^G&<{R){bq?l7ilwhp%-oJa`^K-WY&_1t zw2uv=>&|1s?ZO#cR8wgWpCReiUMUuL&1s4GwsG|5*?x@Xp9HUp9~ug;7}0jSlDcMe zOCx}8psNno&YMMNpXWB9IV6wU>2aV_c)1f^tMBVbZfSL=YCQ40PtIfU^L{b!l5yng zKHut@O|FJxmqxtV=+gWB^5vyw|KSd?J4TP#l=H&gs6hEjtNEWkMt(_=ldq*@{suS4 zdwn9(V5mp;}~`)iwrbx@{(+5hXocD=ER*+TVbK+5xE=c^yb*0X4obxUVJX17Ni8*4C zSjxAs(!#7@YQ&SoAyv~UUr5(k<-EmNE0$T~p8SQutiGJ&I?W+Ov4|Mrkf5N94HRJ` zOsh_cg#_(KJp6;MKTa-%TpM8Im`4d3WY-V=2fw?uauehAq+kSSe{r0TVIZ&z)GChi zee5{36TtrrTeSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00A^fL_t(I%cYY+u0kOU zMGpm^#=&zbF2{v9a2M{u9ZH;NpzsGjtuWAuB!uuDJ-y|Ya6BG6Ka2!$I-S^Vx5O9$ zh%q7}0JPR%M10>`<956K12&rt`~BVnZ4DvNd#CqK2mt_9rIbP`<*h(ONGXw0 zngAl=0Kh@5HB|Kt7_(fj*RM67&u1^q0i#rabzL1OB9u}P5gw1nRN4d>SAd+e2gVpV z91ecQCV-43*sudRXYTjA-};zjQeYQp)|FBm+I#2qdO3XjKDjV@ud1}xSl1O1VObXc zXZK=Ck~NM^fZcA#vMi`7F-B5K?~Ex?Yjt26rCFUh=dV_V5Ik8jPfhmaa`9}VT^69K zQ)fEBTGOO`0`%Td)wvUEtvsJkYOPbyXqN%THvtfSea?S4^VXP}4m6zr0000DJ@` literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_assembler.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_assembler.png new file mode 100644 index 0000000000000000000000000000000000000000..a0859beb0e122237a3aa516db4a40e0c9f8998da GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z_$#98Qbry z%m)femAFQf1m~xflqVLYG6W=M=9TFAxrQi|8S9zq85+8Ob}|R5+Ue=y7-Hc+cTy~0 zlYu}>wcw*}*ToavI_<+hgzc+R6JFnT-^f>St>V>_%g($zR~B{P?d{O}l}EqsnCTvQ zbDi|@$A*XZI^^^uaF_F~o%H;}vbooEralv@DG5ksoaOkYpmF7^Ht~;B8B)qr+B}{F zyb_vtK*mCBjnGHF4PNd0`<66%-C)q$a9`$C{A%8U!Wm`}3`{APr~4Uao(;-ermTA5 zoPe})PQr8Pp1gyf#f6kiI9_pSEpIOUeXgG2=MFh_{pUX0fu3OSboFyt=akR{0L0dU Apa1{> literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png new file mode 100644 index 0000000000000000000000000000000000000000..c0da91e8ee642842fa76249c0b9c1196a83f398a GIT binary patch literal 527 zcmV+q0`UEbP)^Qon24KotHYBrIiAtr!t7F-XGDwAjNzGi6BdPz<3< zmuARd$lSlAbGHud(xntkhm!sU2VEM6Ll(%08sW%4Hs1*i>F{FNX{UZ1$KAW{y?5`u zPdj<>9KeCM4*Mfb_JddJiwOYk-44~id3n4nk zJ=b-K@ZtSCgJOUG`Vl}joBK~0V@yd2KnMZg+m}yXqYj{VesSRK8`$7S+5%cXug7={ z%VD=EPMZl`4f;ton*%Mi90O>!!it?Hky?%|C&*^=JkJ4WgTa8_K3_kuRk;)1M4ChZ zUY`YI(Up`+O3wK%yC_kV0HDfk!gjk2K(}oB_jLN~=%|WXlqkykqP#D5QYk5i!yy12 z{|Q}5>HB`C+pSDB6E#z-nOe=<@_yai&@^?+wy|k>nnada`8+C`G4_yQy_key7>~H+ z1eH&Lifuq)(ekHkvX$9hERdl=DwZA4ek*V+P%CJo-i%qU2uc-NLJbbvEBtMlvooa8syHm rFmy4V(CmqDD2!X6V{L3;z`(HOjloe4?$BnSqZmA0{an^LB{Ts5w~|6+ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.png new file mode 100644 index 0000000000000000000000000000000000000000..88304b9371a28c489dd723613cfda7d951bb6f82 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`2A(dCAr`&KQ@VVApLbwPV9YGl z(ojC#QY*YmVur>>Sr#{53Dblt3>*t|tc?u}7#OS{vE3~Hm*ND}%i!ti=d#Wzp$PyF CEgR$j literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..623fb299b1a5ced003836b7eca76f9ef190a7e01 GIT binary patch literal 2450 zcmV;D32pX?P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Yuk|ZY#{pS=u0uqSjIII!AfsgOUnqAf1o}L(2 zMkXo{LcAvlRQu=euKvQ0$vb7m2N#{w_}OS9CkR2WUz}gkc7FFCeT0{Obq5$-2CZ3t zecJj7{qjsWYfygeS7DEE*oKbfZP1LB8AAQ&B>H*vlih}5TY`(he{?nTYRgufuIx9& z>B}&-hVWl~?64jvRl!1rxfwEge#6o+>rHsw{L;`f2C^8ZX4lyg0D0No1yFwk=mq2> zsdx0R^aehs-EZEobQ>cGUk;Fb4gQ4q!@}lz#IFN3YX50F`|Z7k-rL$;vJQy49Z^~x zbjBPgW|%T_n#1Gq3S>W@h$rZz3n;I;=!~aOBnB&8k)=wBI`(u$g_q zc#d2KSH_NqI(rjMH37^OZVL2S!`By@>&CfWp@5YuL6r%{2vfd&)PD{B&ZBxS10vGl zVgD$24%t=u)(DBGLeOz*fNu613f{zQiznV z+W;WK+5%_DfC25u2szT8*%JZB0D=-cXJa{o0TST~C)pa~Sa}XS*2Wx9Q{09^6EeK$f+F`0+L%hLRYuBI3kLP!c6M~qL5r-JY)wn%Zsmr!C!m8+>J z6V+8;Lya{xQVo@&*(S|3-$IKmb;MdX-FE4&`yP7i>9V$2{q+0>YqVM8$<)yEmo@Y% z?~e%@t!LlRy#os;7|`{jpoWAi$6oKW zK0UZfKd_l)qv*I6wzv!}HKYh8OucMb%UYenUR0lU?~6Bt^8&Pmv$R6MED%fQyi78( ze($5p2?%ayOXdi2$wcW;7aaE;I0tWj^Y7IyH6@WlSaB!RuTyq8l@SX$p6J=R0S`D$ z(3Rk*dLVPYw1tCD;~Mb}*4b6-%TLtLs%I9c7e@CwxXbJfQf%T+!aZi5@RBtctU>|4 zsUYb=kB{r7`Nc=BWFlf_{Yjx*N=lm^F?L>UPdiV$2`zsPzgsYhIgr`T55?qhJ<+|%` zROP4a^~1t&S6zQUMnL^R1a!O8v3aVF)aLOPKU|T*@@P29UjqB!ddIcO{g}$PcJ3$| zd3@R1HKUvWyPl;*h?Vtt%*knPpCCp@7BW2p<-Su#L#s7UZi(LADJI6MaqQ-1Yb}0se9%m z#^z)o8v9*@o*JhPlPs6?huwb4qrNoOM{g&@49*=#<4`^UcgnK!xBOqP$(i@MS9aM` zqweJ!>rct~;$W%ujS?Hoj+_$IbI#g$qv*XoQFzjOtZDBlJNK*)vyK&u%IQaerqU`ljNtO$70004mX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&Mm zKpe$i(`rR34t5Z6$WS|35WjGgDi*;)X)CnqU~=h)(4-+rad8w}3l4rPRvlcNb#-tR z1i=pwCr2km7b)?7NufoI2gm(*ckglc4iFj@rka5%KKJM7Qwk;nd?N8I(+!Jwop^fF(mC%Fhgn%th|h^f4Z0xl zBiCh@-#C{X7Itp zhU+wkk-#F7NI`^*8a7aYg&3_GDJIgiANTMNIsO#6WO8kQkz*cJsE`~#_#gc4)+|np zyGfxq(Dh>5A0t3;7iiXP`}^3ong4(~Q-+=GV_339x+*ltOVeKcjRRjngP4S&7$Y&pBS3J- z!vJ{LwlvQ2{wha^q)m1KxX@rsJ@02f$)xsJo;lgy@xv>H`4H<8P{o z>3h&Lkw4GR1K?ycjV|^`D3rLnB_d}12G1&Oruoj7fk~!FLKH07*qoM6N<$g1;`LbpQYW literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..38ca52d36ed9d548b1e34560913819177c8ab72b GIT binary patch literal 2028 zcmV-P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+ND=*mh319{qHJf34$WJ9OK71vx8awpb@*us_N;U z7dw%l!j%iA+O&WE?&dGK5HJ`|9777h%H@+!m_>;8zVdv6_4B!2tO`$hvjC3W{MNbcgQd9a^Y<73Ckc}2$u#O z*(Fz96#9##iW`a0yIELZ`c2bcfhe9U$W#O|2D0QooB7G4?^^GpmH^DE8Mq0?QHBCz zFgKnuO&Ec`f=V;M+MW|~5$l9PnqiKz!ECSF#6|FxTW%8VWFAR>7dLRd*AGKpe3O6y zCluHKjv#<#LQfw(rZhGEV{!dow=q%c8JrYzhf zmxV0vN%Dt%lpM`sAEpPZ! z=FFh(51E_1nMM!1yXjfW;RCIdx=F8?ZxdCb* zL2-Ch;8M`jhK-D<1If5U;8r#d4v}&l_5)AA(^b^oY)E=NSZQV`(h>$MI*N-A}DYBx2#?dFH(y913LjbK> z+f0%FEMHMexO67j;P9r)DV)d8 zknY`lpVlvDC%q}Ndu#D4IPnF|uD29+3V-lvEPgnBKF8)nH&0#jzS4i+n?E(M;v)YY z$v?&BU-qfh^^aT+jQ{`vglR)VP)S2WAaHVTW@&6?004NLeUUv#!$2IxU(;$uDh_rK zamY|RSrET)lqwd%LTM|s>R@u|htQ-UNpW!$Tni3+H=u-+N1AHR!EYl5(c%689)6zNb6Ngz@Qi#uqM-93l@gvt|m)|&-92R(H z*vO{mh{MEUse|PXW@SSqo+OSasz&)j&Si!37H73mW37Ag7lsPja)#?PhmpV{l1M>> zj2bpjfrS{Y8Yw2yv>*5I4>|r6xny!}fRSS!Rj800KlmT~?$#_$jJrvpIMDTC+aDu9 za2IISZTtJ!wwot_{~5T_+Wu+-nE52V-qs>VK>s#yaoyJBJ>YT&7<|$tLvkcPO`%u> z-p}Zp^1#3?(7WpP*4oGE1CXVzmTrK9Lm*nB>@|;f_jLC5@0nJAKZ_G`v2XC#M*si- z24YJ`L;!LCasYCZFQone000SaNLh0L04^f{04^f|c%?sf00007bV*G`2jm6<1S%%p zCodKN000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}00030Nkl)?y!0Nb|le!np@0L%-Az;mYJAVL>OZ*#y~^>=)H5EC+B%y3RD$$$K7uLsyYFH z2^10Z-Y zd6@F*VcMsfN~oD!J748~DzV0R4o>c>qAD;m#u&Iet+lJM8IafXpX}cItxq|%o_E@N zpY~HjD^YFk1jx}$X-|M;Of|PjY^`w|2d%X~tWSXYCIIBy=llbC7s;Wh`|m>l0000< KMNUMnLSTXoJ+w;z literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png new file mode 100644 index 0000000000000000000000000000000000000000..b2b560145610c64d4124845fc61538cc344f0ce9 GIT binary patch literal 2159 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 FV1iAQccuUU literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png b/src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png new file mode 100644 index 0000000000000000000000000000000000000000..8233dcdcb2c48a9d6e3dedfcb83ac3ff5f524bdc GIT binary patch literal 6180 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 BV5|TD literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface.png b/src/main/resources/assets/ae2fc/textures/blocks/interface.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c2989370c4a8d8249fd213fd74ba4f3fe019e7 GIT binary patch literal 546 zcmV+-0^R+IP)aE*u%3k$IpEd&cw9VybL%3qMb zz)HI-q7pd^FF|jA;4OQ*^36KyUAQg-kN0NYeDBT7H{;yh*(@E$!LQ#DD6Z?mU%rab zXoN$=>hxF&qw|l4Ti!Ai(bKJF$$% zW7+rj-ohs7A5jhuKA$lM9bP&g}+z}jxA>O}vg}`r$?f%23Al4G3KOdj2 zSAU)t0Q`STg|-{O2-sRX5e%cZdd;6_#k%F3o=+Vt@^!l1ZmA(2eJsS-^eV8m)%$s# z%bsS{Blm6_L{TIu8diHS7>EHgKNDQIzASx6@90Ymv4&K6o>v!a7F=gP^N6Gn$ATf& z;F4n4B+7GLusnyECbiD9)}__{ooipENmj4l zta;X)8TRnx6T*P(ID#S8>Qp^=^lWaJ$TO*;aIl#3^Ovth*I{afKAi6MhIB_1rxFq) k30Bq?jx%#sAA$kkFZ&t%#Q6GhrT_o{07*qoM6N<$g1@o_ZvX%Q literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png new file mode 100644 index 0000000000000000000000000000000000000000..99ce20c0e5c88ab6ecc5e47a5257563da29b5563 GIT binary patch literal 545 zcmV++0^a?JP)zDKv!~9?BIdM67JI6z{r-)nO+JX+(sxun=p}La@*(SlC+m3+@+K zX_w+uPIATN4$W>Bv+V7YZ`N58N$0@hy_q-Pn|U+en44SIyfFqpex{%VL4fG=IZP%K zv|25(&F6D;yIstVzr%YE`p3~JxpOq!yZ=~}PNyULa5%(xJcde96o}&(J3BAMGM!Fk zfBWVI+${ek%KLY(QB@VPS&p@h%jorb$g)iC2u_j&ubw|e9JR%^edhs)bp&bT-TjR^ z=5Yle`m+5@hVfIe18F^7$;Ft9B#9)=L>G%6yQ~dC;5@S28!Oqq1mt`q? zUeu4=x?+*0seIA0+N04(3|NRvuCFkOA8|h2T^za)Tc`a#?VMnX`{;x#sdJe3$H518gvg*e9uR@Zu7cMn@ z);}}t@!lsSG1+MZL#)%Ox_$TI(lD{lY!!ur#XmoK@=SCcrcM&U^f#{xJJKwdl$c3; jW_{&2^Uvx-FaZ1p9Mk=^>MbL800000NkvXXu0mjfj5G%w literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..80a60336b7f8dafe810ae175c029c95c57c67162 GIT binary patch literal 575 zcmV-F0>J%=P)^QonE1Fc^JytVDOVBf$kxK`T&)3<$A+RKbXV?GO`U zLm*{A2&oJVh#A3v#6YJL11l4M0ecZFbj#4AYDv-kD3CazjuM-}y`$>JH~I7T{QUg< z$juv92qC|I7XUoZ=Y{;2i+WT6_P&698ct0vHSiS(X6+ zP}j8(Vs-TufIQCuyj^?A%4#3L`*&}QF{-RiEnkS^SSbZS2qC3>{o!W5J}j$! zW{h#%AGenQV9x*?;5aA)oWlfHmo~#NwAM1Wt;%YK0P}2TVFkdSAL~s^jsQr%-*;Dk z_-p`lee0cAnICS)>s3tv+`2@IqEJeCyXn z)|xp7l~PVO`S}HaJ$7-vK9Ho8@B2XzQ2b|6t+n&p^-a?p9?x4LL|xZfYZgV3do*C0 zmiV4+n#MbXc1B4l-TzQE-A>vtQO9mcYb3QjcoI)6dWLVo*QGB%ix`RkLKY^ z)71BU09lsBas2q{bIigQN>2LMF9Rr4MJct`ir&(Zc<19SHwq;O@D~Vq^o#DX2yp-a N002ovPDHLkV1ku=`Zxdp literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/blocks/large_ingredient_buffer.png b/src/main/resources/assets/ae2fc/textures/blocks/large_ingredient_buffer.png new file mode 100644 index 0000000000000000000000000000000000000000..31d8cd0b8184ac3ac16824522890ad5be55f0532 GIT binary patch literal 383 zcmV-_0f7FAP)(Qqjm+#I%IkKc1|?VHKxGG2fcgu72;0~DUk$lAyKS%n@ba7$E@ZdM_Bf7M$K9sJIkgV3Xj(8>730NS zHOOwMW^q?4S|l@q-7orw*yNTO|g?mv*kqTFuHz11m|s*0upo_E`W d?*I7!_yUA{ro$&W+HwE@002ovPDHLkV1m!Avj6}9 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/items/dense_craft_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/dense_craft_encoded_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..86bc00a2fe966ad4b68ee060b5e99ec6b23ae6cb GIT binary patch literal 379 zcmV->0fhdEP)QpIAoiebHfq!7?h7>V$&4}O&s%|I? z5)v|cm)h8_!$~LEclYj|pX~?$ilRWs)|<6rIJ?TCP`;`vjDpBLxL(zu>qa39HXz{R z=-KSK9%?cfnhfkPTb@d+x!(b(kuP%PA7|u)?aAZMSMmRtOl+_{nZTEaGs)fFz2oQP zCdfn>v~8=V$I~I54ve+Ln0T6|s%aXPTqX+FZ?dM3&mYD0o;lWVP0WD?JY(`bH+#~i zYh%vV$Y`+r+L{1$$QQX3_GgA@LNIf)WnZT;!hnJhG{nT8%vwi8-NXiQT3MFj{p~o- z%v#pmf4DMtx Z0Kc41xeVpgni~KB002ovPDHLkV1i~OvfBUv literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_drop.png b/src/main/resources/assets/ae2fc/textures/items/fluid_drop.png new file mode 100644 index 0000000000000000000000000000000000000000..32ae84ab4b9a1ea82bc5381046b30a1f97c75ca9 GIT binary patch literal 1482 zcmV;*1vUDKP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U-_ZvfLmH{O1%o0+JAd<6xGm+#ttKgO^#}OY*kL zPh!R(3n8`D0*~uIz7O{e7ki&vP)jk#=y8P{G8YKR?^j+u`LO!q!Joo|-dz(6m0*

>d<`EHy)RpFh7c73;U2ICX+LFbyz)mslTZo*s!vRu+ z*a|QtV8BNbrAXeGjR<*6AgBO2OW+L*kSaHTnSU~19Kl4UDNQb?L&vXoLzC36;> zftq8soN~^kuqy~u;H$vCK(p~CTiA4qn{8>!Td63Y8miVYa67i-Wuv(p95<3uNC zV2lHSahnXFpm}oU6NSK&xyhN&j7^~okUF_>jV5EDFmz%aba8iM?oGToO>g3j|HPb| z)O`hW4%EHm?G@JA{LaICcd74&Ib?rnI4p7Gq}ho|J0_yxSVcUDb{S7vg%ucW_#=!Q)6LKfR& zJ>NjM&+oE*bZ@L{dBN#ZboVJ3{~i5%j%?t+Np?R0bQ^OC3uEX>4Tx0C=2zkv&MmKpe$iTT4YM4i+pTlA$_T5G~>;RV;#q(pG5I!Q|2}Xws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;lcSTOir6bq^0;@1i`*{oJ3UUoIF7@Cd|nOw&!`4dR(i zQ{%i(9AafrCO#*g(CLE2k6f1=e&bxS*~>FSdNw^z93mD1~ z4$GXkI4jjUYu}Teq!>uke%!@BZ2ME>lE_s7 zBgX=2P$1fV@IUz7tx=qwa*~2^!2e=dA7emZ7icvs>-*TUS|@<-8MsnA{#p~5{Up87 z(IQ8{z&3Dk-O;2y;Bp5DKWU;a+LDi^w^#(;&*+=-z~C*=x90TL-pAn`u^?e6X0GwuF<0NFlr(3}*PEC2ui24YJ`L;(K){{a7>y{D4^000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2jm4F7c~-b##-Y5002@+L_t(I%VS^|1$aSVVBmi@ zH@E*}YrcH>^8ektcjGmL1)m{eVqy#w+5Yn7%l|K5zQpSaCVaLN;{u!pyn6Ktg(exe kxw#P)hy)EGTk{A40Hu&LM7JIYO8@`>07*qoM6N<$f--Tqu>b%7 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/fluid_encoded_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..f4210af1ebc9e0e5282231697379e4d89f4ca161 GIT binary patch literal 392 zcmV;30eAk1P)-Z8UWK69uWZ-Te)HdRzpw>z^1XYql2Nmq5`KOU;}Vz z1c6+EdWOj#8gUxJ*xc0gKRqLZ;lqay7#cy~@tcPXvLf<04PgQiSFc`W5D*XmODg@* z0&50o1YwvVATgNXAeX{i%>;7_*pRbQVD{EGt1$wWE8{2J1t7~nu1-!&{Lcas`1J83 z1Iq;j)d&I`H*7#eW4e3xe|ZTdupwObObpuE+Hi&F8bRRA>(>lNj~+!$2QWjd5{vhWm~!22IU7i>vP+!#e(4cIotmJ;f}n;5|4rs)8Dgb=6%AcTa;UqI;;eu4<_8OITZVfcB_HxfcX-}j-uy?@kNXFDSl-!uZM zs>%XQ)1(2VR2JB_Ee$xAUx~V|)4=5`ob_+9)~11FS+c-9&ru!i0mc}*t_x4qwr%KU zj0yF=?;xf8m@)!5=h+F)3BY45-E+H&0smM16#%#ch?+v;2r#g-00000NkvXXu0mjf D@z!Y2 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_packet_mask.png b/src/main/resources/assets/ae2fc/textures/items/fluid_packet_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed80022c2be7847963cb9b9da8b9b8e606498cb GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`TAnVBAr`&K5(Ni1iVyq;g2|60 u+}zwY#Ho4J8e|<2AV-$1O-jP;mf>ARfKaBL}ETFKtgy2~He(1gTQh3L2@$@OvLt3Vn!LvOS*p{C)p> zV{u__-`IgMNs{(e=PNaMKbPHmcf<4Goox!QC*%1yQb~H|i|pRD=hLO@lJvy>puQ}Y z-Pef6LzVix!PH(D16q;}AMM4|TVVnL zOSXgQWu(!Z@dm(qWh&U|NsH zny%u^r9RO2a$LNcbo%1Hr?CzTStL@x>Nz%UbHUR#e*krUe2)Nx)^&4@``QwQxkM^v zPJn(RD>P>yR^}M9ir}xJ)n}Kwa(!&9(nPbxe#hG0l6RP%u6f^pRlSBTb%! z6x`@J=4Mp^;lUkXtAM`VNYo`%4WIHG@3pJp?}F#r2nA1p3v=biI7$=d-VmT|!Sgcdp!e_m5k1bDO_^wsrNriOW}76VHGB;YYQ* z-|znG&12`EC7Xx7bIyKqcd++|RwYlrYh1pXdZ@+ME`@}oH{CSc8{rQ-* o@fM!E_Wt9v1@2d_e%>?5>VD>g8h*Eiqq{oAW7FHVeI8IvSwqP$dE z5%c9_j1G(cwI7FAOqar?2eBkgzD~xukyon+l5}z0tFH5P=N=-=SFy`#RPFeoKuglh zY$wEIgK|)#4KJ|d?;k(Qz;i8mE$2Wdv}w~@+KK4u&T^IPY!K6xXYYWS4iW@Du9BPw=YZVEV;>fh%~L;ZmaE#%A$s*o2IEjLo*CTAe4AJ;JBj% z@$`V9L}L9K;+4gqBN|ES#cxo{New=R%@G zg}PK%ayVO5YNSXNT+7!B6y{(io4zkGvYCW_@ZSB;_xB@Dlpb!KnI|d8Bxlf)7mH|a z_e*A#KJKqt9_XeC!X!z5OD0L&sVn!7y)%2DN84^vL3&C?^k6Zo^EQs?Ttj&4ANi&U z)wQHddsb+_8`!-&Qqlvs=o85@ZBSy9wm2Gyz7~lFcK++TebM}sa%rwwd_jI59?lM3 z-`$fBKEU0n!#8i|3cD{yhbsHypWsw(cWn6imuDBGCzB&rGY`M{N!u(hESKKTKYIEL DnAW@{ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_bright.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_bright.png new file mode 100644 index 0000000000000000000000000000000000000000..45985f13df1fe4427fa2dca45001965bc5ed608d GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`KAtX)Ar`%FCm8ZEDDW^H{PAD^ z@uMW&tL#&jaxN5Sib|N{Flkk!hn)T8r#9v1@2d_e%>?5>VD>g8h*Eiqq{oAW7FHVeI8IvSwqP$dE z5%c9_j1G(cwI7FAOqar?2eBkgzD~xukyon+l5}z0tFH5P=N=-=SFy`#RPFeoKuglh zY$wEIgK|)#4KJ|d?;k(Qz;i8mE$2Wdv}w~@+KK4u&T^IPY!K6xXYYWS4iW@Du9BPw=YZVEV;>fh%~L;ZmaE#%A$s*o2IEjLo*CTAe4AJ;JBj% z@$`V9L}L9K;+4gqBN|ES#cxo{New=R%@G zg}PK%ayVO5YNSXNT+7!B6y{(io4zkGvYCW_@ZSB;_xB@Dlpb!KnI|d8Bxlf)7mH|a z_e*A#KJKqt9_XeC!X!z5OD0L&sVn!7y)%2DN84^vL3&C?^k6Zo^EQs?Ttj&4ANi&U z)wQHddsb+_8`!-&Qqlvs=o85@ZBSy9wm2Gyz7~lFcK++TebM}sa%rwwd_jI59?lM3 z-`$fBKEU0n!#8i|3cD{yhbsHypWsw(cWn6imuDBGCzB&rGY`M{N!u(hESKKTKYIEL DnAW@{ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.png new file mode 100644 index 0000000000000000000000000000000000000000..e881def0c61fafd91aeb80736d3189e6c579ee6b GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`QJyZ2Ar`$?CmrNsP~dQm{qlc* z&h)0JT}^&(g*x{=JMmAvg1NkWt7t=N+RiTzc~9k^nJ~*Vm2Jla#zoHrRxucL3qP3B v%+jN9(=mjbaY3-tskK*q&da|npBc&Qc1=XS|7P87pe+oZu6{1-oD!M<<&rj2 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png new file mode 100644 index 0000000000000000000000000000000000000000..a9271aa88a68991712c9cac54f5b085063f3190d GIT binary patch literal 1506 zcmbVMON}p;hK z^0B$uS7!x5n5$LGD}3IYjG0~h|JuDR#HW|T>Zw=|=D$kDj+ZX1-V%hJ3#8FxP5W)= zP+vwaZDD!fha4>ki-!jxa@H^dE!-x7DgOP_FCrkWDW1&RsvTOmL#i7QUfo!3I2&t@ z;fjY3fyDvj1U_a64E$~o!+|M|cp+aW$BGC>5VmHDONl_xwClj45e9j=AURr22Sr2H z3aX*!-UPa;6&1CpXjw_spjv=g157@VON(3&u9S~XWbvITb{GqxqV)THxu2D3)K)aZ zFcei+bY0>IDINw44WuB>Oc}~Jb|MloLIaR6q89BjQ{=C{#3A zO-vdAT_?qby{J19?m7zYVjl-A=2$Joh8@ai+@b$LonAj8fVk3TG9co~*hAjFJT+Cn*9`bdu;}SDYM^=k~yuw%@^= z^ndbV42!A9`Y6InZSJk7WSzoQSCV2KTcP~hz|q~d!k)r;w@8+0o8p_a(cyr1w}`i~ zyT^Yvc?(>xm6sZ&&;K}oV*U!4vEKjU*4FhqZSRvy$N&7jy60N`QpNS|-2CC(!^MyG z315DFaP!v}=l0yrTzepY|Lxub*L%&?na%HRocL{FP(Ql+Oja`Qze~=4c60y5doNsm zPoKSeb@&at|1g7JIrG-i!jH}Qnz^}kgYEe1(EiMa7q6a8x^S(sT)uqdgOC3Kw>0JS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json b/src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json new file mode 100644 index 000000000..ddb529421 --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json @@ -0,0 +1,90 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json", "common/player_inventory.json"], + "background": { + "texture": "guis/interfacefluid.png", + "srcRect": [0, 0, 176, 231] + }, + "slots": { + "CONFIG": { + "left": 35, + "top": 35, + "grid": "HORIZONTAL" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "gui.appliedenergistics2.FluidInterface" + }, + "position": { + "left": 8, + "top": 6 + } + }, + "interface_config": { + "text": { + "translate": "gui.appliedenergistics2.Config" + }, + "position": { + "left": 35, + "top": 24 + } + }, + "interface_stored_fluids": { + "text": { + "translate": "gui.appliedenergistics2.StoredFluids" + }, + "position": { + "left": 35, + "top": 125 + } + } + }, + "widgets": { + "priorityBtn": { + "left": 154, + "top": 0 + }, + "switchInterface": { + "left": 133, + "top": 0 + }, + "tank1": { + "left": 35, + "top": 53, + "width": 16, + "height": 68 + }, + "tank2": { + "left": 53, + "top": 53, + "width": 16, + "height": 68 + }, + "tank3": { + "left": 71, + "top": 53, + "width": 16, + "height": 68 + }, + "tank4": { + "left": 89, + "top": 53, + "width": 16, + "height": 68 + }, + "tank5": { + "left": 107, + "top": 53, + "width": 16, + "height": 68 + }, + "tank6": { + "left": 125, + "top": 53, + "width": 16, + "height": 68 + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json b/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json new file mode 100644 index 000000000..245a93d45 --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json @@ -0,0 +1,73 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json", "common/player_inventory.json"], + "background": { + "texture": "guis/interface.png", + "srcRect": [0, 0, 176, 211] + }, + "slots": { + "CONFIG": { + "left": 8, + "top": 35, + "grid": "HORIZONTAL" + }, + "STORAGE": { + "left": 8, + "top": 53, + "grid": "HORIZONTAL" + }, + "ENCODED_PATTERN": { + "left": 8, + "top": 97, + "grid": "HORIZONTAL" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "gui.appliedenergistics2.Interface" + }, + "position": { + "left": 8, + "top": 6 + } + }, + "interface_config": { + "text": { + "translate": "gui.appliedenergistics2.Config" + }, + "position": { + "left": 8, + "top": 24 + } + }, + "interface_stored_items": { + "text": { + "translate": "gui.appliedenergistics2.StoredItems" + }, + "position": { + "left": 8, + "top": 73 + } + }, + "interface_patterns": { + "text": { + "translate": "gui.appliedenergistics2.Patterns" + }, + "position": { + "left": 8, + "top": 86 + } + } + }, + "widgets": { + "priorityBtn": { + "left": 154, + "top": 0 + }, + "switchInterface": { + "left": 133, + "top": 0 + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json b/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json new file mode 100644 index 000000000..7fc761361 --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json @@ -0,0 +1,49 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json"], + "background": { + "texture": "guis/priority.png", + "srcRect": [0, 0, 176, 125] + }, + "text": { + "dialog_title": { + "text": { + "translate": "gui.appliedenergistics2.Priority" + }, + "position": { + "left": 8, + "top": 6 + } + }, + "priority_insertion_hint": { + "text": { + "translate": "gui.appliedenergistics2.PriorityInsertionHint" + }, + "position": { + "left": 8, + "top": 98 + } + }, + "priority_extraction_hint": { + "text": { + "translate": "gui.appliedenergistics2.PriorityExtractionHint" + }, + "position": { + "left": 8, + "top": 110 + } + } + }, + "widgets": { + "back": { + "left": 154, + "top": 0 + }, + "priority": { + "left": 20, + "top": 30, + "width": 138, + "height": 62 + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json b/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json new file mode 100644 index 000000000..77ef26f24 --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json @@ -0,0 +1,26 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json", "common/player_inventory.json"], + "background": { + "texture": "gui/fluid_packet_decoder.png", + "srcRect": [0, 0, 176, 166] + }, + "slots": { + "STORAGE": { + "left": 80, + "top": 35, + "grid": "HORIZONTAL" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "ae2fc.gui.fluid_packet_decoder" + }, + "position": { + "left": 8, + "top": 6 + } + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json b/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json new file mode 100644 index 000000000..052718a7a --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json @@ -0,0 +1,104 @@ +{ + "$schema": "../schema.json", + "includes": ["terminals/item_terminal.json"], + "slots": { + "BLANK_PATTERN": { + "left": 147, + "bottom": 161 + }, + "ENCODED_PATTERN": { + "left": 147, + "bottom": 118 + }, + "CRAFTING_GRID": { + "left": 18, + "bottom": 156, + "grid": "BREAK_AFTER_3COLS" + }, + "CRAFTING_RESULT": { + "left": 110, + "bottom": 138 + }, + "PROCESSING_RESULT": { + "left": 110, + "bottom": 156, + "grid": "VERTICAL" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "gui.appliedenergistics2.Terminal" + }, + "position": { + "left": 8, + "top": 6 + } + }, + "crafting_grid_title": { + "text": { + "translate": "ae2fc.gui.fluid_pattern_terminal" + }, + "position": { + "left": 8, + "bottom": 175 + } + } + }, + "terminalStyle": { + "header": { + "texture": "guis/pattern.png", + "srcRect": [0, 0, 195, 17] + }, + "firstRow": { + "texture": "guis/pattern.png", + "srcRect": [0, 17, 195, 18] + }, + "row": { + "texture": "guis/pattern.png", + "srcRect": [0, 35, 195, 18] + }, + "lastRow": { + "texture": "guis/pattern.png", + "srcRect": [0, 53, 195, 18] + }, + "bottom": { + "texture": "guis/pattern.png", + "srcRect": [0, 71, 195, 178] + } + }, + "widgets": { + "craftingPatternMode": { + "left": 173, + "bottom": 177 + }, + "processingPatternMode": { + "left": 173, + "bottom": 177 + }, + "substitutionsEnabled": { + "left": 84, + "bottom": 163 + }, + "substitutionsDisabled": { + "left": 84, + "bottom": 163 + }, + "clearPattern": { + "left": 74, + "bottom": 163 + }, + "combineBtn": { + "left": 84, + "bottom": 163 + }, + "fluidBtn": { + "left": 74, + "bottom": 153 + }, + "encodePattern": { + "left": 147, + "bottom": 142 + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json b/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json new file mode 100644 index 000000000..63f9bb7ba --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json @@ -0,0 +1,26 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json", "common/player_inventory.json"], + "background": { + "texture": "gui/ingredient_buffer.png", + "srcRect": [0, 0, 176, 222] + }, + "slots": { + "STORAGE": { + "left": 8, + "top": 108, + "grid": "HORIZONTAL" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "ae2fc.gui.ingredient_buffer" + }, + "position": { + "left": 8, + "top": 6 + } + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json b/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json new file mode 100644 index 000000000..462073c55 --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json @@ -0,0 +1,26 @@ +{ + "$schema": "schema.json", + "includes": ["common/common.json", "common/player_inventory.json"], + "background": { + "texture": "gui/large_ingredient_buffer.png", + "srcRect": [0, 0, 176, 222] + }, + "slots": { + "STORAGE": { + "left": 8, + "top": 72, + "grid": "BREAK_AFTER_9COLS" + } + }, + "text": { + "dialog_title": { + "text": { + "translate": "ae2fc.gui.large_ingredient_buffer" + }, + "position": { + "left": 8, + "top": 6 + } + } + } +} diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/burette.png b/src/main/resources/assets/appliedenergistics2/textures/gui/burette.png new file mode 100644 index 0000000000000000000000000000000000000000..7238bf637970ed460d0de7905fd7b5adab827d04 GIT binary patch literal 6072 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png new file mode 100644 index 0000000000000000000000000000000000000000..6435daecd8393d1a771b76f92cb9777a5af140e3 GIT binary patch literal 1772 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png new file mode 100644 index 0000000000000000000000000000000000000000..a65acc2bc0b3e6d6b7c84e0672ad1c811f50bf9d GIT binary patch literal 1732 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 efN#aBRrL(wOj@fw6*i2l@ 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 Ss@OFDaR5_PQ>d1E9r|BoPFNKH literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png new file mode 100644 index 0000000000000000000000000000000000000000..045611f803177de95e5f09ef81db6f0cf749c8a1 GIT binary patch literal 7072 zcmcIpWmFu#mz}}AKylYXad&qqF2%jLyE_zz0)@dTZbe#(yK8Z0aHd5E2KVK6{<~k+ z&VJeCBzZ|rZr*wK<;zX1x~e=T8VMQz0KimKkkJGHfUhPHfP(Zo7`azi0|5Au{@Qw; zn&v)KuI?_@c8;H@JpEihQGN2Yd)@S1J;~8`Hx$f_+-!(5=^MmgLAI2E7G`dm!ljR8 z(0@H*zcDnVPHpQza4i$b0yr(cJl^oV40az&aZ*E4?UmlVNMSDUnxy}EuKqJ4wk^{2 z^s{cbpf=|wGs$M^XSd7V^}Y74|Kkz7i^uZwg_o8Iv;{`;*9o!|YQimSGC@f=>g7|n zc1BWB`#`gh8e(McbK}?HETZi9^zLDA$?WFmqf>APHkV2Em)H)Eal@C~VRz{HP4hXa z-{ppT!PVEL8DS6hPTg?nU@j;-^2^^swLBLw{}<*z?bmIm;_Rjlbj;xH~+&b~oqyNt1`|WWRsfiofeZ$%(8onUvjDA+> ziEtmNLxatRKZLZ2kR?K247Q(qcRCpYv%IEo{}MFTUZBKQrMuhUfI@dYYr9=>`T~3lU)r zW!VmuznUN)EQ^Np5+6uEwjFhSrU*D}%`^!hEtx{0P2zzu7ANt6FW-Vdb`X8*JSY91 z5^?ytMt1u;y2egv4r|c*T~%S|k0WQOMu6;9W|c>h6iT%ZhZc?<1T}f zPsV!*Qa21VHYngaw+A|Vtsv*z7%)@18l~FR_r=dQ)+=oVIBRTdZ!)@-*TcN#Gs@N8 zuxlQEN3H=qqT5qgihLmSHt{5iW46d&5%^Wo^)YrTSQkk#0A}c=8W!0p*(16>kH7W9 z;}dSK+>*b=x6fi1?EuaW^`7?QZOOubby<&+yB$0M|8T{DB@ zcE7j6u>C9iZdEwHl2cSQuz#GIE``HrPfk0SoRm#eFER7BtCwdBdN?Me{1se>eIG0| z&c-)ssU3qjJYWmkoKIVywKEU6W$Sp~qw7?Ts@q~g&WqJUot3V~4S&1;mUbVX+cI2Y z?NI02%L$ku#y#PD;fhJ2Qf%M=K)OjVeF4(9s8%x=P2Qm#Lc$IB5bP!#R99!9r4QR9 zkq&b;m-xXnG90vJ>ygc#LqWiOs(atCyrO5V?4O(>F(o7*+mR>I#JhMtF-EHbGaIkL zh4XxJ`z(QWKSFgCTzcnVH4>My z*b^@Nug1G7jfi>yk2wIFRZLJ8YDvyYI|8bU6@Dz_dnIkeb`}Az7E?{mJEG)}Qj`i5$vzZNOz@#iLR4p8c8?~R%|?8Aar?5y7;#w8hzV+;scXI{I%0V}Bdr6yFt zwHpieJgOMZO=DkQ6cI(_LtiNk*z+EXaNU)ACVAGOs+=3T5>ic5!yGi`miclWNJir^ zYv5~KDRjZ>T91p{$K9yrb?oRj#-k8NoIhzHl84XM&xN*XgTX|!dz~xHhf6`I%H-86 zZm^iTo&_*amDN(}oW@rXCd`Y+yIgJSzFg1Hom?yEX1=*8b9dHE__NZ{dApy5aoZ(1 zbW2MG@{wur?J=m96+hdmtVtQSMYKs#b;jGtU5bvz(Aaz197t9WeNbM-{L71MEDpk1 zo}!57dIU@Ka#uNJ-$W52Ao+-gBKEb}5G+yTctVv-PitnzsgVSiWA#T%iCs*pe%2_Z z{@~U(La_uQkEuXj$m@TrG%}Qgqd$d9H$pqmxs%dT{MBJ_GC%+YD@n!uQ_(lSK&<&X zu$Bm4R`0An=T*KcdGh=rQf5$@dW^TNw%umQc!d=eIz_r>_F`?KzSajfsuzr!+9CEM3;l$@E0YMl_&aT2bh<1HG`6TsQfVyawy7bubK*KbfHSjy*h8$K( zpQW_Be4ZfOQgaUD_(wu;-^5;yhOG1lr}EW90|1s;ryPBOSwh;GNc(QR?^z|h6f!mX zV2^~&G^0yaxa7CCb@OkxFpcWJg}-Y&zpXGNq|!v^5aNG~7=fdS_xX56&X)iyDYM}^ zlfD&>A+vW^ODd|8umx9p$DEIR^O0h(Nk5@^0_F+BKk}mSvYed~!x0g0me8SJRKbnq zD!b|F0Hj!XGu9l!E>)%8qEvjN89LsXF5NZ&g%;Q#M?b?GV>Zmur>dNvtr6SGHAv2- z!3tN1Y%RmmdGM#p@G54qhB5(*B#uI9V(Onrf(z36s%tQ9B~|naFlB8EuJpt|ebhqS zpIL*q0`}HWGo-sO$$g|9oniJFMz#8;*qVR3>)O(}Mv`Ua?;~8nP(`CpobR zJGyPeI_~6ro;lYz#b@&Pe|Ut3ru)JV6Y`+NZ!BY_e6+#;T&*3c7A->$Kcnj8lWM0K zQWLA1?q+B+6B<+Idw9|@WjCB1`EKqr({SuwkG-2q_tj4+L&k3TcFvK;tu@d{2L;J0 z&lKd*5r?kWN~1nPN2jN@oy?j)Jw02?{0kkD`;1P^2btabldIUmf{d>?M&kg{3mT?sDGFED7~|q*QOz_ob^@&V?mim^wpks zgG>v9W*&f?ly54_5(ZbCu9k|bF+Rpnf6$QwQYXtoHlkx4wv!KCBsNMurToGG9QuMS zIBcW!9QI{;zk!W%-=orP3;KjO}dK{2|30!Tqw=~fBPXklu9)Kb3>E$SwAtqWj^$H(Z>FxeoxjG;u8TD zntR+S6`$M$ZCBL@E6AocQ*s!q>B?nuIJsW}b-?$R&hwK;KJow-6KjAVTTl@>>{Br? zX^n}d-L_3?s4k2}n?g{jy%KQDP4*LAn}Vvq3o3fq1BB)sgC|Ri}tu3VEb$8g2%OLd1pG zw0PXw{$-V#tEqHnA)mmccS5=``H8k{vu zrs_53%C-NP9^o3Ox|LmyinFU~Ykmy*)P4IU^hw(5@xIo6SBC`RV^6z+Ou>P^S`cX> z^JKS2MuMNQo_%8LG6!r*XTY^C+<#}%x-!2Ae#7YABS~J$ zHOw<&aBV{f5r<896t8BT>Kh9=&Ny7pG;4*RwgvTG>;7$n>N8T!p^Z&hjZKHd>c?wD2D=qANUY;#2Z~p=D$1JZHFv1_lgGiUqJ_r&27x(H2z%OR4!93 zh6h>UA3)io2`vzrVzj>=*VcsoW4G$GbGT@YOfRf+jo&G=w zTFV{p&hrC{{i-~m?5c8mKske_(eEd$VDRB%UAF1^7}^$LBR) zMU`9kI*O7ZO?WWLu6O(2Xe@W+q8VD2GJoi5g?+D4cy_#Tb=P!bPf6gh_;FCs6$Z8B zyJGn0YMK7D59Q1|v{x~-$-s2)vX zA@?(bd`fSKkVOb_HS8e z{Lu+n2f~}Ye(csy@4t!Q|E0J+=scH&@w;}H{e+KZhq}WnjU%sm*~(@am@K}!^qA5E z5sgrsYPk296?RA77ugHGCSV8ISb}h2Fq|=0LR2s zuZ@z?w)kO=Xm;mDB7>Z@Xe^{ER`bfSFCYOYd*r-?Xl0D#PoCH2IBkq&a*m4DG*1bL zqG-6w;#dyBWQ2=_tPp|S@5g~}fA>@9*w}C*XCO8cHP|&Fr+BE9)Mo-&$FFUh>JPrW zc~99m2h-Fvm1|gkava5GpO}z?l`;`Y7NNa$6FyeZOcZRk1>(hHL&8yHgo-uN8I?V) zQw(aFxzg@de{HA7YY|}yvZu1(Ew+V9Vec;uAk(+1jN?(0oK)(s1gf%_lM&k=AP?3^ zBTU|2MV_UHq!xx|;GUOQ>jL%^sQExLe%xK}q*54-NH`Y!Aco<+TZTtvt zZ(eZvlhB?K2PQU)^}=*nNl_Hj&P{xHck3+w6ab z$VxT`j4gpi>ybv`RLn?X6&d_&<<#Fh>UhGxq)d@1@g^AmHax=ZVx}zxa*M7INOttr{Hsf4 zXGK=b3iz>Q;&^cU)Dhy;W;#K-dPgC2p@@4iLK2!Vf1O?b_knxo_^-hj(~2F;VTfIS z#Rr1V%<2EkN7^!GPS@DBAZIq8po{I_J^hJZ-MW6JwcFrq3NBe@j|^UD}$YS*!1am@gHd)lm%9m=gQmtZR(hG@F{v3phAH^?qFPQm8W%ndt@$ zs6`pi&>dEm6xNBx5G=IDlcN+oKSi2ncLVF>h0||jJSF=LV7i7V^dsC!MO=8HV&mfzPoC111Mu-%h?0`-O8mCp^F#a z)GllXmu5puS~83iDN*?jM@g&w`1O|@Yc^I&C#O)6^51H%N|ePXURoa-PYT0`0t(4a z7V#g1*w!C()&ygN$%Q2c!vvv6+(uJ8m?V5Z2HGW6x1#PrvavQGl!`(R4X2a)%+ghk zpAJ%mI@XP?0go!A>3NiJR>4WrSMbiY=weBb)J)_Lpb~^D2rNBqZd*Y4Q&{Ljn;UOp zOk1~Ns0$c_U7HOwU~XJ#^AxOAHwpKc8@V`5dI!0`eY0P%R8Go4;If5 zH@=pWt-7X6Th>-a5jp)hj!G{k(_!y@Z+#z8#ex}KcmFjYcY$Wj^68S=SYYR$;jhqL zK?XU*>GI)wFjJ8un6hlYgqTnBEaP#g+Nxyu~`!Y?S)vr)h!e^2ypyb z8iv$dEF8~ld@9Rve{4?ij_Y9lfQyDN?#^={Z6G(H@f>fJ*_Yq5uBQQuT!4jHduPSU zf4SB1iay3^icsvn)R=N$FFtDWks8Ku27%I;hJdc(71e4c%X7>{Yd4b~n2#a&J~zMO zW0PNyV1=S+?RUd&FAIy72VhaLp^d8hR$8g{4R!XUI!m>`ljRLB8|q5WODCKjzs02& z{Q8$>G+tbDP79&1+EdrXFZC4+y6bz(N8W`V41Xwrl zpQ`eUB>hncg&;?Xf`ZVAgwiOd-dfkQffNMlaD0>h5>og=`S-<^iew~z;gKB!>c9pj zGst)qXCv*TrPURsrT;^ZeN|y|gT9C=^sD1Vsm`N6qTm zF7)n|hD?`GE4sPi8KGl6v*?l=<$8I!;v@7@Vl&7y$P&Ik?<$$58A49JaoZ66{BQ~% zDHl~~Z3d^#JFYNjX~Eabgk(B{Ng2wCKxcgzduf4`e{{4_IcS`2L7Fu$;s(-V@vdL*t+A>Y zm>a;6U0^gFX{&MF*s0OAuUTLSF=wp%Uec->K;*_k9Y!ZJQt>STjYZ~QUV-yV(F5pn z$jCXvD1BNrhFlV&_BSc_$E}dFfF^5m`&kvV!O&X5wPzw=c)P?;=e5n@Ca?(bajNI* z;`K+;o@0iCt0n?Lxcla?_@kzGP++ET&sG{6aVqVd9eNbpS+Lw@Iy{nd*aokU^Y?&U zF#vJ)0u0mD6(xb-eE@hDESl%>&|JpbO^HL|9@->0@Z(h`N3u|smjSr{Q`6W>o?ll` zT@~~_006AMe;N>ho)ei9m1^Z8wH4*b}(0Kyu@h+lr* z3m&Y@i3DePgq=aWG^=bh&Z{54hZ5*3LXm`~PZ~{4L*v|>^R)+w8NeWp0-yrC`T_^| z&&K}ks>9ipze>JatC0oSa1ErZLZi~ps8U^sxBWoKMfDMrN=jQ<7zZhaJ&Ft{TY?*)CPF+3RJ?0DI``|f}EMaUS zD;(fDk8&B*eg9`y08Ik;NFgzV!zjDtb&8`0tiMt;>{9|T18NZJ;mqC$ z)&cS}pTw66zve!#SpFTBYG1!HC&!dymK3xCfPLf0ltuS2ctFy1kwI#3X0=J64oJ$4~fuc?1z}0sE-XHrrs2-B> zmm9@$k%=K#omTeG1&Xq&GPP1>;r|8;-X!$^ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png b/src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d3519457cb50afb28467209252f585d14071b3 GIT binary patch literal 1614 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgSRS)vRaE2o(Pz$)6r$&CEA^^aEoYEslm=K+_OwnH1+zLvJW2Y zKf0>9d-FQJbxjIC7#}@(vE+r4>OXzWEgc^p?2v!|&i36sMyJJxLN;eJ1(=@pIGUVf zwq1)Y?qahek77^55g*%$ac2$`*W5jP{Q7Q<^PlX#KYtPaNsYNi>7&eL-tLO#inPm@ zEZ+;aM|cKxobNo&yG!At^7Yi|_btR_#VXQHoLKU#)A>l!#^$GdX{yeu^VIq@R=NFj zx|Z7dSuA~PQ1aU9PvUH!Z+XpnZQ?F)Tu8{nsijBi)QSU6Q^I;hgf(yVSN>?1t)CQQ zwDVNLlL!Yzp2lZ_0wrtyf ziy_V4bN%+MCj<>P@W#Y!H?8E~afk7c|E?5|z9-8yMYs<9maGltli0=j>)M@rQNJx} z8H}!6{lr^Pz|kYBS$+5M-Mw@7{;ghi^*+B{O3|d8e_Yb zCYGe8D3oWGWGJ|M`UZqI@`*Dru&8;uIEGZ*dVAN=@3wbx0KGjtB_;+`2)^e^~? z<82eA4U^>k>b#0=Q&fvFva%ke^V}(5(9htLYIM)=gK6t{;$iTuNz~BH% zerSvaCYve0_}~Awt9H9ydU4y@o#D@v7-nBDjlFhXzU|>FYlX8%br}>G8W$f|fd0zajF<(!0^)tl>uk*z9+YjG%{QulD zL60dQkmZ`)i?gl_TpMJ*oc`bNOl}S%Q^56;^_7x2>o~5g>1uEI{nzff?S4y_#EO5mnVo#D{dvXwL*lFnm5fC^hXtTQ zKR#}W#u{JH7!+Xe21ZE8i)VEV*|p#HSU%g}YbW0D=tm60c3g1Cfo!jN zetxa_^WtY)a$o!W?PAc;cj$ZnXXn>vHRojVgUgW|j2Y=n37ifL4P1Bc@E>@7ZT99? z%RBzUf|^T31V83IX0>LT2^5)b&D8r?LQwFsiSBWO?>m?x;u>Vi_86*{_sKTeFf*W~ zLF81)xPe81!J+jHN6NWp&kO9!Sd7v?7r){7;u_9yntQ_GKJ#Z!&TUklkdDijAm=JD zOaof6VuEbrnVWxP-aUWT=di)GjHL+Ts{`$KYB(~&6N0)rB%&7VI$eM5qky3AWs{c`Hc>GvzHlw5tg6~1 uC32YgKxvF#!X_1wsy9r)q5?ly^F(~g?gz_T9|U{^DfD#pb6Mw<&;$U16FN%( literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png b/src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png new file mode 100644 index 0000000000000000000000000000000000000000..f7763ab054b53752972e487e5ed367322418af33 GIT binary patch literal 1382 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJD%T~8Oskcv5P@A~H5;*e-a z41OA<`$}@x$+W)4Daj#^BsME-UomA)ikNMyyPxy&E~_4)+Y@JguQdA`_**rhf|al4 z&9OomwcU5$9nVjAe5~-!-2I2|<~=VsTVQN+|L1wdhPBoE_U?MF|Df60{`#|r4-YC| zYkgB^v*_3JT{Sf|dp>m^`G5CgMa{03<(D%n_VIppldt=m|L)I)PX|A?=`l7K;6)o| zUyu88_@Tk?Tf6q|mHqa6@6KocUdP-!zM8qfE}{6&-MfFk&24`+>-6tWA`FLE=!Np) zsxRi}=eO_Pz5D2T!{W0~|GsTYlsH~k^XpK5e?K4N>~b5q`TLGueraNJzpZj{xII%v zZ@;|#zi;vNe}9$Uc>n#`tlcMK?&V!&@cH-aQ|gb;pP$dK|M&Rz_J{v|f4|9oC#HC2 z{@2M3mk-Y4`}Cn^L%r<}poS|k_nyVpaTVCT@RrXhKASqPob$lXg!!lQ*X_(c^Ym*N z|AF;O<3BB!ovrtIZIw`g)o$y#4>vrx@U(fvDRfX(oblt1t#?%m73H;QK_^W84_*zB0YVwBEZ9GA!P>7c^tje;lUoISr$FvIp7qr(ga zqEWBIvz$7<*9h%qOfxtCv@~N{d)$(NLzrl^8Ek)|t++YUL9v@0TV(th6?_;7qc@Lk zH}bb1ev^Id`N!GD<>%`5&$n&Azgwc=GvkcIV4=S4E7upc~G*u^vJWHKa&r9R&LbZD|2MWXJ%lDh#zQ6 awmr=MZf6p~aViv=2U57g4E# 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/mixins.ae2fc.json b/src/main/resources/mixins.ae2fc.json new file mode 100644 index 000000000..f4d2e44e9 --- /dev/null +++ b/src/main/resources/mixins.ae2fc.json @@ -0,0 +1,25 @@ +{ + "required": true, + "package": "com.glodblock.github.mixins", + "refmap": "mixins.ae2fc.refmap.json", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ApiCraftingMixin", + "CraftConfirmContainerMixin", + "CraftingCpuMixin", + "CraftingGridCacheMixin", + "CraftingTreeNodeMixin", + "DualityInterfaceMixin", + "InterfaceSlotMixin", + "InterfaceTerminalContainerMixin", + "PatternSlotPacketMixin", + "RestrictedInputSlotMixin" + ], + "client": [ + "AESubScreenMixin", + "CraftConfirmTableRendererMixin", + "CraftingStatusTableRendererMixin", + "EncodedPatternBakedModelMixin" + ], + "server": [] +} \ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..5744378fb --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Cool Mod", + "pack_format": 3 + } +}