From 632c0c3b8f3a141637b61bd5927629047aae8fa1 Mon Sep 17 00:00:00 2001 From: GlodBlock <1356392126@qq.com> Date: Fri, 13 May 2022 23:09:58 +0800 Subject: [PATCH] begin work --- .gitattributes | 2 + .gitignore | 24 ++ LICENSE.txt | 165 ++++++++++ README.md | 74 +++++ build.gradle | 78 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++++++ gradlew.bat | 84 +++++ logo.png | Bin 0 -> 11939 bytes .../java/com/glodblock/github/FluidCraft.java | 44 +++ .../glodblock/github/client/GuiBurette.java | 119 +++++++ .../github/client/GuiFCPriority.java | 43 +++ .../github/client/GuiFluidDualInterface.java | 50 +++ .../github/client/GuiFluidPacketDecoder.java | 33 ++ .../client/GuiFluidPatternTerminal.java | 53 +++ ...GuiFluidPatternTerminalCraftingStatus.java | 37 +++ .../github/client/GuiIngredientBuffer.java | 77 +++++ .../github/client/GuiItemDualInterface.java | 50 +++ .../client/container/ContainerBurette.java | 134 ++++++++ .../ContainerFluidPacketDecoder.java | 16 + .../ContainerFluidPatternEncoder.java | 216 ++++++++++++ .../ContainerFluidPatternTerminal.java | 148 +++++++++ .../container/ContainerIngredientBuffer.java | 40 +++ .../container/GuiFluidPatternEncoder.java | 107 ++++++ .../model/DenseEncodedPatternModel.java | 67 ++++ .../github/client/model/FluidPacketModel.java | 234 +++++++++++++ .../client/render/DropColourHandler.java | 62 ++++ .../client/render/FluidRenderUtils.java | 127 +++++++ .../client/render/RenderIngredientBuffer.java | 81 +++++ .../github/common/block/BlockBurette.java | 38 +++ .../common/block/BlockDualInterface.java | 85 +++++ .../common/block/BlockFluidDiscretizer.java | 14 + .../common/block/BlockFluidPacketDecoder.java | 38 +++ .../block/BlockFluidPatternEncoder.java | 38 +++ .../common/block/BlockIngredientBuffer.java | 58 ++++ .../component/DualityDualInterface.java | 184 +++++++++++ .../github/common/item/ItemFluidDrop.java | 131 ++++++++ .../common/item/ItemFluidEncodedPattern.java | 35 ++ .../github/common/item/ItemFluidPacket.java | 94 ++++++ .../common/item/ItemPartDualInterface.java | 37 +++ .../item/ItemPartFluidPatternTerminal.java | 37 +++ .../github/common/part/PartDualInterface.java | 267 +++++++++++++++ .../common/part/PartFluidPatternTerminal.java | 96 ++++++ .../github/common/tile/TileBurette.java | 84 +++++ .../github/common/tile/TileDualInterface.java | 309 ++++++++++++++++++ .../common/tile/TileFluidDiscretizer.java | 279 ++++++++++++++++ .../common/tile/TileFluidPacketDecoder.java | 130 ++++++++ .../common/tile/TileFluidPatternEncoder.java | 98 ++++++ .../common/tile/TileIngredientBuffer.java | 139 ++++++++ .../github/coremod/CoreModHooks.java | 105 ++++++ .../github/coremod/FCClassTransformer.java | 67 ++++ .../glodblock/github/coremod/FCCoreMod.java | 40 +++ ...ContainerInterfaceTerminalTransformer.java | 58 ++++ .../transform/CraftingCpuTransformer.java | 86 +++++ .../CraftingTreeNodeTransformer.java | 77 +++++ .../DualityInterfaceTransformer.java | 62 ++++ ...PautoCraftingPatternHelperTransformer.java | 57 ++++ .../transform/TileUnpackagerTransformer.java | 84 +++++ .../github/handler/AeItemStackHandler.java | 46 +++ .../github/handler/ButtonMouseHandler.java | 48 +++ .../github/handler/ClientRegistryHandler.java | 41 +++ .../handler/FluidConvertingItemHandler.java | 147 +++++++++ .../github/handler/RegistryHandler.java | 74 +++++ .../github/handler/TankMouseHandler.java | 34 ++ .../integration/jei/ExtraExtractors.java | 23 ++ .../github/integration/jei/FCJeiPlugin.java | 35 ++ ...idPatternEncoderRecipeTransferHandler.java | 105 ++++++ ...dPatternTerminalRecipeTransferHandler.java | 55 ++++ .../jei/ModMachHybridFluidStackExtractor.java | 29 ++ .../integration/jei/WrappedIngredient.java | 25 ++ .../jei/interfaces/IngredientExtractor.java | 12 + .../pauto/PackagedFluidCrafting.java | 19 ++ .../RecipeEncoderFluidTooltipHandler.java | 34 ++ .../pauto/RecipeInfoFluidProcessing.java | 13 + .../pauto/RecipeTypeFluidProcessing.java | 150 +++++++++ .../github/interfaces/AeStackInventory.java | 19 ++ .../github/interfaces/FCPriorityHost.java | 16 + .../github/interfaces/HasCustomModel.java | 9 + .../github/interfaces/PatternConsumer.java | 9 + .../github/interfaces/SlotFluid.java | 14 + .../github/interfaces/TankDumpable.java | 9 + .../inventory/AeStackInventoryImpl.java | 98 ++++++ .../FluidConvertingInventoryAdaptor.java | 166 ++++++++++ .../FluidConvertingInventoryCrafting.java | 32 ++ .../glodblock/github/inventory/GuiType.java | 265 +++++++++++++++ .../github/inventory/InventoryHandler.java | 51 +++ .../github/inventory/slot/SlotSingleItem.java | 139 ++++++++ .../github/loader/ChannelLoader.java | 19 ++ .../com/glodblock/github/loader/FCBlocks.java | 33 ++ .../com/glodblock/github/loader/FCItems.java | 39 +++ .../github/network/CPacketDumpTank.java | 50 +++ .../github/network/CPacketEncodePattern.java | 40 +++ .../github/network/CPacketLoadPattern.java | 87 +++++ .../github/network/CPacketSwitchGuis.java | 64 ++++ .../github/network/CPacketTransposeFluid.java | 53 +++ .../glodblock/github/proxy/ClientProxy.java | 66 ++++ .../glodblock/github/proxy/CommonProxy.java | 86 +++++ .../com/glodblock/github/util/Ae2Reflect.java | 112 +++++++ .../github/util/Ae2ReflectClient.java | 90 +++++ .../com/glodblock/github/util/FluidKey.java | 38 +++ .../github/util/FluidPatternDetails.java | 199 +++++++++++ .../github/util/ModAndClassUtil.java | 15 + .../github/util/MouseRegionManager.java | 82 +++++ .../com/glodblock/github/util/NameConst.java | 41 +++ .../github/util/SetBackedMachineSet.java | 61 ++++ .../assets/ae2fc/blockstates/burette.json | 7 + .../ae2fc/blockstates/dual_interface.json | 35 ++ .../ae2fc/blockstates/fluid_discretizer.json | 7 + .../blockstates/fluid_packet_decoder.json | 7 + .../blockstates/fluid_pattern_encoder.json | 7 + .../ae2fc/blockstates/ingredient_buffer.json | 7 + .../resources/assets/ae2fc/lang/en_us.lang | 33 ++ .../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_discretizer.json | 6 + .../models/block/fluid_packet_decoder.json | 6 + .../models/block/fluid_pattern_encoder.json | 12 + .../ae2fc/models/block/ingredient_buffer.json | 6 + .../assets/ae2fc/models/item/burette.json | 3 + .../models/item/dense_encoded_pattern.json | 6 + .../ae2fc/models/item/dual_interface.json | 3 + .../ae2fc/models/item/fluid_discretizer.json | 3 + .../assets/ae2fc/models/item/fluid_drop.json | 6 + .../ae2fc/models/item/fluid_packet.json | 7 + .../models/item/fluid_packet_decoder.json | 3 + .../models/item/fluid_pattern_encoder.json | 3 + .../ae2fc/models/item/ingredient_buffer.json | 3 + .../models/item/part_dual_interface.json | 106 ++++++ .../item/part_fluid_pattern_terminal.json | 9 + .../ae2fc/models/part/f_pattern_term_off.json | 8 + .../ae2fc/models/part/f_pattern_term_on.json | 76 +++++ .../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 +++ .../assets/ae2fc/recipes/burette.json | 31 ++ .../assets/ae2fc/recipes/dual_interface.json | 15 + .../ae2fc/recipes/dual_interface_alter.json | 11 + .../ae2fc/recipes/fluid_discretizer.json | 33 ++ .../ae2fc/recipes/fluid_packet_decoder.json | 32 ++ .../ae2fc/recipes/fluid_pattern_encoder.json | 30 ++ .../ae2fc/recipes/ingredient_buffer.json | 37 +++ .../ae2fc/recipes/part_dual_interface.json | 11 + .../recipes/part_fluid_pattern_terminal.json | 15 + .../ae2fc/textures/blocks/burette_side.png | Bin 0 -> 933 bytes .../ae2fc/textures/blocks/burette_top.png | Bin 0 -> 2128 bytes .../textures/blocks/fluid_discretizer.png | Bin 0 -> 2004 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 -> 1910 bytes .../textures/blocks/interface_alternate.png | Bin 0 -> 1910 bytes .../blocks/interface_alternate_arrow.png | Bin 0 -> 1927 bytes .../assets/ae2fc/textures/gui/burette.png | Bin 0 -> 6072 bytes .../textures/gui/fluid_packet_decoder.png | Bin 0 -> 5240 bytes .../textures/gui/fluid_pattern_encoder.png | Bin 0 -> 7072 bytes .../ae2fc/textures/gui/ingredient_buffer.png | Bin 0 -> 1614 bytes .../textures/items/dense_encoded_pattern.png | Bin 0 -> 801 bytes .../ae2fc/textures/items/fluid_drop.png | Bin 0 -> 1482 bytes .../ae2fc/textures/items/fluid_packet.png | Bin 0 -> 825 bytes .../textures/items/fluid_packet_overlay.png | Bin 0 -> 618 bytes .../parts/pattern_terminal_bright.png | Bin 0 -> 1488 bytes .../textures/parts/pattern_terminal_dark.png | Bin 0 -> 1427 bytes .../parts/pattern_terminal_medium.png | Bin 0 -> 1506 bytes src/main/resources/mcmod.info | 18 + src/main/resources/pack.mcmeta | 6 + 170 files changed, 8945 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/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/GuiBurette.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/GuiFluidPatternTerminalCraftingStatus.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/container/ContainerBurette.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/ContainerFluidPatternEncoder.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/GuiFluidPatternEncoder.java create mode 100644 src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.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/FluidRenderUtils.java create mode 100644 src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockBurette.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/BlockFluidPatternEncoder.java create mode 100644 src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/common/component/DualityDualInterface.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/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/TileBurette.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/TileFluidPatternEncoder.java create mode 100644 src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java create mode 100644 src/main/java/com/glodblock/github/coremod/CoreModHooks.java create mode 100644 src/main/java/com/glodblock/github/coremod/FCClassTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/FCCoreMod.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java create mode 100644 src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java create mode 100644 src/main/java/com/glodblock/github/handler/AeItemStackHandler.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/FluidConvertingItemHandler.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/jei/ExtraExtractors.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/FluidPatternEncoderRecipeTransferHandler.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java create mode 100644 src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java create mode 100644 src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java create mode 100644 src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java create mode 100644 src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java create mode 100644 src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java create mode 100644 src/main/java/com/glodblock/github/interfaces/AeStackInventory.java create mode 100644 src/main/java/com/glodblock/github/interfaces/FCPriorityHost.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/AeStackInventoryImpl.java create mode 100644 src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java create mode 100644 src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java create mode 100644 src/main/java/com/glodblock/github/inventory/GuiType.java create mode 100644 src/main/java/com/glodblock/github/inventory/InventoryHandler.java create mode 100644 src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.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/network/CPacketDumpTank.java create mode 100644 src/main/java/com/glodblock/github/network/CPacketEncodePattern.java create mode 100644 src/main/java/com/glodblock/github/network/CPacketLoadPattern.java create mode 100644 src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java create mode 100644 src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java create mode 100644 src/main/java/com/glodblock/github/proxy/ClientProxy.java create mode 100644 src/main/java/com/glodblock/github/proxy/CommonProxy.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/FluidKey.java create mode 100644 src/main/java/com/glodblock/github/util/FluidPatternDetails.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/java/com/glodblock/github/util/SetBackedMachineSet.java create mode 100644 src/main/resources/assets/ae2fc/blockstates/burette.json 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/fluid_pattern_encoder.json create mode 100644 src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/lang/en_us.lang 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_discretizer.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/item/burette.json create mode 100644 src/main/resources/assets/ae2fc/models/item/dense_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_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_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/part_dual_interface.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_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/recipes/burette.json create mode 100644 src/main/resources/assets/ae2fc/recipes/dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json create mode 100644 src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json create mode 100644 src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json create mode 100644 src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json create mode 100644 src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json create mode 100644 src/main/resources/assets/ae2fc/recipes/part_dual_interface.json create mode 100644 src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.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_discretizer.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/gui/burette.png create mode 100644 src/main/resources/assets/ae2fc/textures/gui/fluid_packet_decoder.png create mode 100644 src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png create mode 100644 src/main/resources/assets/ae2fc/textures/gui/ingredient_buffer.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/dense_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_packet.png create mode 100644 src/main/resources/assets/ae2fc/textures/items/fluid_packet_overlay.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_medium.png create mode 100644 src/main/resources/mcmod.info create mode 100644 src/main/resources/pack.mcmeta diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /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 00000000..0ed9cfa0 --- /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 00000000..0a041280 --- /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 00000000..c3709f96 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# AE2 Fluid Crafting Rework + +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.18, 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) + +## Installation + +### 1.7.10 +Any version of AE2(Both Official AE2 and GTNH edition AE2 works). +**Extra Cells isn't needed** + +### 1.12.2 +WIP + +### 1.16.5 +WIP + +## Basic Devices + +### Fluid Discretizer +The **Fluid Discretizer** is a device that, when attached to ME network, exposes the contents of its fluid storage grid as items, which take the form of "fluid drops". +It does this by functioning as a sort of storage bus: when fluid drops are removed from its storage via the item grid, it extracts the corresponding fluid from the fluid grid. +Conversely, when fluid drops inserted into its storage via the item grid, it injects the corresponding fluid into the fluid grid. +Each fluid drop is equivalent to one mB of its respective fluid, which means a full stack of them is equivalent to 64 mB. +Fluid drops have an important property: when an ME interface attempts to export fluid drops to a machine, it will attempt to convert them to fluid. +This means an interface exporting drops of gelid cryotheum into a fluid transposer will successfully fill the transposer's internal tank rather than inserting the drops as items. +This is the central mechanic that makes fluid autocrafting possible. +Note that the only way to convert between fluids and fluid drops is a discretizer attached to an ME network. +While you could theoretically use this as a very convoluted method of transporting fluids, it is not recomomended to do so. + +### Fluid Pattern Encoder +Most crafting recipes involving fluids require far more than 64 mB of a particular fluid, and so the standard pattern terminal will not do for encoding such recipes into patterns. +This problem is solved by the **Fluid Pattern Encoder**, a utility that functions similarly to a pattern terminal. +When a fluid-handling item (e.g. a bucket or tank) is inserted into the crafting ingredient slots, they will be converted into an equivalent stack of the corresponding fluid drops. +Using this, patterns for recipes that require more than a stack of fluid drops can easily be encoded. +AE2 Fluid Crafting also comes with a handy JEI integration module that allows the fluid pattern encoder to encode any JEI recipe involving fluids. +This is the recommended way to play with the mod, since encoding patterns by hand is a little cumbersome. + +### Fluid Pattern Terminal + +Encoding recipes in a big and bulky workbench separate from the rest of your AE2 equipment can be a little inconvenient. +Luckily, we have the **Fluid Pattern Terminal**, which combines the functionality of the standard pattern terminal and the fluid pattern encoder. +Now, you can encode your fluid recipes using the same familiar interface you know and love! + +### Fluid Pattern Interface + +The standard ME interface lets you emit items and fluid packets with AE2FC, but it will only accept items, as in vanilla AE2. +This is a little inconvenient when you want to build compact setups for autocrafting with fluid outputs, where you would need to use both an item interface for inputs, and a separate fluid interface for outputs. +To make things easier, we have the **Fluid Pattern Interface**, which functions as a combination of an item interface and a fluid interface! +Its GUI is the same as normal ME interface, but it can emit fluids directly instead of fluid packets! It also accepts fluid and item inputs. +Automating fluid crafting machines has never been this quick and painless! + +### Fluid Packets + +When putting fluid pattern in a normal ME interface, it will emit fluid packets as fluid. +So if you prefer to deal with item instead of fluids, you can transport these packets and turn them back to fluid with **ME Fluid Packet Decoder**. +Simply connect the decoder to your ME network and insert the fluid packet; the decoder will, if possible, inject the fluid into your fluid storage grid. + +## Credited Works + +E. Geng(@phantamanta44) and KilaBash (@Yefancy) - Their amazing origin work in 1.12. + +## To-Do + + - [ ] Port to 1.12, with supporting for PAE2 + + - [ ] Port to 1.16.5 diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..cf7d305b --- /dev/null +++ b/build.gradle @@ -0,0 +1,78 @@ +buildscript { + repositories { + maven { + url 'https://maven.minecraftforge.net/' + } + maven { + name 'sonatype' + url 'https://oss.sonatype.org/content/repositories/snapshots/' + } + maven { + name 'Scala CI dependencies' + url 'https://repo1.maven.org/maven2/' + } + maven { + name 'jitpack' + url 'https://jitpack.io' + } + mavenLocal() + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + } +} + +apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'idea' + +version = "1.0.0" +group = "ae2fc" +archivesBaseName = "Fluid Craft for AE2" + +sourceCompatibility = targetCompatibility = '1.8' +compileJava { + sourceCompatibility = targetCompatibility = '1.8' +} + +repositories { + maven { url 'https://dvs1.progwml6.com/files/maven/' } + maven { url 'https://jitpack.io/' } + maven { url 'https://cfa2.cursemaven.com/' } +} + +dependencies { + compile 'com.github.phantamanta44:jsr305:1.0.1' + deobfCompile 'mezz.jei:jei_1.12.2:4.15.0.293' + deobfCompile "curse.maven:ae2-extended-life-570458:2747063" // pae2 + deobfCompile "curse.maven:packagedauto-308380:2977147" // 1.0.3.14 + deobfCompile "curse.maven:modular-machinery-270790:2761302" // 1.11.1 +} + +minecraft { + version = "1.12.2-14.23.5.2847" + runDir = "run" + mappings = "stable_39" + useDepAts = true + makeObfSourceJar = false +} + +jar { + manifest { + attributes 'FMLCorePluginContainsFMLMod': 'true' + } +} + +processResources { + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + + expand 'version': project.version, 'mcversion': project.minecraft.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..736fb7d3f94c051b359fc7ae7212d351bc094bdd GIT binary patch literal 54708 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 ZypaY2tb=~UE;0eV1Nd9`dw%@&{{Y^v%e4Ri literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..2d146a2b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun May 24 18:34:45 CEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /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 00000000..f9553162 --- /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 00000000..a751dbc2 --- /dev/null +++ b/src/main/java/com/glodblock/github/FluidCraft.java @@ -0,0 +1,44 @@ +package com.glodblock.github; + +import com.glodblock.github.proxy.CommonProxy; +import com.glodblock.github.util.ModAndClassUtil; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + +@Mod(modid = FluidCraft.MODID, version = FluidCraft.VERSION, useMetadata = true) +public class FluidCraft { + + public static final String MODID = "ae2fc"; + public static final String VERSION = "2.0.0-r"; + + @Mod.Instance(MODID) + public static FluidCraft INSTANCE; + + @SidedProxy(clientSide = "com.glodblock.github.proxy.ClientProxy", serverSide = "com.glodblock.github.proxy.CommonProxy") + public static CommonProxy proxy; + + @Mod.EventHandler + public void onPreInit(FMLPreInitializationEvent event) { + ModAndClassUtil.init(); + proxy.preInit(event); + } + + @Mod.EventHandler + public void onInit(FMLInitializationEvent event) { + proxy.init(event); + } + + @Mod.EventHandler + public void onPostInit(FMLPostInitializationEvent event) { + proxy.postInit(event); + } + + public static ResourceLocation resource(String path) { + return new ResourceLocation(MODID, path); + } + +} diff --git a/src/main/java/com/glodblock/github/client/GuiBurette.java b/src/main/java/com/glodblock/github/client/GuiBurette.java new file mode 100644 index 00000000..575cf102 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiBurette.java @@ -0,0 +1,119 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseGui; +import appeng.client.gui.widgets.GuiNumberBox; +import appeng.core.localization.GuiText; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerBurette; +import com.glodblock.github.client.render.FluidRenderUtils; +import com.glodblock.github.common.tile.TileBurette; +import com.glodblock.github.handler.ButtonMouseHandler; +import com.glodblock.github.handler.TankMouseHandler; +import com.glodblock.github.network.CPacketTransposeFluid; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.util.ResourceLocation; + +import java.io.IOException; + +public class GuiBurette extends AEBaseGui { + + private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/burette.png"); + private static final int TANK_X = 52, TANK_Y = 17; + private static final int TANK_WIDTH = 16, TANK_HEIGHT = 32; + + private final ContainerBurette cont; + private GuiNumberBox amountField; + private final MouseRegionManager mouseRegions = new MouseRegionManager(this); + + public GuiBurette(InventoryPlayer ipl, TileBurette tile) { + super(new ContainerBurette(ipl, tile)); + this.cont = (ContainerBurette)inventorySlots; + + mouseRegions.addRegion(TANK_X, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + new TankMouseHandler(cont.getTile().getFluidInventory(), 0)); + mouseRegions.addRegion(70, 16, 7, 7, ButtonMouseHandler.dumpTank(cont, 0)); + mouseRegions.addRegion(73, 42, 8, 16, createTransposeButton(true)); + mouseRegions.addRegion(81, 42, 8, 16, createTransposeButton(false)); + } + + @Override + public void initGui() { + super.initGui(); + amountField = new GuiNumberBox(fontRenderer, guiLeft + 95, guiTop + 46, 28, fontRenderer.FONT_HEIGHT, Integer.class); + amountField.setEnableBackgroundDrawing(false); + amountField.setMaxStringLength(4); + amountField.setTextColor(0xffffff); + amountField.setVisible(true); + amountField.setFocused(true); + amountField.setText("1000"); + } + + private ButtonMouseHandler createTransposeButton(boolean into) { + return new ButtonMouseHandler(into ? NameConst.TT_TRANSPOSE_IN : NameConst.TT_TRANSPOSE_OUT, () -> { + try { + int amount = Integer.parseInt(amountField.getText()); + if (cont.canTranferFluid(into)) { + FluidCraft.proxy.netHandler.sendToServer(new CPacketTransposeFluid(amount, into)); + } + } catch (NumberFormatException e) { + // NO-OP + } + }); + } + + @Override + protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException { + if (mouseRegions.onClick(xCoord, yCoord, btn)) { + amountField.mouseClicked(xCoord, yCoord, btn); + super.mouseClicked(xCoord, yCoord, btn); + } + } + + @Override + protected void keyTyped(final char character, final int key) throws IOException { + if (!checkHotbarKeys(key)) { + if ((key == 211 || key == 205 || key == 203 || key == 14 || Character.isDigit(character)) + && amountField.textboxKeyTyped(character, key)) { + try { + int amount = Integer.parseInt(amountField.getText()); + if (amount < 0) { + amountField.setText("1"); + } else { + amountField.setText(Long.toString(amount)); + } + } catch (final NumberFormatException e) { + // NO-OP + } + } else { + super.keyTyped(character, key); + } + } + } + + @Override + public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { + mc.getTextureManager().bindTexture(TEX_BG); + drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize); + amountField.drawTextBox(); + GlStateManager.color(1F, 1F, 1F, 1F); + } + + @Override + public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) { + fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_BURETTE)), 8, 6, 0x404040); + fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040); + GlStateManager.color(1F, 1F, 1F, 1F); + + IAEFluidTank fluidInv = cont.getTile().getFluidInventory(); + FluidRenderUtils.renderFluidIntoGuiCleanly(TANK_X, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + fluidInv.getFluidInSlot(0), fluidInv.getTankProperties()[0].getCapacity()); + + mouseRegions.render(mouseX, mouseY); + } + +} \ No newline at end of file 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 00000000..552a4bf5 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFCPriority.java @@ -0,0 +1,43 @@ +package com.glodblock.github.client; + +import appeng.client.gui.implementations.GuiPriority; +import appeng.client.gui.widgets.GuiTabButton; +import com.glodblock.github.interfaces.FCPriorityHost; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.InventoryPlayer; + +import java.io.IOException; + +public class GuiFCPriority extends GuiPriority { + + private final GuiType originalGui; + private GuiTabButton originalGuiBtn; + + public GuiFCPriority(final InventoryPlayer inventoryPlayer, final FCPriorityHost te) { + super(inventoryPlayer, te); + this.originalGui = te.getGuiType(); + } + + @Override + public void initGui() { + super.initGui(); + originalGuiBtn = Ae2ReflectClient.getOriginalGuiButton(this); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + if (btn == originalGuiBtn) { + InventoryHandler.switchGui(originalGui); + } else { + super.actionPerformed(btn); + } + } + + protected String getBackground() { + return "guis/priority.png"; + } + +} 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 00000000..aaab99da --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java @@ -0,0 +1,50 @@ +package com.glodblock.github.client; + +import appeng.api.AEApi; +import appeng.client.gui.widgets.GuiTabButton; +import appeng.fluids.client.gui.GuiFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; + +import java.io.IOException; + +public class GuiFluidDualInterface extends GuiFluidInterface { + + private GuiTabButton switchInterface; + private GuiTabButton priorityBtn; + + public GuiFluidDualInterface(final InventoryPlayer ip, final IFluidInterfaceHost te) { + super(ip, te); + } + + @Override + public void initGui() { + super.initGui(); + ItemStack icon = AEApi.instance().definitions().blocks().iface().maybeStack(1).orElse(ItemStack.EMPTY); + switchInterface = new GuiTabButton(guiLeft + 133, guiTop, icon, icon.getDisplayName(), itemRender); + buttonList.add(switchInterface); + priorityBtn = Ae2ReflectClient.getPriorityButton(this); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + if (btn == switchInterface) { + InventoryHandler.switchGui(GuiType.DUAL_ITEM_INTERFACE); + } else if (btn == priorityBtn) { + InventoryHandler.switchGui(GuiType.PRIORITY); + } else { + super.actionPerformed(btn); + } + } + + @Override + protected boolean drawUpgrades() { + return false; + } + +} \ No newline at end of file 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 00000000..0dd00f87 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java @@ -0,0 +1,33 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseGui; +import appeng.core.localization.GuiText; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerFluidPacketDecoder; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.util.ResourceLocation; + +public class GuiFluidPacketDecoder extends AEBaseGui { + + private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/fluid_packet_decoder.png"); + + public GuiFluidPacketDecoder(InventoryPlayer ipl, TileFluidPacketDecoder tile) { + super(new ContainerFluidPacketDecoder(ipl, tile)); + } + + @Override + public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { + mc.getTextureManager().bindTexture(TEX_BG); + drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize); + } + + @Override + public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) { + fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_FLUID_PACKET_DECODER)), 8, 6, 0x404040); + fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040); + } + +} \ No newline at end of file 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 00000000..1bac12d2 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java @@ -0,0 +1,53 @@ +package com.glodblock.github.client; + +import appeng.api.storage.ITerminalHost; +import appeng.client.gui.implementations.GuiPatternTerm; +import appeng.client.gui.widgets.GuiTabButton; +import appeng.client.render.StackSizeRenderer; +import appeng.container.slot.SlotFake; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.client.render.FluidRenderUtils; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Slot; + +public class GuiFluidPatternTerminal extends GuiPatternTerm { + + private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this); + private GuiTabButton craftingStatusBtn; + + public GuiFluidPatternTerminal(InventoryPlayer inventoryPlayer, ITerminalHost te) { + super(inventoryPlayer, te); + ContainerFluidPatternTerminal container = new ContainerFluidPatternTerminal(inventoryPlayer, te); + container.setGui(this); + this.inventorySlots = container; + Ae2ReflectClient.setGuiContainer(this, container); + } + + @Override + public void initGui() { + super.initGui(); + craftingStatusBtn = Ae2ReflectClient.getCraftingStatusButton(this); + } + + @Override + public void drawSlot(Slot slot) { + if (!(slot instanceof SlotFake && FluidRenderUtils.renderFluidPacketIntoGuiSlot( + slot, slot.getStack(), stackSizeRenderer, fontRenderer))) { + super.drawSlot(slot); + } + } + + @Override + protected void actionPerformed(final GuiButton btn) { + if (btn == craftingStatusBtn) { + InventoryHandler.switchGui(GuiType.FLUID_PAT_TERM_CRAFTING_STATUS); + } else { + super.actionPerformed(btn); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java new file mode 100644 index 00000000..fec68de3 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java @@ -0,0 +1,37 @@ +package com.glodblock.github.client; + +import appeng.api.storage.ITerminalHost; +import appeng.client.gui.implementations.GuiCraftingStatus; +import appeng.client.gui.widgets.GuiTabButton; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.InventoryPlayer; + +import java.io.IOException; + +public class GuiFluidPatternTerminalCraftingStatus extends GuiCraftingStatus { + + private GuiTabButton originalGuiBtn; + + public GuiFluidPatternTerminalCraftingStatus(InventoryPlayer inventoryPlayer, ITerminalHost te) { + super(inventoryPlayer, te); + } + + @Override + public void initGui() { + super.initGui(); + originalGuiBtn = Ae2ReflectClient.getOriginalGuiButton(this); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + if (btn == originalGuiBtn) { + InventoryHandler.switchGui(GuiType.FLUID_PATTERN_TERMINAL); + } else { + super.actionPerformed(btn); + } + } + +} \ 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 00000000..c4181619 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java @@ -0,0 +1,77 @@ +package com.glodblock.github.client; + +import appeng.client.gui.AEBaseGui; +import appeng.core.localization.GuiText; +import appeng.fluids.util.IAEFluidTank; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerIngredientBuffer; +import com.glodblock.github.client.render.FluidRenderUtils; +import com.glodblock.github.common.tile.TileIngredientBuffer; +import com.glodblock.github.handler.ButtonMouseHandler; +import com.glodblock.github.handler.TankMouseHandler; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.util.ResourceLocation; + +import java.io.IOException; + +public class GuiIngredientBuffer extends AEBaseGui { + + private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/ingredient_buffer.png"); + 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 ContainerIngredientBuffer cont; + private final MouseRegionManager mouseRegions = new MouseRegionManager(this); + + public GuiIngredientBuffer(InventoryPlayer ipl, TileIngredientBuffer tile) { + super(new ContainerIngredientBuffer(ipl, tile)); + this.cont = (ContainerIngredientBuffer)inventorySlots; + this.ySize = 222; + for (int i = 0; i < 4; i++) { + mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT, + new TankMouseHandler(cont.getTile().getFluidInventory(), i)); + mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7, + ButtonMouseHandler.dumpTank(cont, i)); + } + } + + @Override + protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException { + if (mouseRegions.onClick(xCoord, yCoord, btn)) { + super.mouseClicked(xCoord, yCoord, btn); + } + } + + @Override + public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { + mc.getTextureManager().bindTexture(TEX_BG); + drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize); + } + + @Override + public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) { + fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_INGREDIENT_BUFFER)), 8, 6, 0x404040); + fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040); + GlStateManager.color(1F, 1F, 1F, 1F); + + IAEFluidTank fluidInv = cont.getTile().getFluidInventory(); + mc.getTextureManager().bindTexture(TextureMap.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.getTankProperties()[i].getCapacity()); + } + GlStateManager.color(1F, 1F, 1F, 1F); + + mouseRegions.render(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 00000000..60dde6ed --- /dev/null +++ b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java @@ -0,0 +1,50 @@ +package com.glodblock.github.client; + +import appeng.api.AEApi; +import appeng.client.gui.implementations.GuiInterface; +import appeng.client.gui.widgets.GuiTabButton; +import appeng.helpers.IInterfaceHost; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.util.Ae2ReflectClient; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; + +import java.io.IOException; + +public class GuiItemDualInterface extends GuiInterface { + + private GuiTabButton switchInterface; + private GuiTabButton priorityBtn; + + public GuiItemDualInterface(final InventoryPlayer inventoryPlayer, final IInterfaceHost te) { + super(inventoryPlayer, te); + } + + @Override + protected void addButtons() { + super.addButtons(); + ItemStack icon = AEApi.instance().definitions().blocks().fluidIface().maybeStack(1).orElse(ItemStack.EMPTY); + switchInterface = new GuiTabButton(guiLeft + 133, guiTop, icon, icon.getDisplayName(), itemRender); + buttonList.add(switchInterface); + priorityBtn = Ae2ReflectClient.getPriorityButton(this); + } + + @Override + protected String getBackground() { + return "guis/interface.png"; + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + if (btn == switchInterface) { + InventoryHandler.switchGui(GuiType.DUAL_FLUID_INTERFACE); + } else if (btn == priorityBtn) { + InventoryHandler.switchGui(GuiType.PRIORITY); + } else { + super.actionPerformed(btn); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/container/ContainerBurette.java b/src/main/java/com/glodblock/github/client/container/ContainerBurette.java new file mode 100644 index 00000000..6a1bf5a7 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerBurette.java @@ -0,0 +1,134 @@ +package com.glodblock.github.client.container; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.container.AEBaseContainer; +import appeng.container.slot.SlotNormal; +import appeng.fluids.container.IFluidSyncContainer; +import appeng.fluids.helper.FluidSyncHelper; +import appeng.fluids.util.IAEFluidTank; +import appeng.util.Platform; +import com.glodblock.github.common.tile.TileBurette; +import com.glodblock.github.interfaces.TankDumpable; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.IContainerListener; +import net.minecraft.item.ItemStack; +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.fluids.capability.IFluidTankProperties; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +public class ContainerBurette extends AEBaseContainer implements IFluidSyncContainer, TankDumpable { + + private final TileBurette tile; + private final FluidSyncHelper fluidSync; + + public ContainerBurette(InventoryPlayer ipl, TileBurette tile) { + super(ipl, tile); + this.tile = tile; + this.fluidSync = new FluidSyncHelper(tile.getFluidInventory(), 0); + addSlotToContainer(new SlotNormal(tile.getInternalInventory(), 0, 52, 53)); + bindPlayerInventory(ipl, 0, 84); + } + + public TileBurette getTile() { + return tile; + } + + public boolean canTranferFluid(boolean into) { + IAEFluidTank tileTank = tile.getFluidInventory(); + IFluidTankProperties tileTankInfo = tileTank.getTankProperties()[0]; + IAEFluidStack tileFluid = tileTank.getFluidInSlot(0); + if (into) { + if (tileFluid != null && tileFluid.getStackSize() >= tileTankInfo.getCapacity()) { + return false; + } + } else if (tileFluid == null) { + return false; + } + ItemStack stack = tile.getInternalInventory().getStackInSlot(0); + if (stack.isEmpty() || !stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) { + return false; + } + IFluidHandlerItem itemTank = Objects.requireNonNull( + stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)); + for (IFluidTankProperties itemTankInfo : itemTank.getTankProperties()) { + if (into) { + if (itemTankInfo.canDrain() && tileTankInfo.canFillFluidType(itemTankInfo.getContents())) { + return true; + } + } else if (itemTankInfo.canFillFluidType(tileFluid.getFluidStack())) { + return true; + } + } + return false; + } + + public void tryTransferFluid(int amount, boolean into) { + ItemStack stack = tile.getInternalInventory().getStackInSlot(0); + if (stack.isEmpty() || !stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) { + return; + } + IAEFluidTank tileTank = tile.getFluidInventory(); + IFluidHandlerItem itemTank = Objects.requireNonNull( + stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)); + if (into) { + transferFluidBetween(itemTank, tileTank, amount); + } else { + transferFluidBetween(tileTank, itemTank, amount); + } + tile.getInternalInventory().setStackInSlot(0, itemTank.getContainer()); + } + + private void transferFluidBetween(IFluidHandler from, IFluidHandler to, int amount) { + // simulated pass to figure out the real amount we can transfer + FluidStack fluid = from.drain(amount, false); + if (fluid == null) { + return; + } + amount = Math.min(amount, to.fill(fluid, false)); + // actually do the transfer + fluid = from.drain(amount, true); + if (fluid != null) { + fluid.amount -= to.fill(fluid, true); + if (fluid.amount > 0) { // just in case the tanks don't behave exactly as simulated + from.fill(fluid, true); + } + } + } + + @Override + public void detectAndSendChanges() { + super.detectAndSendChanges(); + if (Platform.isServer()) { + fluidSync.sendDiff(listeners); + } + } + + @Override + public void addListener(@Nonnull IContainerListener listener) { + super.addListener(listener); + fluidSync.sendFull(Collections.singleton(listener)); + } + + @Override + public void receiveFluidSlots(Map fluids) { + fluidSync.readPacket(fluids); + } + + @Override + public boolean canDumpTank(int index) { + return tile.getFluidInventory().getFluidInSlot(0) != null; + } + + @Override + public void dumpTank(int index) { + tile.getFluidInventory().setFluidInSlot(0, null); + } + +} 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 00000000..220f8b06 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java @@ -0,0 +1,16 @@ +package com.glodblock.github.client.container; + +import appeng.container.AEBaseContainer; +import appeng.container.slot.SlotNormal; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import net.minecraft.entity.player.InventoryPlayer; + +public class ContainerFluidPacketDecoder extends AEBaseContainer { + + public ContainerFluidPacketDecoder(InventoryPlayer ipl, TileFluidPacketDecoder tile) { + super(ipl, tile); + addSlotToContainer(new SlotNormal(tile.getInventory(), 0, 80, 35)); + bindPlayerInventory(ipl, 0, 84); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java new file mode 100644 index 00000000..13905282 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java @@ -0,0 +1,216 @@ +package com.glodblock.github.client.container; + +import appeng.api.AEApi; +import appeng.api.storage.data.IAEItemStack; +import appeng.container.AEBaseContainer; +import appeng.container.slot.SlotFake; +import appeng.container.slot.SlotRestrictedInput; +import appeng.helpers.InventoryAction; +import appeng.util.item.AEItemStack; +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.common.tile.TileFluidPatternEncoder; +import com.glodblock.github.handler.AeItemStackHandler; +import com.glodblock.github.interfaces.AeStackInventory; +import com.glodblock.github.interfaces.PatternConsumer; +import com.glodblock.github.interfaces.SlotFluid; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.FluidPatternDetails; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidTankProperties; +import net.minecraftforge.items.ItemHandlerHelper; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class ContainerFluidPatternEncoder extends AEBaseContainer implements PatternConsumer { + + private final TileFluidPatternEncoder tile; + + public ContainerFluidPatternEncoder(InventoryPlayer ipl, TileFluidPatternEncoder tile) { + super(ipl, tile); + this.tile = tile; + AeItemStackHandler crafting = new AeItemStackHandler(tile.getCraftingSlots()); + AeItemStackHandler output = new AeItemStackHandler(tile.getOutputSlots()); + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + addSlotToContainer(new SlotFluidConvertingFake(crafting, y * 3 + x, 23 + x * 18, 17 + y * 18)); + } + addSlotToContainer(new SlotFluidConvertingFake(output, y, 113, 17 + y * 18)); + } + addSlotToContainer(new SlotRestrictedInput( + SlotRestrictedInput.PlacableItemType.BLANK_PATTERN, tile.getInventory(), 0, 138, 20, ipl)); + addSlotToContainer(new SlotRestrictedInput( + SlotRestrictedInput.PlacableItemType.ENCODED_PATTERN, tile.getInventory(), 1, 138, 50, ipl)); + bindPlayerInventory(ipl, 0, 84); + } + + public TileFluidPatternEncoder getTile() { + return tile; + } + + public boolean canEncodePattern() { + if (isNotPattern(tile.getInventory().getStackInSlot(0)) && isNotPattern(tile.getInventory().getStackInSlot(1))) { + return false; + } + find_input: + { + for (IAEItemStack stack : tile.getCraftingSlots()) { + if (stack != null && stack.getStackSize() > 0) { + break find_input; + } + } + return false; + } + for (IAEItemStack stack : tile.getOutputSlots()) { + if (stack != null && stack.getStackSize() > 0) { + return true; + } + } + return false; + } + + private static boolean isNotPattern(ItemStack stack) { + return stack.isEmpty() || !(AEApi.instance().definitions().materials().blankPattern().isSameAs(stack) + || (stack.getItem() instanceof ItemFluidEncodedPattern)); + } + + public void encodePattern() { + if (canEncodePattern()) { + // if there is an encoded pattern, overwrite it; otherwise, consume a blank + if (tile.getInventory().getStackInSlot(1).isEmpty()) { + tile.getInventory().extractItem(0, 1, false); // this better work + } + ItemStack patternStack = new ItemStack(FCItems.DENSE_ENCODED_PATTERN); + FluidPatternDetails pattern = new FluidPatternDetails(patternStack); + pattern.setInputs(collectAeInventory(tile.getCraftingSlots())); + pattern.setOutputs(collectAeInventory(tile.getOutputSlots())); + tile.getInventory().setStackInSlot(1, pattern.writeToStack()); + } + } + + private static IAEItemStack[] collectAeInventory(AeStackInventory inv) { + // see note at top of DensePatternDetails + List acc = new ArrayList<>(); + for (IAEItemStack stack : inv) { + if (stack != null) { + if (stack.getItem() instanceof ItemFluidPacket) { + IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack)); + if (dropStack != null) { + acc.add(dropStack); + continue; + } + } + acc.add(stack); + } + } + return acc.toArray(new IAEItemStack[0]); + } + + // adapted from ae2's AEBaseContainer#doAction + @Override + public void doAction(EntityPlayerMP player, InventoryAction action, int slotId, long id) { + Slot slot = getSlot(slotId); + if (slot instanceof SlotFluidConvertingFake) { + final ItemStack stack = player.inventory.getItemStack(); + switch (action) { + case PICKUP_OR_SET_DOWN: + if (stack.isEmpty()) { + slot.putStack(ItemStack.EMPTY); + } else { + ((SlotFluidConvertingFake)slot).putConvertedStack(stack.copy()); + } + break; + case PLACE_SINGLE: + if (!stack.isEmpty()) { + ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1)); + } + break; + case SPLIT_OR_PLACE_SINGLE: + ItemStack inSlot = slot.getStack(); + if (!inSlot.isEmpty()) { + if (stack.isEmpty()) { + slot.putStack(ItemHandlerHelper.copyStackWithSize(inSlot, Math.max(1, inSlot.getCount() - 1))); + } else if (stack.isItemEqual(inSlot)) { + slot.putStack(ItemHandlerHelper.copyStackWithSize(inSlot, + Math.min(inSlot.getMaxStackSize(), inSlot.getCount() + 1))); + } else { + ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1)); + } + } else if (!stack.isEmpty()) { + ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1)); + } + break; + } + } else { + super.doAction(player, action, slotId, id); + } + } + + @Override + public void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) { + copyStacks(inputs, tile.getCraftingSlots()); + copyStacks(outputs, tile.getOutputSlots()); + } + + private static void copyStacks(IAEItemStack[] src, AeStackInventory dest) { + int bound = Math.min(src.length, dest.getSlotCount()); + for (int i = 0; i < bound; i++) { + dest.setStack(i, src[i]); + } + } + + private static class SlotFluidConvertingFake extends SlotFake implements SlotFluid { + + private final AeStackInventory inv; + + public SlotFluidConvertingFake(AeItemStackHandler inv, int idx, int x, int y) { + super(inv, idx, x, y); + this.inv = inv.getAeInventory(); + } + + @Override + public void putStack(ItemStack stack) { + inv.setStack(getSlotIndex(), AEItemStack.fromItemStack(stack)); + } + + @Override + public void setAeStack(@Nullable IAEItemStack stack, boolean sync) { + inv.setStack(getSlotIndex(), stack); + } + + public void putConvertedStack(ItemStack stack) { + if (stack.isEmpty()) { + setAeStack(null, false); + return; + } else if (stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) { + IFluidTankProperties[] tanks = Objects.requireNonNull( + stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) + .getTankProperties(); + for (IFluidTankProperties tank : tanks) { + IAEItemStack aeStack = ItemFluidPacket.newAeStack(tank.getContents()); + if (aeStack != null) { + setAeStack(aeStack, false); + return; + } + } + } + putStack(stack); + } + + @Nullable + @Override + public IAEItemStack getAeStack() { + return inv.getStack(getSlotIndex()); + } + + } + +} 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 00000000..306e3b00 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java @@ -0,0 +1,148 @@ +package com.glodblock.github.client.container; + +import appeng.api.AEApi; +import appeng.api.definitions.IDefinitions; +import appeng.api.storage.ITerminalHost; +import appeng.api.storage.data.IAEItemStack; +import appeng.container.implementations.ContainerPatternTerm; +import appeng.util.item.AEItemStack; +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.common.part.PartFluidPatternTerminal; +import com.glodblock.github.interfaces.PatternConsumer; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.Ae2Reflect; +import com.glodblock.github.util.FluidPatternDetails; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class ContainerFluidPatternTerminal extends ContainerPatternTerm implements PatternConsumer { + + private final Slot[] craftingSlots; + private final Slot[] outputSlots; + private final Slot patternSlotIN; + private final Slot patternSlotOUT; + + public ContainerFluidPatternTerminal(InventoryPlayer ip, ITerminalHost monitorable) { + super(ip, monitorable); + craftingSlots = Ae2Reflect.getCraftingSlots(this); + outputSlots = Ae2Reflect.getOutputSlots(this); + patternSlotIN = Ae2Reflect.getPatternSlotIn(this); + patternSlotOUT = Ae2Reflect.getPatternSlotOut(this); + } + + @Override + public void encode() { + if (!checkHasFluidPattern()) { + super.encode(); + return; + } + ItemStack stack = this.patternSlotOUT.getStack(); + if (stack.isEmpty()) { + stack = this.patternSlotIN.getStack(); + if (stack.isEmpty() || !isPattern(stack)) { + return; + } + if (stack.getCount() == 1) { + this.patternSlotIN.putStack(ItemStack.EMPTY); + } else { + stack.shrink(1); + } + encodeFluidPattern(); + } else if (isPattern(stack)) { + encodeFluidPattern(); + } + } + + private static boolean isPattern(final ItemStack output) { + if (output.isEmpty()) { + return false; + } + if (output.getItem() instanceof ItemFluidEncodedPattern) { + return true; + } + final IDefinitions defs = AEApi.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.craftingSlots) { + 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.outputSlots) { + 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 void encodeFluidPattern() { + ItemStack patternStack = new ItemStack(FCItems.DENSE_ENCODED_PATTERN); + FluidPatternDetails pattern = new FluidPatternDetails(patternStack); + pattern.setInputs(collectInventory(craftingSlots)); + pattern.setOutputs(collectInventory(outputSlots)); + patternSlotOUT.putStack(pattern.writeToStack()); + } + + private static IAEItemStack[] collectInventory(Slot[] slots) { + // see note at top of DensePatternDetails + List acc = new ArrayList<>(); + for (Slot slot : slots) { + ItemStack stack = slot.getStack(); + if (stack.isEmpty()) { + continue; + } + if (stack.getItem() instanceof ItemFluidPacket) { + IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack)); + if (dropStack != null) { + acc.add(dropStack); + continue; + } + } + IAEItemStack aeStack = AEItemStack.fromItemStack(stack); + if (aeStack == null) { + continue; + } + acc.add(aeStack); + } + return acc.toArray(new IAEItemStack[0]); + } + + @Override + public void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) { + if (getPatternTerminal() instanceof PartFluidPatternTerminal) { + ((PartFluidPatternTerminal)getPatternTerminal()).onChangeCrafting(inputs, outputs); + } + } + +} 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 00000000..9cab2265 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java @@ -0,0 +1,40 @@ +package com.glodblock.github.client.container; + +import appeng.container.AEBaseContainer; +import appeng.container.slot.SlotNormal; +import com.glodblock.github.common.tile.TileIngredientBuffer; +import com.glodblock.github.interfaces.TankDumpable; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraftforge.items.IItemHandler; + +public class ContainerIngredientBuffer extends AEBaseContainer implements TankDumpable { + + private final TileIngredientBuffer tile; + + public ContainerIngredientBuffer(InventoryPlayer ipl, TileIngredientBuffer tile) { + super(ipl, tile); + this.tile = tile; + IItemHandler inv = tile.getInternalInventory(); + for (int i = 0; i < 9; i++) { + addSlotToContainer(new SlotNormal(inv, i, 8 + 18 * i, 108)); + } + bindPlayerInventory(ipl, 0, 140); + } + + public 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/GuiFluidPatternEncoder.java b/src/main/java/com/glodblock/github/client/container/GuiFluidPatternEncoder.java new file mode 100644 index 00000000..1ca5dfde --- /dev/null +++ b/src/main/java/com/glodblock/github/client/container/GuiFluidPatternEncoder.java @@ -0,0 +1,107 @@ +package com.glodblock.github.client.container; + +import appeng.api.storage.data.IAEItemStack; +import appeng.client.gui.AEBaseGui; +import appeng.client.render.StackSizeRenderer; +import appeng.core.localization.GuiText; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.render.FluidRenderUtils; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.tile.TileFluidPatternEncoder; +import com.glodblock.github.interfaces.SlotFluid; +import com.glodblock.github.inventory.slot.SlotSingleItem; +import com.glodblock.github.network.CPacketEncodePattern; +import com.glodblock.github.util.Ae2ReflectClient; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class GuiFluidPatternEncoder extends AEBaseGui { + + private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/fluid_pattern_encoder.png"); + + private final ContainerFluidPatternEncoder cont; + private final MouseRegionManager mouseRegions = new MouseRegionManager(this); + private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this); + + public GuiFluidPatternEncoder(InventoryPlayer ipl, TileFluidPatternEncoder tile) { + super(new ContainerFluidPatternEncoder(ipl, tile)); + this.cont = (ContainerFluidPatternEncoder)inventorySlots; + mouseRegions.addRegion(141, 38, 10, 10, new MouseRegionManager.Handler() { + @Override + public List getTooltip() { + return Collections.singletonList(I18n.format(NameConst.TT_ENCODE_PATTERN)); + } + + @Override + public boolean onClick(int button) { + if (button == 0) { + if (cont.canEncodePattern()) { + FluidCraft.proxy.netHandler.sendToServer(new CPacketEncodePattern()); + } + return true; + } + return false; + } + }); + } + + @Override + protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException { + if (mouseRegions.onClick(xCoord, yCoord, btn)) { + super.mouseClicked(xCoord, yCoord, btn); + } + } + + @Override + public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { + mc.getTextureManager().bindTexture(TEX_BG); + drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize); + } + + @Override + public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) { + fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_FLUID_PATTERN_ENCODER)), 8, 6, 0x404040); + fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040); + mouseRegions.render(mouseX, mouseY); + } + + @Override + public void drawSlot(Slot slot) { + if (slot instanceof SlotFluid) { + IAEItemStack stack = ((SlotFluid)slot).getAeStack(); + if (FluidRenderUtils.renderFluidPacketIntoGuiSlot(slot, stack, stackSizeRenderer, fontRenderer)) { + return; + } + super.drawSlot(new SlotSingleItem(slot)); + stackSizeRenderer.renderStackSize(fontRenderer, stack, slot.xPos, slot.yPos); + } else { + super.drawSlot(slot); + } + } + + @Override + @Nonnull + public List getItemToolTip(ItemStack stack) { + if (stack.getItem() instanceof ItemFluidPacket) { + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + if (fluid != null) { + return Arrays.asList(fluid.getLocalizedName(), String.format(TextFormatting.GRAY + "%,d mB", fluid.amount)); + } + } + return super.getItemToolTip(stack); + } + +} diff --git a/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java b/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java new file mode 100644 index 00000000..7be8e3e8 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java @@ -0,0 +1,67 @@ +package com.glodblock.github.client.model; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.util.Ae2ReflectClient; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ICustomModelLoader; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.client.model.PerspectiveMapWrapper; +import net.minecraftforge.common.model.IModelState; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +public class DenseEncodedPatternModel implements IModel { + + private static final ResourceLocation BASE_MODEL = FluidCraft.resource("item/dense_encoded_pattern"); + + @Override + @Nonnull + public Collection getDependencies() { + return Collections.singletonList(BASE_MODEL); + } + + // adapted from ae2's ItemEncodedPatternModel#bake + @Override + @Nonnull + public IBakedModel bake(@Nonnull IModelState state, @Nonnull VertexFormat format, @Nonnull Function bakedTextureGetter) { + IBakedModel baseModel; + try { + baseModel = ModelLoaderRegistry.getModel(BASE_MODEL).bake(state, format, bakedTextureGetter); + } catch (Exception e) { + throw new RuntimeException(e); + } + return Ae2ReflectClient.bakeEncodedPatternModel(baseModel, PerspectiveMapWrapper.getTransforms(state)); + } + + public static class Loader implements ICustomModelLoader { + + @Override + public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { + // NO-OP + } + + @Override + public boolean accepts(ResourceLocation modelLocation) { + // modelLocation will probably be a ModelResourceLocation, so using compareTo lets us bypass the + // ModelResourceLocation equality behaviour and fall back to that of ResourceLocation + return modelLocation.compareTo(NameConst.MODEL_DENSE_ENCODED_PATTERN) == 0; + } + + @Override + @Nonnull + public IModel loadModel(@Nonnull ResourceLocation modelLocation) { + return new DenseEncodedPatternModel(); + } + + } + +} \ No newline at end of file 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 00000000..5f8ec5dd --- /dev/null +++ b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java @@ -0,0 +1,234 @@ +package com.glodblock.github.client.model; + +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.util.FluidKey; +import com.glodblock.github.util.NameConst; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.*; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import net.minecraftforge.client.model.ICustomModelLoader; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.ItemLayerModel; +import net.minecraftforge.common.model.IModelState; +import net.minecraftforge.common.model.TRSRTransformation; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.util.vector.Vector3f; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.vecmath.Matrix4f; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +public class FluidPacketModel implements IModel { + + @SuppressWarnings("deprecation") + private static final ItemCameraTransforms CAMERA_TRANSFORMS = new ItemCameraTransforms( + new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.1875F, 0.0625F), new Vector3f(0.55F, 0.55F, 0.55F)), + new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.1875F, 0.0625F), new Vector3f(0.55F, 0.55F, 0.55F)), + new ItemTransformVec3f(new Vector3f(0F, -90F, 25F), new Vector3f(0.070625F, 0.2F, 0.070625F), new Vector3f(0.68F, 0.68F, 0.68F)), + new ItemTransformVec3f(new Vector3f(0F, -90F, 25F), new Vector3f(0.070625F, 0.2F, 0.070625F), new Vector3f(0.68F, 0.68F, 0.68F)), + new ItemTransformVec3f(new Vector3f(0F, 180F, 0F), new Vector3f(0F, 0.8125F, 0.4375F), new Vector3f(1F, 1F, 1F)), + ItemTransformVec3f.DEFAULT, + new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.125F, 0F), new Vector3f(0.5F, 0.5F, 0.5F)), + new ItemTransformVec3f(new Vector3f(0F, 180F, 0F), new Vector3f(0F, 0F, 0F), new Vector3f(1F, 1F, 1F))); + + @Override + @Nonnull + public IBakedModel bake(@Nonnull IModelState state, @Nonnull VertexFormat format, @Nonnull Function textureBakery) { + return new BakedFluidPacketModel(state, format); + } + + public static class Loader implements ICustomModelLoader { + + @Override + public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { + // NO-OP + } + + @Override + public boolean accepts(ResourceLocation modelLocation) { + // modelLocation will probably be a ModelResourceLocation, so using compareTo lets us bypass the + // ModelResourceLocation equality behaviour and fall back to that of ResourceLocation + return modelLocation.compareTo(NameConst.MODEL_FLUID_PACKET) == 0; + } + + @Override + @Nonnull + public IModel loadModel(@Nonnull ResourceLocation modelLocation) { + return new FluidPacketModel(); + } + + } + + private static class BakedFluidPacketModel implements IBakedModel { + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private final Optional modelTransform; + private final VertexFormat vertexFormat; + private final OverrideCache overrides; + private final OverrideCache.OverrideModel defaultOverride; + + public BakedFluidPacketModel(IModelState modelState, VertexFormat vertexFormat) { + this.modelTransform = modelState.apply(Optional.empty()); + this.vertexFormat = vertexFormat; + this.overrides = new OverrideCache(); + this.defaultOverride = overrides.resolve(new FluidStack(FluidRegistry.WATER, Fluid.BUCKET_VOLUME)); + } + + @Override + @Nonnull + public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) { + return defaultOverride.getQuads(state, side, rand); + } + + @Override + public boolean isAmbientOcclusion() { + return defaultOverride.isAmbientOcclusion(); + } + + @Override + public boolean isGui3d() { + return defaultOverride.isGui3d(); + } + + @Override + public boolean isBuiltInRenderer() { + return defaultOverride.isBuiltInRenderer(); + } + + @Override + @Nonnull + public TextureAtlasSprite getParticleTexture() { + return defaultOverride.getParticleTexture(); + } + + @Override + public boolean isAmbientOcclusion(@Nonnull IBlockState state) { + return defaultOverride.isAmbientOcclusion(state); + } + + @SuppressWarnings("deprecation") + @Override + @Nonnull + public ItemCameraTransforms getItemCameraTransforms() { + return defaultOverride.getItemCameraTransforms(); + } + + @Override + @Nonnull + public Pair handlePerspective(@Nonnull ItemCameraTransforms.TransformType cameraTransformType) { + return defaultOverride.handlePerspective(cameraTransformType); + } + + @Override + @Nonnull + public ItemOverrideList getOverrides() { + return overrides; + } + + private class OverrideCache extends ItemOverrideList { + + private final Cache cache = CacheBuilder.newBuilder() + .maximumSize(1000) // cache params borrowed from Tinkers' Construct model system, which is under MIT + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + + OverrideCache() { + super(Collections.emptyList()); + } + + @Override + @Nonnull + public IBakedModel handleItemState(@Nonnull IBakedModel originalModel, ItemStack stack, + @Nullable World world, @Nullable EntityLivingBase entity) { + if (!(stack.getItem() instanceof ItemFluidPacket)) { + return originalModel; + } + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + return fluid != null ? resolve(fluid) : originalModel; + } + + OverrideModel resolve(FluidStack fluid) { + try { + return cache.get(new FluidKey(fluid), () -> new OverrideModel(fluid)); + } catch (ExecutionException e) { + throw new IllegalStateException(e); + } + } + + class OverrideModel implements IBakedModel { + + private final TextureAtlasSprite texture; + private final List quads; + + OverrideModel(FluidStack fluidStack) { + this.texture = Minecraft.getMinecraft().getTextureMapBlocks() + .getAtlasSprite(fluidStack.getFluid().getStill(fluidStack).toString()); + this.quads = ItemLayerModel.getQuadsForSprite(1, texture, vertexFormat, modelTransform); + } + + @Override + @Nonnull + public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) { + return quads; + } + + @Override + public boolean isAmbientOcclusion() { + return false; + } + + @Override + public boolean isGui3d() { + return false; + } + + @Override + public boolean isBuiltInRenderer() { + return false; + } + + @Override + @Nonnull + public TextureAtlasSprite getParticleTexture() { + return texture; + } + + @Override + @Nonnull + @SuppressWarnings("deprecation") + public ItemCameraTransforms getItemCameraTransforms() { + return CAMERA_TRANSFORMS; + } + + @Override + @Nonnull + public ItemOverrideList getOverrides() { + return OverrideCache.this; + } + + } + + } + + } + +} 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 00000000..bc8bd8f5 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java @@ -0,0 +1,62 @@ +package com.glodblock.github.client.render; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.HashMap; +import java.util.Map; + +public class DropColourHandler { + + private final Map colourCache = new HashMap<>(); + + @SubscribeEvent + public void onTextureMapStitch(TextureStitchEvent event) { + if (event.getMap() == Minecraft.getMinecraft().getTextureMapBlocks()) { + colourCache.clear(); + } + } + + public int getColour(FluidStack fluidStack) { + Fluid fluid = fluidStack.getFluid(); + int colour = fluid.getColor(fluidStack); + return colour != -1 ? colour : getColour(fluid); + } + + public int getColour(Fluid fluid) { + Integer cached = colourCache.get(fluid.getName()); + if (cached != null) { + return cached; + } + int colour = fluid.getColor(); + if (colour == -1) { + TextureAtlasSprite sprite = Minecraft.getMinecraft().getTextureMapBlocks() + .getTextureExtry(fluid.getStill().toString()); + if (sprite != null && sprite.getFrameCount() > 0) { + int[][] image = sprite.getFrameTextureData(0); + int r = 0, g = 0, b = 0, count = 0; + for (int[] row : image) { + for (int pixel : row) { + if (((pixel >> 24) & 0xFF) > 127) { // is alpha above 50%? + r += (pixel >> 16) & 0xFF; + g += (pixel >> 8) & 0xFF; + b += pixel & 0xFF; + ++count; + } + } + } + if (count > 0) { + // probably shouldn't need to mask each component by 0xFF + colour = ((r / count) << 16) | ((g / count) << 8) | (b / count); + } + } + } + colourCache.put(fluid.getName(), colour); + return colour; + } + +} diff --git a/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java b/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java new file mode 100644 index 00000000..add1ac29 --- /dev/null +++ b/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java @@ -0,0 +1,127 @@ +package com.glodblock.github.client.render; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; +import appeng.client.render.StackSizeRenderer; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nullable; + +public class FluidRenderUtils { + + @Nullable + public static TextureAtlasSprite prepareRender(@Nullable FluidStack fluidStack) { + if (fluidStack == null) { + return null; + } + Fluid fluid = fluidStack.getFluid(); + TextureAtlasSprite sprite = Minecraft.getMinecraft().getTextureMapBlocks() + .getAtlasSprite(fluid.getStill(fluidStack).toString()); + int colour = fluid.getColor(fluidStack); + GlStateManager.color( + ((colour >> 16) & 0xFF) / 255F, + ((colour >> 8) & 0xFF) / 255F, + (colour & 0xFF) / 255F, + ((colour >> 24) & 0xFF) / 255F); + return sprite; + } + + 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, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + 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); + double y1 = y + height - fluidHeight, y2 = y1 + Math.min(fluidHeight, width); + double u1 = sprite.getMinU(), v1 = sprite.getMinV(), u2 = sprite.getMaxU(), v2 = sprite.getMaxV(); + if (fluidHeight < width) { + v2 = v1 + (v2 - v1) * (fluidHeight / (double)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 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, + @Nullable FluidStack fluidStack, int capacity) { + if (fluidStack != null) { + TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluidStack); + if (sprite != null) { + doRenderFluid(tess, buf, x, y, width, height, sprite, fluidStack.amount / (double)capacity); + } + } + } + + public static void renderFluidIntoGuiCleanly(int x, int y, int width, int height, + @Nullable IAEFluidStack aeFluidStack, int capacity) { + Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + Tessellator tess = Tessellator.getInstance(); + renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, aeFluidStack, capacity); + GlStateManager.color(1F, 1F, 1F, 1F); + } + + public static void renderFluidIntoGuiCleanly(int x, int y, int width, int height, + @Nullable FluidStack fluidStack, int capacity) { + Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + Tessellator tess = Tessellator.getInstance(); + renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, fluidStack, capacity); + GlStateManager.color(1F, 1F, 1F, 1F); + } + + public static boolean renderFluidIntoGuiSlot(Slot slot, @Nullable FluidStack fluid, + StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) { + if (fluid == null || fluid.amount <= 0) { + return false; + } + renderFluidIntoGuiCleanly(slot.xPos, slot.yPos, 16, 16, fluid, fluid.amount); + stackSizeRenderer.renderStackSize(fontRenderer, ItemFluidDrop.newAeStack(fluid), slot.xPos, slot.yPos); + return true; + } + + public static boolean renderFluidPacketIntoGuiSlot(Slot slot, @Nullable IAEItemStack stack, + StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) { + return stack != null && stack.getItem() instanceof ItemFluidPacket + && renderFluidIntoGuiSlot(slot, ItemFluidPacket.getFluidStack(stack), stackSizeRenderer, fontRenderer); + } + + 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/client/render/RenderIngredientBuffer.java b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java new file mode 100644 index 00000000..656b89ac --- /dev/null +++ b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java @@ -0,0 +1,81 @@ +package com.glodblock.github.client.render; + +import com.glodblock.github.common.tile.TileIngredientBuffer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.capability.IFluidTankProperties; +import net.minecraftforge.items.IItemHandler; +import org.lwjgl.opengl.GL11; + +public class RenderIngredientBuffer extends TileEntitySpecialRenderer { + + private static final double d = 0.45D; + + @Override + public void render(TileIngredientBuffer tile, double x, double y, double z, float partialTicks, int destroyStage, float alpha) { + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.enableLighting(); + GlStateManager.pushMatrix(); + GlStateManager.translate(x + 0.5D, y + 0.5D, z + 0.5D); + bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + + IItemHandler inv = tile.getInternalInventory(); + for (int i = 0; i < inv.getSlots(); i++) { + ItemStack stack = inv.getStackInSlot(i); + if (!stack.isEmpty()) { + GlStateManager.pushMatrix(); + float scale = stack.getItem() instanceof ItemBlock ? 0.36F : 0.64F; + GlStateManager.scale(scale, scale, scale); + GlStateManager.rotate((getWorld().getTotalWorldTime() + partialTicks) * 6F, 0F, 1F, 0F); + RenderItem renderer = Minecraft.getMinecraft().getRenderItem(); + renderer.renderItem(stack, renderer.getItemModelWithOverrides(stack, getWorld(), null)); + GlStateManager.popMatrix(); + break; + } + } + + for (IFluidTankProperties tank : tile.getFluidInventory().getTankProperties()) { + TextureAtlasSprite fluidSprite = FluidRenderUtils.prepareRender(tank.getContents()); + if (fluidSprite != null) { + Tessellator tess = Tessellator.getInstance(); + BufferBuilder buf = tess.getBuffer(); + // not necessarily the most efficient way to draw a cube, but probably the least tedious + GlStateManager.pushMatrix(); + drawFace(tess, buf, fluidSprite); + for (int i = 0; i < 3; i++) { + GlStateManager.rotate(90F, 1F, 0F, 0F); + drawFace(tess, buf, fluidSprite); + } + GlStateManager.rotate(90F, 0F, 0F, 1F); + drawFace(tess, buf, fluidSprite); + GlStateManager.rotate(180F, 0F, 0F, 1F); + drawFace(tess, buf, fluidSprite); + GlStateManager.popMatrix(); + break; + } + } + + GlStateManager.color(1F, 1F, 1F, 1F); + GlStateManager.popMatrix(); + } + + private static void drawFace(Tessellator tess, BufferBuilder buf, TextureAtlasSprite sprite) { + buf.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buf.pos(-d, d, -d).tex(sprite.getMinU(), sprite.getMinV()).endVertex(); + buf.pos(-d, d, d).tex(sprite.getMinU(), sprite.getMaxV()).endVertex(); + buf.pos(d, d, d).tex(sprite.getMaxU(), sprite.getMaxV()).endVertex(); + buf.pos(d, d, -d).tex(sprite.getMaxU(), sprite.getMinV()).endVertex(); + tess.draw(); + } + +} diff --git a/src/main/java/com/glodblock/github/common/block/BlockBurette.java b/src/main/java/com/glodblock/github/common/block/BlockBurette.java new file mode 100644 index 00000000..583a5e81 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockBurette.java @@ -0,0 +1,38 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import com.glodblock.github.common.tile.TileBurette; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BlockBurette extends AEBaseTileBlock { + + public BlockBurette() { + super(Material.IRON); + setTileEntity(TileBurette.class); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { + if (player.isSneaking()) { + return false; + } + TileBurette tile = getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + InventoryHandler.openGui(player, world, pos, facing, GuiType.PRECISION_BURETTE); + } + return true; + } + return false; + } + +} 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 00000000..6dee9140 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java @@ -0,0 +1,85 @@ +package com.glodblock.github.common.block; + +import appeng.api.util.IOrientable; +import appeng.block.AEBaseTileBlock; +import appeng.util.Platform; +import com.glodblock.github.common.tile.TileDualInterface; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.properties.PropertyDirection; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockDualInterface extends AEBaseTileBlock { + + private static final PropertyBool OMNIDIRECTIONAL = PropertyBool.create("omnidirectional"); + private static final PropertyDirection FACING = PropertyDirection.create("facing"); + + public BlockDualInterface() { + super(Material.IRON); + this.setTileEntity(TileDualInterface.class); + } + + @SuppressWarnings("rawtypes") + @Override + protected IProperty[] getAEStates() { + return new IProperty[] { OMNIDIRECTIONAL }; + } + + @Override + protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, OMNIDIRECTIONAL, FACING); + } + + @Override + public boolean onActivated(final World w, final BlockPos pos, final EntityPlayer p, + final EnumHand hand, final @Nullable ItemStack heldItem, final EnumFacing side, + final float hitX, final float hitY, final float hitZ) { + if (p.isSneaking()) { + return false; + } + final TileDualInterface tg = this.getTileEntity(w, pos); + if (tg != null) { + if (Platform.isServer()) { + InventoryHandler.openGui(p, w, pos, side, GuiType.DUAL_ITEM_INTERFACE); + } + return true; + } + return false; + } + + @SuppressWarnings("deprecation") + @Override + @Nonnull + public IBlockState getActualState(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos) { + TileDualInterface te = this.getTileEntity(world, pos); + return te == null ? state + : state.withProperty(OMNIDIRECTIONAL, te.isOmniDirectional()).withProperty(FACING, te.getForward()); + } + + @Override + protected boolean hasCustomRotation() { + return true; + } + + @Override + protected void customRotateBlock(final IOrientable rotatable, final EnumFacing axis) { + if (rotatable instanceof TileDualInterface) { + ((TileDualInterface)rotatable).setSide(axis); + } + } + +} 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 00000000..16fbbbad --- /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(Material.IRON); + setTileEntity(TileFluidDiscretizer.class); + } + +} 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 00000000..24363014 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java @@ -0,0 +1,38 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BlockFluidPacketDecoder extends AEBaseTileBlock { + + public BlockFluidPacketDecoder() { + super(Material.IRON); + setTileEntity(TileFluidPacketDecoder.class); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { + if (player.isSneaking()) { + return false; + } + TileFluidPacketDecoder tile = getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + InventoryHandler.openGui(player, world, pos, facing, GuiType.FLUID_PACKET_DECODER); + } + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java b/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java new file mode 100644 index 00000000..4b84a3bf --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java @@ -0,0 +1,38 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import com.glodblock.github.common.tile.TileFluidPatternEncoder; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BlockFluidPatternEncoder extends AEBaseTileBlock { + + public BlockFluidPatternEncoder() { + super(Material.IRON); + setTileEntity(TileFluidPatternEncoder.class); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { + if (player.isSneaking()) { + return false; + } + TileFluidPatternEncoder tile = getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + InventoryHandler.openGui(player, world, pos, facing, GuiType.FLUID_PATTERN_ENCODER); + } + return true; + } + return false; + } + +} 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 00000000..f01dd2f7 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java @@ -0,0 +1,58 @@ +package com.glodblock.github.common.block; + +import appeng.block.AEBaseTileBlock; +import com.glodblock.github.common.tile.TileIngredientBuffer; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nonnull; + +public class BlockIngredientBuffer extends AEBaseTileBlock { + + public BlockIngredientBuffer() { + super(Material.IRON); + setTileEntity(TileIngredientBuffer.class); + setOpaque(false); + this.lightOpacity = 4; + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { + if (player.isSneaking()) { + return false; + } + TileIngredientBuffer tile = getTileEntity(world, pos); + if (tile != null) { + if (!world.isRemote) { + InventoryHandler.openGui(player, world, pos, facing, GuiType.INGREDIENT_BUFFER); + } + return true; + } + return false; + } + + @SideOnly(Side.CLIENT) + @Override + @Nonnull + public BlockRenderLayer getRenderLayer() { + return BlockRenderLayer.CUTOUT; + } + + @SuppressWarnings("deprecation") + @Override + public boolean isFullCube(@Nonnull IBlockState state) { + return false; + } + +} diff --git a/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java b/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java new file mode 100644 index 00000000..b9040d06 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java @@ -0,0 +1,184 @@ +package com.glodblock.github.common.component; + +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.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.me.helpers.AENetworkProxy; +import appeng.util.inv.InvOperation; +import com.google.common.collect.ImmutableSet; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +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); // fluid interface supports no upgrades, so this is fine + } + + public int getPriority() { + return itemDuality.getPriority(); // both ifaces should always have the same prio + } + + public void setPriority(final int newValue) { + itemDuality.setPriority(newValue); + fluidDuality.setPriority(newValue); + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return itemDuality.hasCapability(capability, facing) || fluidDuality.hasCapability(capability, facing); + } + + @Nullable + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + T capInst = itemDuality.getCapability(capability, facing); + return capInst != null ? 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(); + } + + // item interface behaviour + + 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, InventoryCrafting 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 NBTTagCompound data) { + NBTTagCompound itemIfaceTag = new NBTTagCompound(), fluidIfaceTag = new NBTTagCompound(); + itemDuality.writeToNBT(itemIfaceTag); + fluidDuality.writeToNBT(fluidIfaceTag); + data.setTag("itemDuality", itemIfaceTag); + data.setTag("fluidDuality", fluidIfaceTag); + } + + public void readFromNBT(final NBTTagCompound data) { + itemDuality.readFromNBT(data.getCompoundTag("itemDuality")); + fluidDuality.readFromNBT(data.getCompoundTag("fluidDuality")); + } + +} 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 00000000..c290ef1c --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java @@ -0,0 +1,131 @@ +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.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.NonNullList; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.translation.I18n; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +public class ItemFluidDrop extends Item { + + @Override + public void getSubItems(@Nonnull CreativeTabs tab,@Nonnull NonNullList items) { + if (isInCreativeTab(tab)) { + items.add(newStack(new FluidStack(FluidRegistry.WATER, 1))); + items.add(newStack(new FluidStack(FluidRegistry.LAVA, 1))); + } + } + + @SuppressWarnings("deprecation") + @Override + @Nonnull + public String getItemStackDisplayName(@Nonnull ItemStack stack) { + FluidStack fluid = getFluidStack(stack); + // would like to use I18n::format instead of this deprecated function, but that only exists on the client :/ + return I18n.translateToLocalFormatted(getTranslationKey(stack) + ".name", fluid != null ? fluid.getLocalizedName() : "???"); + } + + @SuppressWarnings("deprecation") + @Override + public void addInformation(@Nonnull ItemStack stack, @Nullable World world, @Nonnull List tooltip, @Nonnull ITooltipFlag flags) { + FluidStack fluid = getFluidStack(stack); + if (fluid != null) { + tooltip.add(String.format(TextFormatting.GRAY + "%s, 1 mB", fluid.getLocalizedName())); + } else { + tooltip.add(TextFormatting.RED + I18n.translateToLocal(NameConst.TT_INVALID_FLUID)); + } + } + + @Nullable + public static FluidStack getFluidStack(ItemStack stack) { + if (stack.isEmpty() || stack.getItem() != FCItems.FLUID_DROP || !stack.hasTagCompound()) { + return null; + } + NBTTagCompound tag = Objects.requireNonNull(stack.getTagCompound()); + if (!tag.hasKey("Fluid", Constants.NBT.TAG_STRING)) { + return null; + } + Fluid fluid = FluidRegistry.getFluid(tag.getString("Fluid")); + if (fluid == null) { + return null; + } + FluidStack fluidStack = new FluidStack(fluid, stack.getCount()); + if (tag.hasKey("FluidTag", Constants.NBT.TAG_COMPOUND)) { + fluidStack.tag = tag.getCompoundTag("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; + } + + public static ItemStack newStack(@Nullable FluidStack fluid) { + if (fluid == null || fluid.amount <= 0) { + return ItemStack.EMPTY; + } + ItemStack stack = new ItemStack(FCItems.FLUID_DROP, fluid.amount); + NBTTagCompound tag = new NBTTagCompound(); + tag.setString("Fluid", fluid.getFluid().getName()); + if (fluid.tag != null) { + tag.setTag("FluidTag", fluid.tag); + } + stack.setTagCompound(tag); + return stack; + } + + @Nullable + public static IAEItemStack newAeStack(@Nullable FluidStack fluid) { + if (fluid == null || fluid.amount <= 0) { + return null; + } + IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid)); + if (stack == null) { + return null; + } + stack.setStackSize(fluid.amount); + 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 00000000..63c00dd3 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java @@ -0,0 +1,35 @@ +package com.glodblock.github.common.item; + +import appeng.api.networking.crafting.ICraftingPatternDetails; +import appeng.items.misc.ItemEncodedPattern; +import com.glodblock.github.interfaces.HasCustomModel; +import com.glodblock.github.util.FluidPatternDetails; +import com.glodblock.github.util.NameConst; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class ItemFluidEncodedPattern extends ItemEncodedPattern implements HasCustomModel { + + @Override + protected void getCheckedSubItems(CreativeTabs creativeTab, NonNullList itemStacks) { + // NO-OP + } + + @Nullable + @Override + public ICraftingPatternDetails getPatternForItem(ItemStack is, World w) { + FluidPatternDetails pattern = new FluidPatternDetails(is); + return pattern.readFromStack() ? pattern : null; + } + + @Override + public ResourceLocation getCustomModelPath() { + return NameConst.MODEL_DENSE_ENCODED_PATTERN; + } + +} \ No newline at end of file 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 00000000..ad27784a --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java @@ -0,0 +1,94 @@ +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.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.translation.I18n; +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; + +public class ItemFluidPacket extends Item implements HasCustomModel { + + public ItemFluidPacket() { + setMaxStackSize(1); + } + + @Override + public void getSubItems(@Nonnull CreativeTabs tab,@Nonnull NonNullList items) { + // NO-OP + } + + @Override + @Nonnull + public String getItemStackDisplayName(@Nonnull ItemStack stack) { + FluidStack fluid = getFluidStack(stack); + return fluid != null ? String.format("%s, %,d mB", fluid.getLocalizedName(), fluid.amount) + : super.getItemStackDisplayName(stack); + } + + @SuppressWarnings("deprecation") + @Override + public void addInformation(@Nonnull ItemStack stack, @Nullable World world, @Nonnull List tooltip, @Nonnull ITooltipFlag flags) { + FluidStack fluid = getFluidStack(stack); + if (fluid != null) { + for (String line : I18n.translateToLocal(NameConst.TT_FLUID_PACKET).split("\\\\n")) { + tooltip.add(TextFormatting.GRAY + line); + } + } else { + tooltip.add(TextFormatting.RED + I18n.translateToLocal(NameConst.TT_INVALID_FLUID)); + } + } + + @Nullable + public static FluidStack getFluidStack(ItemStack stack) { + if (stack.isEmpty() || !stack.hasTagCompound()) { + return null; + } + FluidStack fluid = FluidStack.loadFluidStackFromNBT(Objects.requireNonNull(stack.getTagCompound()).getCompoundTag("FluidStack")); + return (fluid != null && fluid.amount > 0) ? fluid : null; + } + + @Nullable + public static FluidStack getFluidStack(@Nullable IAEItemStack stack) { + return stack != null ? getFluidStack(stack.getDefinition()) : null; + } + + public static ItemStack newStack(@Nullable FluidStack fluid) { + if (fluid == null || fluid.amount == 0) { + return ItemStack.EMPTY; + } + ItemStack stack = new ItemStack(FCItems.FLUID_PACKET); + NBTTagCompound tag = new NBTTagCompound(); + NBTTagCompound fluidTag = new NBTTagCompound(); + fluid.writeToNBT(fluidTag); + tag.setTag("FluidStack", fluidTag); + stack.setTagCompound(tag); + return stack; + } + + @Nullable + public static IAEItemStack newAeStack(@Nullable FluidStack fluid) { + return AEItemStack.fromItemStack(newStack(fluid)); + } + + @Override + public ResourceLocation getCustomModelPath() { + return NameConst.MODEL_FLUID_PACKET; + } + +} \ 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 00000000..18c154fb --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java @@ -0,0 +1,37 @@ +package com.glodblock.github.common.item; + +import appeng.api.AEApi; +import appeng.api.parts.IPartItem; +import com.glodblock.github.common.part.PartDualInterface; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPartDualInterface extends Item implements IPartItem { + + public ItemPartDualInterface() { + this.setMaxStackSize(64); + } + + @Nullable + @Override + public PartDualInterface createPartFromItemStack(ItemStack is) { + return new PartDualInterface(is); + } + + @Override + @Nonnull + public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumHand hand, @Nonnull EnumFacing side, + float hitX, float hitY, float hitZ) { + return AEApi.instance().partHelper().placeBus(player.getHeldItem(hand), pos, side, player, hand, world); + } + +} \ No newline at end of file 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 00000000..9d8597a3 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java @@ -0,0 +1,37 @@ +package com.glodblock.github.common.item; + +import appeng.api.AEApi; +import appeng.api.parts.IPartItem; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPartFluidPatternTerminal extends Item implements IPartItem { + + public ItemPartFluidPatternTerminal() { + this.setMaxStackSize(64); + } + + @Nullable + @Override + public PartFluidPatternTerminal createPartFromItemStack(ItemStack is) { + return new PartFluidPatternTerminal(is); + } + + @Override + @Nonnull + public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumHand hand, @Nonnull EnumFacing side, + float hitX, float hitY, float hitZ) { + return AEApi.instance().partHelper().placeBus(player.getHeldItem(hand), pos, side, player, hand, world); + } + +} 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 00000000..e4b118b1 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java @@ -0,0 +1,267 @@ +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.fluids.helper.DualityFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.helpers.DualityInterface; +import appeng.helpers.IInterfaceHost; +import appeng.helpers.Reflected; +import appeng.items.parts.PartModels; +import appeng.parts.PartBasicState; +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.common.component.DualityDualInterface; +import com.glodblock.github.interfaces.FCPriorityHost; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import com.glodblock.github.loader.FCItems; +import com.google.common.collect.ImmutableSet; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.EnumSet; +import java.util.List; + +public class PartDualInterface extends PartBasicState + implements IGridTickable, IInventoryDestination, IInterfaceHost, IAEAppEngInventory, FCPriorityHost, 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); + + @Reflected + 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 NBTTagCompound data) { + super.readFromNBT(data); + duality.readFromNBT(data); + } + + @Override + public void writeToNBT(final NBTTagCompound 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(final EntityPlayer p, final EnumHand hand, final Vec3d pos) { + if (Platform.isServer()) { + TileEntity tile = getTileEntity(); + InventoryHandler.openGui(p, tile.getWorld(), tile.getPos(), getSide().getFacing(), GuiType.DUAL_ITEM_INTERFACE); + } + 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 InventoryCrafting 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); + } + + @Override + public boolean hasCapability(Capability capabilityClass) { + return duality.hasCapability(capabilityClass, getSide().getFacing()); + } + + @Nullable + @Override + public T getCapability(Capability capabilityClass) { + return duality.getCapability(capabilityClass, getSide().getFacing()); + } + + @Override + public GuiType getGuiType() { + return GuiType.DUAL_ITEM_INTERFACE; + } + + @Override + public ItemStack getItemStackRepresentation() { + return new ItemStack(FCItems.PART_DUAL_INTERFACE, 1); + } + + @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; + } + } + +} \ No newline at end of file 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 00000000..6a0413bf --- /dev/null +++ b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java @@ -0,0 +1,96 @@ +package com.glodblock.github.common.part; + +import appeng.api.parts.IPartModel; +import appeng.api.storage.data.IAEItemStack; +import appeng.core.sync.GuiBridge; +import appeng.items.parts.PartModels; +import appeng.parts.PartModel; +import appeng.parts.reporting.PartPatternTerminal; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.Platform; +import appeng.util.inv.InvOperation; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.item.ItemFluidEncodedPattern; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; + +public class PartFluidPatternTerminal extends PartPatternTerminal { + + @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); + } + + @Nonnull + @Override + public IPartModel getStaticModels() { + return this.selectModel(MODELS_OFF, MODELS_ON, MODELS_HAS_CHANNEL); + } + + @Override + public boolean onPartActivate(final EntityPlayer player, final EnumHand hand, final Vec3d pos) { + TileEntity te = this.getTile(); + BlockPos tePos = te.getPos(); + if (Platform.isWrench(player, player.inventory.getCurrentItem(), tePos)) { + return super.onPartActivate(player, hand, pos); + } + if (Platform.isServer()) { + if (GuiBridge.GUI_PATTERN_TERMINAL.hasPermissions(te, tePos.getX(), tePos.getY(), tePos.getZ(), getSide(), player)) { + InventoryHandler.openGui(player, te.getWorld(), tePos, getSide().getFacing(), GuiType.FLUID_PATTERN_TERMINAL); + } else { + Platform.openGUI(player, this.getHost().getTile(), this.getSide(), GuiBridge.GUI_ME); + } + } + return true; + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, + ItemStack newStack) { + if (slot == 1) { + final ItemStack is = inv.getStackInSlot(1); + if (!is.isEmpty() && is.getItem() instanceof ItemFluidEncodedPattern) { + return; + } + } + super.onChangeInventory(inv, slot, mc, removedStack, newStack); + } + + public void onChangeCrafting(IAEItemStack[] newCrafting, IAEItemStack[] newOutput) { + IItemHandler crafting = this.getInventoryByName("crafting"); + IItemHandler output = this.getInventoryByName("output"); + if (crafting instanceof AppEngInternalInventory && output instanceof AppEngInternalInventory) { + for (int x = 0; x < crafting.getSlots() && x < newCrafting.length; x++) { + final IAEItemStack item = newCrafting[x]; + ((AppEngInternalInventory)crafting) + .setStackInSlot(x, item == null ? ItemStack.EMPTY : item.createItemStack()); + } + + for (int x = 0; x < output.getSlots() && x < newOutput.length; x++) { + final IAEItemStack item = newOutput[x]; + ((AppEngInternalInventory)output) + .setStackInSlot(x, item == null ? ItemStack.EMPTY : item.createItemStack()); + } + } + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileBurette.java b/src/main/java/com/glodblock/github/common/tile/TileBurette.java new file mode 100644 index 00000000..bf548db4 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileBurette.java @@ -0,0 +1,84 @@ +package com.glodblock.github.common.tile; + +import appeng.fluids.util.AEFluidInventory; +import appeng.fluids.util.IAEFluidInventory; +import appeng.fluids.util.IAEFluidTank; +import appeng.tile.AEBaseInvTile; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.inv.InvOperation; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TileBurette extends AEBaseInvTile implements IAEFluidInventory { + + private final AppEngInternalInventory invTarget = new AppEngInternalInventory(this, 1); + private final AEFluidInventory invFluid = new AEFluidInventory(this, 1, 8000); + + @Nonnull + @Override + public IItemHandlerModifiable getInternalInventory() { + return invTarget; + } + + public IAEFluidTank getFluidInventory() { + return invFluid; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Override + public boolean hasCapability(Capability capability, EnumFacing facing) { + return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY + || capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(Capability capability, @Nullable EnumFacing facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return (T)invTarget; + } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return (T)invFluid; + } + return null; + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removed, ItemStack added) { + // NO-OP + } + + @Override + public void onFluidInventoryChanged(IAEFluidTank inv, int slot) { + saveChanges(); + } + + @Override + public void readFromNBT(NBTTagCompound data) { + super.readFromNBT(data); + invTarget.readFromNBT(data, "ItemInv"); + invFluid.readFromNBT(data, "FluidInv"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound data) { + super.writeToNBT(data); + invTarget.writeToNBT(data, "ItemInv"); + invFluid.writeToNBT(data, "FluidInv"); + return data; + } + +} 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 00000000..bf7df981 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java @@ -0,0 +1,309 @@ +package com.glodblock.github.common.tile; + +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.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.tile.grid.AENetworkInvTile; +import appeng.util.Platform; +import appeng.util.inv.IInventoryDestination; +import appeng.util.inv.InvOperation; +import com.glodblock.github.common.component.DualityDualInterface; +import com.glodblock.github.interfaces.FCPriorityHost; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.loader.FCBlocks; +import com.google.common.collect.ImmutableSet; +import io.netty.buffer.ByteBuf; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.capabilities.Capability; +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 AENetworkInvTile + implements IGridTickable, IInventoryDestination, IInterfaceHost, FCPriorityHost, IFluidInterfaceHost { + + public TileDualInterface() { + super(); + } + + 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 EnumFacing facing) { + if (Platform.isClient()) { + return; + } + + EnumFacing 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(EnumFacing.NORTH, EnumFacing.UP); + } else { + EnumFacing newUp = EnumFacing.UP; + if (newForward == EnumFacing.UP || newForward == EnumFacing.DOWN) { + newUp = EnumFacing.NORTH; + } + this.setOrientation(newForward, newUp); + } + + this.configureNodeSides(); + this.markForUpdate(); + this.saveChanges(); + } + + private void configureNodeSides() { + if (this.omniDirectional) { + this.getProxy().setValidSides(EnumSet.allOf(EnumFacing.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 NBTTagCompound writeToNBT(final NBTTagCompound data) { + super.writeToNBT(data); + data.setBoolean("omniDirectional", this.omniDirectional); + duality.writeToNBT(data); + return data; + } + + @Override + public void readFromNBT(final NBTTagCompound data) { + super.readFromNBT(data); + this.omniDirectional = data.getBoolean("omniDirectional"); + duality.readFromNBT(data); + } + + @Override + protected boolean readFromStream(final ByteBuf 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(final ByteBuf 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(EnumFacing.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 InventoryCrafting 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; + } + + @Override + public boolean hasCapability(Capability capability, @Nullable EnumFacing facing) { + return duality.hasCapability(capability, facing) || super.hasCapability(capability, facing); + } + + @Nullable + @Override + public T getCapability(Capability capability, @Nullable EnumFacing facing) { + T capInst = duality.getCapability(capability, facing); + return capInst != null ? capInst : super.getCapability(capability, facing); + } + + @Override + public ItemStack getItemStackRepresentation() { + return new ItemStack(FCBlocks.DUAL_INTERFACE); + } + + @Override + public GuiType getGuiType() { + return GuiType.DUAL_ITEM_INTERFACE; + } + +} 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 00000000..e8a22e54 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java @@ -0,0 +1,279 @@ +package com.glodblock.github.common.tile; + +import appeng.api.AEApi; +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.MENetworkCellArrayUpdate; +import appeng.api.networking.events.MENetworkChannelsChanged; +import appeng.api.networking.events.MENetworkEventSubscribe; +import appeng.api.networking.events.MENetworkPowerStatusChange; +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.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.helpers.Reflected; +import appeng.me.GridAccessException; +import appeng.me.cache.CraftingGridCache; +import appeng.me.helpers.MachineSource; +import appeng.me.storage.MEInventoryHandler; +import appeng.tile.grid.AENetworkTile; +import appeng.util.Platform; +import com.glodblock.github.common.item.ItemFluidDrop; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TileFluidDiscretizer extends AENetworkTile 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; + + @Reflected + public TileFluidDiscretizer() { + 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 == AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class)) { + return Collections.singletonList(fluidDropInv.invHandler); + } else if (channel == AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class)) { + return Collections.singletonList(fluidCraftInv.invHandler); + } + } + return Collections.emptyList(); + } + + @Override + public void saveChanges(@Nullable ICellInventory cellInventory) { + 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(); + } + + 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(AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class)); + } catch (GridAccessException e) { + return null; + } + } + + private class FluidDiscretizingInventory implements IMEInventory, IMEMonitorHandlerReceiver { + + private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel()); + @Nullable + private List 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(Platform.poweredExtraction(energyGrid, fluidGrid, fluidStack, ownActionSource, mode)); + } + + @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(Platform.poweredInsert(energyGrid, fluidGrid, fluidStack, ownActionSource, type)); + } + + @Override + public IItemList getAvailableItems(IItemList out) { + if (itemCache == null) { + itemCache = new ArrayList<>(); + 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 { + List mappedChanges = new ArrayList<>(); + for (IAEFluidStack fluidStack : change) { + IAEItemStack itemStack = ItemFluidDrop.newAeStack(fluidStack); + if (itemStack != null) { + 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 AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class); + } + + } + + 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 AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class); + } + + } + +} 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 00000000..dcec9b7b --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java @@ -0,0 +1,130 @@ +package com.glodblock.github.common.tile; + +import appeng.api.AEApi; +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.channels.IFluidStorageChannel; +import appeng.api.storage.data.IAEFluidStack; +import appeng.fluids.util.AEFluidStack; +import appeng.helpers.Reflected; +import appeng.me.GridAccessException; +import appeng.me.helpers.MachineSource; +import appeng.tile.grid.AENetworkTile; +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 net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TileFluidPacketDecoder extends AENetworkTile implements IGridTickable, IAEAppEngInventory { + + private final AppEngInternalInventory inventory = new AppEngInternalInventory(this, 1); + private final IActionSource ownActionSource = new MachineSource(this); + + @Reflected + public TileFluidPacketDecoder() { + getProxy().setIdlePowerUsage(1D); + getProxy().setFlags(GridFlags.REQUIRE_CHANNEL); + } + + public IItemHandlerModifiable getInventory() { + return inventory; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return (T)inventory; + } else { + return null; + } + } + + @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 == null || fluid.amount <= 0) { + 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(AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class)); + 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 NBTTagCompound writeToNBT(NBTTagCompound data) { + super.writeToNBT(data); + inventory.writeToNBT(data, "Inventory"); + return data; + } + + @Override + public void readFromNBT(NBTTagCompound data) { + super.readFromNBT(data); + inventory.readFromNBT(data, "Inventory"); + } + +} diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java b/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java new file mode 100644 index 00000000..b8e8d162 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java @@ -0,0 +1,98 @@ +package com.glodblock.github.common.tile; + +import appeng.api.AEApi; +import appeng.api.storage.channels.IItemStorageChannel; +import appeng.api.storage.data.IAEItemStack; +import appeng.tile.AEBaseTile; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.inv.IAEAppEngInventory; +import appeng.util.inv.InvOperation; +import com.glodblock.github.interfaces.AeStackInventory; +import com.glodblock.github.inventory.AeStackInventoryImpl; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class TileFluidPatternEncoder extends AEBaseTile implements IAEAppEngInventory { + + private final AppEngInternalInventory patternInv = new AppEngInternalInventory(this, 2); + private final AeStackInventoryImpl crafting = new AeStackInventoryImpl<>( + AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class), 9, this); + private final AeStackInventoryImpl output = new AeStackInventoryImpl<>( + AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class), 3, this); + + public IItemHandlerModifiable getInventory() { + return patternInv; + } + + public AeStackInventory getCraftingSlots() { + return crafting; + } + + public AeStackInventory getOutputSlots() { + return output; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return (T)patternInv; + } else { + return null; + } + } + + @Override + public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, ItemStack newStack) { + // NO-OP + } + + @Override + public void getDrops(World world, BlockPos pos, List drops) { + for (ItemStack stack : patternInv) { + if (!stack.isEmpty()) { + drops.add(stack); + } + } + } + + @Override + public void readFromNBT(NBTTagCompound data) { + super.readFromNBT(data); + patternInv.readFromNBT(data, "Inventory"); + crafting.readFromNbt(data, "CraftingSlots"); + output.readFromNbt(data, "OutputSlots"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound data) { + super.writeToNBT(data); + patternInv.writeToNBT(data, "Inventory"); + crafting.writeToNbt(data, "CraftingSlots"); + output.writeToNbt(data, "OutputSlots"); + return data; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java b/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java new file mode 100644 index 00000000..225f8a28 --- /dev/null +++ b/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java @@ -0,0 +1,139 @@ +package com.glodblock.github.common.tile; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.fluids.util.AEFluidInventory; +import appeng.fluids.util.AEFluidStack; +import appeng.fluids.util.IAEFluidInventory; +import appeng.fluids.util.IAEFluidTank; +import appeng.tile.AEBaseInvTile; +import appeng.tile.inventory.AppEngInternalInventory; +import appeng.util.inv.InvOperation; +import io.netty.buffer.ByteBuf; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; + +public class TileIngredientBuffer extends AEBaseInvTile implements IAEFluidInventory { + + private final AppEngInternalInventory invItems = new AppEngInternalInventory(this, 9); + private final AEFluidInventory invFluids = new AEFluidInventory(this, 4, 16000); + + @Nonnull + @Override + public IItemHandler getInternalInventory() { + return invItems; + } + + public IAEFluidTank getFluidInventory() { + return invFluids; + } + + @Override + public boolean canBeRotated() { + return false; + } + + @Override + public boolean hasCapability(Capability capability, EnumFacing facing) { + return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY + || capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(Capability capability, @Nullable EnumFacing facing) { + if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { + return (T)invItems; + } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return (T)invFluids; + } + return null; + } + + @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(ByteBuf data) throws IOException { + super.writeToStream(data); + for (int i = 0; i < invItems.getSlots(); i++) { + ByteBufUtils.writeItemStack(data, invItems.getStackInSlot(i)); + } + int fluidMask = 0; + for (int i = 0; i < invFluids.getSlots(); i++) { + if (invFluids.getFluidInSlot(i) != null) { + fluidMask |= 1 << i; + } + } + data.writeByte(fluidMask); + for (int i = 0; i < invFluids.getSlots(); i++) { + IAEFluidStack fluid = invFluids.getFluidInSlot(i); + if (fluid != null) { + fluid.writeToPacket(data); + } + } + } + + @Override + protected boolean readFromStream(ByteBuf data) throws IOException { + boolean changed = super.readFromStream(data); + for (int i = 0; i < invItems.getSlots(); i++) { + ItemStack stack = ByteBufUtils.readItemStack(data); + if (!ItemStack.areItemStacksEqual(stack, invItems.getStackInSlot(i))) { + invItems.setStackInSlot(i, stack); + changed = true; + } + } + int fluidMask = data.readByte(); + for (int i = 0; i < invFluids.getSlots(); i++) { + if ((fluidMask & (1 << i)) != 0) { + IAEFluidStack fluid = AEFluidStack.fromPacket(data); + if (fluid != null) { // this shouldn't happen, but better safe than sorry + IAEFluidStack origFluid = invFluids.getFluidInSlot(i); + if (!fluid.equals(origFluid) || fluid.getStackSize() != origFluid.getStackSize()) { + invFluids.setFluidInSlot(i, fluid); + changed = true; + } + } + } else if (invFluids.getFluidInSlot(i) != null) { + invFluids.setFluidInSlot(i, null); + changed = true; + } + } + return changed; + } + + @Override + public void readFromNBT(NBTTagCompound data) { + super.readFromNBT(data); + invItems.readFromNBT(data, "ItemInv"); + invFluids.readFromNBT(data, "FluidInv"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound data) { + super.writeToNBT(data); + invItems.writeToNBT(data, "ItemInv"); + invFluids.writeToNBT(data, "FluidInv"); + return data; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/coremod/CoreModHooks.java b/src/main/java/com/glodblock/github/coremod/CoreModHooks.java new file mode 100644 index 00000000..b14ef678 --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/CoreModHooks.java @@ -0,0 +1,105 @@ +package com.glodblock.github.coremod; + +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridHost; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IMachineSet; +import appeng.api.storage.data.IAEItemStack; +import appeng.me.MachineSet; +import appeng.parts.misc.PartInterface; +import appeng.tile.misc.TileInterface; +import appeng.util.InventoryAdaptor; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.part.PartDualInterface; +import com.glodblock.github.common.tile.TileDualInterface; +import com.glodblock.github.handler.FluidConvertingItemHandler; +import com.glodblock.github.inventory.FluidConvertingInventoryAdaptor; +import com.glodblock.github.inventory.FluidConvertingInventoryCrafting; +import com.glodblock.github.loader.FCItems; +import com.glodblock.github.util.Ae2Reflect; +import com.glodblock.github.util.SetBackedMachineSet; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nullable; +import java.util.HashSet; +import java.util.Set; +import com.google.common.collect.Sets; + +public class CoreModHooks { + + public static InventoryCrafting wrapCraftingBuffer(InventoryCrafting inv) { + return new FluidConvertingInventoryCrafting(Ae2Reflect.getCraftContainer(inv), inv.getWidth(), inv.getHeight()); + } + + public static IAEItemStack wrapFluidPacketStack(IAEItemStack stack) { + if (stack.getItem() == FCItems.FLUID_PACKET) { + IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack.getDefinition())); + if (dropStack != null) { + return dropStack; + } + } + return stack; + } + + @Nullable + public static InventoryAdaptor wrapInventory(@Nullable TileEntity tile, EnumFacing face) { + return tile != null ? FluidConvertingInventoryAdaptor.wrap(tile, face) : null; + } + + public static long getCraftingByteCost(IAEItemStack stack) { + return stack.getItem() instanceof ItemFluidDrop + ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize(); + } + + public static boolean checkForItemHandler(ICapabilityProvider capProvider, Capability capability, EnumFacing side) { + return capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side) + || capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side); + } + + public static IItemHandler wrapItemHandler(ICapabilityProvider capProvider, Capability capability, EnumFacing side) { + return FluidConvertingItemHandler.wrap(capProvider, side); + } + + public static IAEItemStack[] flattenFluidPackets(IAEItemStack[] stacks) { + for (int i = 0; i < stacks.length; i++) { + if (stacks[i].getItem() instanceof ItemFluidPacket) { + stacks[i] = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stacks[i])); + } + } + return stacks; + } + + public static IMachineSet getMachines(IGrid grid, Class c) { + if (c == TileInterface.class) { + return unionMachineSets(grid.getMachines(c), grid.getMachines(TileDualInterface.class)); + } else if (c == PartInterface.class) { + return unionMachineSets(grid.getMachines(c), grid.getMachines(PartDualInterface.class)); + } else { + return grid.getMachines(c); + } + } + + private static IMachineSet unionMachineSets(IMachineSet a, IMachineSet b) { + if (a.isEmpty()) { + return b; + } else if (b.isEmpty()) { + return a; + } else if (a instanceof MachineSet && b instanceof MachineSet) { + return new SetBackedMachineSet(TileInterface.class, Sets.union((MachineSet)a, (MachineSet)b)); + } else { + Set union = new HashSet<>(); + a.forEach(union::add); + b.forEach(union::add); + return new SetBackedMachineSet(TileInterface.class, union); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java new file mode 100644 index 00000000..2a681268 --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java @@ -0,0 +1,67 @@ +package com.glodblock.github.coremod; + +import com.glodblock.github.coremod.transform.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassWriter; + +public class FCClassTransformer implements IClassTransformer { + + @Override + public byte[] transform(String name, String transformedName, byte[] code) { + Transform tform; + switch (transformedName) { + case "appeng.crafting.CraftingTreeNode": + tform = CraftingTreeNodeTransformer.INSTANCE; + break; + case "appeng.helpers.DualityInterface": + tform = DualityInterfaceTransformer.INSTANCE; + break; + case "appeng.me.cluster.implementations.CraftingCPUCluster": + tform = CraftingCpuTransformer.INSTANCE; + break; + case "thelm.packagedauto.integration.appeng.recipe.PackageCraftingPatternHelper": + tform = PautoCraftingPatternHelperTransformer.TFORM_INPUTS; + break; + case "thelm.packagedauto.integration.appeng.recipe.RecipeCraftingPatternHelper": + tform = PautoCraftingPatternHelperTransformer.TFORM_OUTPUTS; + break; + case "thelm.packagedauto.tile.TileUnpackager": + tform = TileUnpackagerTransformer.INSTANCE; + break; + case "appeng.container.implementations.ContainerInterfaceTerminal": + tform = ContainerInterfaceTerminalTransformer.INSTANCE; + break; + default: + return code; + } + System.out.println("[AE2FC] Transforming class: " + transformedName); + return tform.transformClass(code); + } + + public interface Transform { + + byte[] transformClass(byte[] code); + + } + + public static abstract class ClassMapper implements Transform { + + @Override + public byte[] transformClass(byte[] code) { + ClassReader reader = new ClassReader(code); + ClassWriter writer = new ClassWriter(reader, getWriteFlags()); + reader.accept(getClassMapper(writer), 0); + return writer.toByteArray(); + } + + protected int getWriteFlags() { + return 0; + } + + protected abstract ClassVisitor getClassMapper(ClassVisitor downstream); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/coremod/FCCoreMod.java b/src/main/java/com/glodblock/github/coremod/FCCoreMod.java new file mode 100644 index 00000000..789f05fc --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/FCCoreMod.java @@ -0,0 +1,40 @@ +package com.glodblock.github.coremod; + +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +import javax.annotation.Nullable; +import java.util.Map; + +@IFMLLoadingPlugin.MCVersion("1.12.2") +@IFMLLoadingPlugin.TransformerExclusions("com.glodblock.github.coremod") +public class FCCoreMod implements IFMLLoadingPlugin { + + @Override + public String[] getASMTransformerClass() { + return new String[] { FCCoreMod.class.getPackage().getName() + ".FCClassTransformer" }; + } + + @Nullable + @Override + public String getModContainerClass() { + return null; + } + + @Nullable + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + // NO-OP + } + + @Nullable + @Override + public String getAccessTransformerClass() { + return null; + } + +} diff --git a/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java new file mode 100644 index 00000000..5d6c82e1 --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java @@ -0,0 +1,58 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class ContainerInterfaceTerminalTransformer extends FCClassTransformer.ClassMapper { + + public static final ContainerInterfaceTerminalTransformer INSTANCE = new ContainerInterfaceTerminalTransformer(); + + private ContainerInterfaceTerminalTransformer() { + // NO-OP + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformContainerInterfaceTerminal(Opcodes.ASM5, downstream); + } + + private static class TransformContainerInterfaceTerminal extends ClassVisitor { + + TransformContainerInterfaceTerminal(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("detectAndSendChanges") || name.equals("func_75142_b") || name.equals("regenList")) { + return new TransformDetectAndSendChanges(api, super.visitMethod(access, name, desc, signature, exceptions)); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + } + + private static class TransformDetectAndSendChanges extends MethodVisitor { + + TransformDetectAndSendChanges(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKEINTERFACE + && owner.equals("appeng/api/networking/IGrid") && name.equals("getMachines")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "com/glodblock/github/coremod/CoreModHooks", + "getMachines", + "(Lappeng/api/networking/IGrid;Ljava/lang/Class;)Lappeng/api/networking/IMachineSet;", + false); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java new file mode 100644 index 00000000..c98e7f7b --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java @@ -0,0 +1,86 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class CraftingCpuTransformer extends FCClassTransformer.ClassMapper { + + public static final CraftingCpuTransformer INSTANCE = new CraftingCpuTransformer(); + + private CraftingCpuTransformer() { + // NO-OP + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformCraftingCPUCluster(Opcodes.ASM5, downstream); + } + + private static class TransformCraftingCPUCluster extends ClassVisitor { + + TransformCraftingCPUCluster(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("executeCrafting")) { + return new TransformExecuteCrafting(api, super.visitMethod(access, name, desc, signature, exceptions)); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + } + + private static class TransformExecuteCrafting extends MethodVisitor { + + private boolean gotInventory = false; + + TransformExecuteCrafting(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.GETFIELD + && owner.equals("appeng/me/cluster/implementations/CraftingCPUCluster") && name.equals("inventory")) { + gotInventory = true; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitLineNumber(int line, Label start) { + gotInventory = false; + super.visitLineNumber(line, start); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + if (gotInventory) { + if (opcode == Opcodes.INVOKESTATIC + && owner.equals("appeng/util/item/AEItemStack") && name.equals("fromItemStack")) { + gotInventory = false; + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "com/glodblock/github/coremod/CoreModHooks", + "wrapFluidPacketStack", + "(Lappeng/api/storage/data/IAEItemStack;)Lappeng/api/storage/data/IAEItemStack;", + false); + } + } else if (opcode == Opcodes.INVOKESPECIAL + && owner.equals("net/minecraft/inventory/InventoryCrafting") && name.equals("")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "com/glodblock/github/coremod/CoreModHooks", + "wrapCraftingBuffer", + "(Lnet/minecraft/inventory/InventoryCrafting;)Lnet/minecraft/inventory/InventoryCrafting;", + false); + } + } + + } + +} diff --git a/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java new file mode 100644 index 00000000..c4028f3c --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java @@ -0,0 +1,77 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class CraftingTreeNodeTransformer extends FCClassTransformer.ClassMapper { + + public static final CraftingTreeNodeTransformer INSTANCE = new CraftingTreeNodeTransformer(); + + private CraftingTreeNodeTransformer() { + // NO-OP + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformCraftingTreeNode(Opcodes.ASM5, downstream); + } + + private static class TransformCraftingTreeNode extends ClassVisitor { + + TransformCraftingTreeNode(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("request")) { + return new TransformRequest(api, super.visitMethod(access, name, desc, signature, exceptions)); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + } + + private static class TransformRequest extends MethodVisitor { + + private boolean writingBytes = false; + + TransformRequest(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.GETFIELD && owner.equals("appeng/crafting/CraftingTreeNode") && name.equals("bytes")) { + writingBytes = true; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitLineNumber(int line, Label start) { + writingBytes = false; // no write here + super.visitLineNumber(line, start); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (writingBytes && opcode == Opcodes.INVOKEINTERFACE + && owner.equals("appeng/api/storage/data/IAEItemStack") && name.equals("getStackSize")) { + writingBytes = false; + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "com/glodblock/github/coremod/CoreModHooks", + "getCraftingByteCost", + "(Lappeng/api/storage/data/IAEItemStack;)J", + false); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + } + +} diff --git a/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java new file mode 100644 index 00000000..7db0adab --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java @@ -0,0 +1,62 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class DualityInterfaceTransformer extends FCClassTransformer.ClassMapper { + + public static final DualityInterfaceTransformer INSTANCE = new DualityInterfaceTransformer(); + + private DualityInterfaceTransformer() { + // NO-OP + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformDualityInterface(Opcodes.ASM5, downstream); + } + + private static class TransformDualityInterface extends ClassVisitor { + + TransformDualityInterface(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + switch (name) { + case "pushItemsOut": + case "pushPattern": + case "isBusy": + return new TransformInvAdaptorCalls(api, super.visitMethod(access, name, desc, signature, exceptions)); + default: + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + } + + private static class TransformInvAdaptorCalls extends MethodVisitor { + + TransformInvAdaptorCalls(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESTATIC && owner.equals("appeng/util/InventoryAdaptor") && name.equals("getAdaptor")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "com/glodblock/github/coremod/CoreModHooks", + "wrapInventory", + "(Lnet/minecraft/tileentity/TileEntity;Lnet/minecraft/util/EnumFacing;)Lappeng/util/InventoryAdaptor;", + false); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java new file mode 100644 index 00000000..22be0477 --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java @@ -0,0 +1,57 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class PautoCraftingPatternHelperTransformer extends FCClassTransformer.ClassMapper { + + public static final PautoCraftingPatternHelperTransformer TFORM_INPUTS = new PautoCraftingPatternHelperTransformer("inputs"); + public static final PautoCraftingPatternHelperTransformer TFORM_OUTPUTS = new PautoCraftingPatternHelperTransformer("outputs"); + + private final String fieldName; + + private PautoCraftingPatternHelperTransformer(String fieldName) { + this.fieldName = fieldName; + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformPackageCraftingPatternHelper(Opcodes.ASM5, downstream); + } + + private class TransformPackageCraftingPatternHelper extends ClassVisitor { + + TransformPackageCraftingPatternHelper(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("")) { + return new TransformCtor(api, super.visitMethod(access, name, desc, signature, exceptions)); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + } + + private class TransformCtor extends MethodVisitor { + + TransformCtor(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.PUTFIELD && name.equals(fieldName) && desc.equals("[Lappeng/api/storage/data/IAEItemStack;")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "flattenFluidPackets", + "([Lappeng/api/storage/data/IAEItemStack;)[Lappeng/api/storage/data/IAEItemStack;", false); + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + } + +} diff --git a/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java new file mode 100644 index 00000000..bf3b3270 --- /dev/null +++ b/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java @@ -0,0 +1,84 @@ +package com.glodblock.github.coremod.transform; + +import com.glodblock.github.coremod.FCClassTransformer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class TileUnpackagerTransformer extends FCClassTransformer.ClassMapper { + + public static final TileUnpackagerTransformer INSTANCE = new TileUnpackagerTransformer(); + + private TileUnpackagerTransformer() { + // NO-OP + } + + @Override + protected ClassVisitor getClassMapper(ClassVisitor downstream) { + return new TransformTileUnpackager(Opcodes.ASM5, downstream); + } + + private static class TransformTileUnpackager extends ClassVisitor { + + TransformTileUnpackager(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("emptyTrackers") && desc.equals("()V")) { + return new TransformEmptyTrackers(api, super.visitMethod(access, name, desc, signature, exceptions)); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + } + + private static class TransformEmptyTrackers extends MethodVisitor { + + private boolean gettingItemHandlerCap = false; + + TransformEmptyTrackers(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + super.visitFieldInsn(opcode, owner, name, desc); + if (opcode == Opcodes.GETSTATIC && desc.equals("Lnet/minecraftforge/common/capabilities/Capability;")) { + gettingItemHandlerCap = name.equals("ITEM_HANDLER_CAPABILITY"); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKEVIRTUAL && gettingItemHandlerCap) { + switch (name) { + case "hasCapability": + if (desc.equals("(Lnet/minecraftforge/common/capabilities/Capability;Lnet/minecraft/util/EnumFacing;)Z")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "checkForItemHandler", + "(Lnet/minecraftforge/common/capabilities/ICapabilityProvider;Lnet/minecraftforge/common/capabilities/Capability;" + + "Lnet/minecraft/util/EnumFacing;)Z", false); + gettingItemHandlerCap = false; + } + break; + case "getCapability": + if (desc.equals("(Lnet/minecraftforge/common/capabilities/Capability;Lnet/minecraft/util/EnumFacing;)Ljava/lang/Object;")) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "wrapItemHandler", + "(Lnet/minecraftforge/common/capabilities/ICapabilityProvider;Lnet/minecraftforge/common/capabilities/Capability;" + + "Lnet/minecraft/util/EnumFacing;)Lnet/minecraftforge/items/IItemHandler;", false); + gettingItemHandlerCap = false; + } + break; + default: + super.visitMethodInsn(opcode, owner, name, desc, itf); + break; + } + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + } + +} diff --git a/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java b/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java new file mode 100644 index 00000000..106372dc --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java @@ -0,0 +1,46 @@ +package com.glodblock.github.handler; + +import appeng.api.storage.data.IAEItemStack; +import com.glodblock.github.interfaces.AeStackInventory; +import net.minecraft.item.ItemStack; +import net.minecraftforge.items.IItemHandler; + +public class AeItemStackHandler implements IItemHandler { + + private final AeStackInventory inv; + + public AeItemStackHandler(AeStackInventory inv) { + this.inv = inv; + } + + public AeStackInventory getAeInventory() { + return inv; + } + + @Override + public int getSlots() { + return inv.getSlotCount(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + IAEItemStack stack = inv.getStack(slot); + return stack != null ? stack.createItemStack() : ItemStack.EMPTY; + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } + + @Override + public int getSlotLimit(int slot) { + return 64; + } + +} 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 00000000..3050bfe6 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java @@ -0,0 +1,48 @@ +package com.glodblock.github.handler; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.interfaces.TankDumpable; +import com.glodblock.github.network.CPacketDumpTank; +import com.glodblock.github.util.MouseRegionManager; +import com.glodblock.github.util.NameConst; +import net.minecraft.client.resources.I18n; + +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(I18n.format(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)) { + FluidCraft.proxy.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 00000000..b325d9b4 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java @@ -0,0 +1,41 @@ +package com.glodblock.github.handler; + +import appeng.api.AEApi; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.model.DenseEncodedPatternModel; +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 net.minecraft.block.Block; +import net.minecraft.client.renderer.block.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.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.tuple.Pair; + +public class ClientRegistryHandler extends RegistryHandler { + + @SubscribeEvent + public void onRegisterModels(ModelRegistryEvent event) { + ModelLoaderRegistry.registerLoader(new DenseEncodedPatternModel.Loader()); + ModelLoaderRegistry.registerLoader(new FluidPacketModel.Loader()); + for (Pair entry : blocks) { + registerModel(entry.getLeft(), Item.getItemFromBlock(entry.getRight())); + } + for (Pair entry : items) { + registerModel(entry.getLeft(), entry.getRight()); + } + AEApi.instance().registries().partModels().registerModels(PartDualInterface.MODELS); + AEApi.instance().registries().partModels().registerModels(PartFluidPatternTerminal.MODELS); + } + + private static void registerModel(String key, Item item) { + ModelLoader.setCustomModelResourceLocation(item, 0, new ModelResourceLocation( + item instanceof HasCustomModel ? ((HasCustomModel)item).getCustomModelPath() : FluidCraft.resource(key), + "inventory")); + } + +} diff --git a/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java b/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java new file mode 100644 index 00000000..2317eb84 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java @@ -0,0 +1,147 @@ +package com.glodblock.github.handler; + +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +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.fluids.capability.IFluidTankProperties; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +public class FluidConvertingItemHandler implements IItemHandler { + + public static FluidConvertingItemHandler wrap(ICapabilityProvider capProvider, EnumFacing face) { + // sometimes i wish i had the monadic version from 1.15 + return new FluidConvertingItemHandler( + capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face) + ? Objects.requireNonNull(capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face)) + : null, + capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face) + ? Objects.requireNonNull(capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face)) + : null); + } + + @Nullable + private final IItemHandler invItems; + @Nullable + private final IFluidHandler invFluids; + + private FluidConvertingItemHandler(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids) { + this.invItems = invItems; + this.invFluids = invFluids; + } + + @Override + public int getSlots() { + int slots = 0; + if (invItems != null) { + slots += invItems.getSlots(); + } + if (invFluids != null) { + slots += invFluids.getTankProperties().length; + } + return slots; + } + + @Override + @Nonnull + public ItemStack getStackInSlot(int slot) { + return slotOp(slot, IItemHandler::getStackInSlot, + (fh, i) -> ItemFluidDrop.newStack(fh.getTankProperties()[i].getContents())); + } + + @Override + @Nonnull + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + return slotOp(slot, + (ih, i) -> (stack.getItem() instanceof ItemFluidDrop || stack.getItem() instanceof ItemFluidPacket) + ? stack : ih.insertItem(i, stack, simulate), + (fh, i) -> { + if (stack.getItem() instanceof ItemFluidDrop) { + FluidStack toInsert = ItemFluidDrop.getFluidStack(stack); + if (toInsert != null && toInsert.amount > 0) { + FluidStack contained = fh.getTankProperties()[i].getContents(); + if (contained == null || contained.amount == 0 || contained.isFluidEqual(toInsert)) { + toInsert.amount -= fh.fill(toInsert, !simulate); + return ItemFluidDrop.newStack(toInsert); + } + } + } else if (stack.getItem() instanceof ItemFluidPacket) { + FluidStack toInsert = ItemFluidPacket.getFluidStack(stack); + if (toInsert != null && toInsert.amount > 0) { + FluidStack contained = fh.getTankProperties()[i].getContents(); + if (contained == null || contained.amount == 0 || contained.isFluidEqual(toInsert)) { + int insertable = fh.fill(toInsert, false); // only insert if the entire packet fits + if (insertable >= toInsert.amount) { + if (!simulate) { + fh.fill(toInsert, true); + } + return ItemStack.EMPTY; + } + } + } + } + return stack; + }); + } + + @Override + @Nonnull + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return slotOp(slot, (ih, i) -> ih.extractItem(i, slot, simulate), (fh, i) -> { + FluidStack contained = fh.getTankProperties()[i].getContents(); + if (contained != null && contained.amount > 0) { + return ItemFluidDrop.newStack(fh.drain(contained, !simulate)); + } + return ItemStack.EMPTY; + }); + } + + @Override + public int getSlotLimit(int slot) { + return slotOp(slot, IItemHandler::getSlotLimit, (fh, i) -> fh.getTankProperties()[i].getCapacity()); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + return slotOp(slot, (ih, i) -> ih.isItemValid(i, stack), + (fh, i) -> stack.getItem() instanceof ItemFluidDrop || stack.getItem() instanceof ItemFluidPacket); + } + + private T slotOp(int slot, Op itemConsumer, Op fluidConsumer) { + if (slot >= 0) { + int fluidSlot = slot; + if (invItems != null) { + if (slot < invItems.getSlots()) { + return itemConsumer.apply(invItems, slot); + } else { + fluidSlot -= invItems.getSlots(); + } + } + if (invFluids != null) { + IFluidTankProperties[] tanks = invFluids.getTankProperties(); + if (fluidSlot < tanks.length) { + return fluidConsumer.apply(invFluids, fluidSlot); + } + } + } + throw new IndexOutOfBoundsException(String.format("Slot index %d out of bounds! |items| = %d, |fluids| = %d", slot, + invItems != null ? invItems.getSlots() : 0, invFluids != null ? invFluids.getTankProperties().length : 0)); + } + + @FunctionalInterface + private interface Op { + + T apply(C collection, int index); + + } + +} 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 00000000..fde38289 --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/RegistryHandler.java @@ -0,0 +1,74 @@ +package com.glodblock.github.handler; + +import appeng.block.AEBaseItemBlock; +import appeng.block.AEBaseTileBlock; +import appeng.core.features.ActivityState; +import appeng.core.features.BlockStackSrc; +import appeng.tile.AEBaseTile; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.loader.FCItems; +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; + +public class RegistryHandler { + + protected final List> blocks = new ArrayList<>(); + protected final List> items = new ArrayList<>(); + + public void block(String name, Block block) { + blocks.add(Pair.of(name, block)); + } + + public void item(String name, Item item) { + items.add(Pair.of(name, item)); + } + + @SubscribeEvent + public void onRegisterBlocks(RegistryEvent.Register event) { + for (Pair entry : blocks) { + String key = entry.getLeft(); + Block block = entry.getRight(); + block.setRegistryName(key); + block.setTranslationKey(FluidCraft.MODID + ":" + key); + block.setCreativeTab(FCItems.TAB_AE2FC); + event.getRegistry().register(block); + } + } + + @SubscribeEvent + public void onRegisterItems(RegistryEvent.Register event) { + // TODO some way to handle blocks with custom ItemBlock + for (Pair entry : blocks) { + event.getRegistry().register(initItem(entry.getLeft(), new AEBaseItemBlock(entry.getRight()))); + } + for (Pair entry : items) { + event.getRegistry().register(initItem(entry.getLeft(), entry.getRight())); + } + } + + private static Item initItem(String key, Item item) { + item.setRegistryName(key); + item.setTranslationKey(FluidCraft.MODID + ":" + key); + item.setCreativeTab(FCItems.TAB_AE2FC); + return item; + } + + public void onInit() { + for (Pair entry : blocks) { + // respects registry overrides, i guess + Block block = ForgeRegistries.BLOCKS.getValue(FluidCraft.resource(entry.getKey())); + if (block instanceof AEBaseTileBlock) { + AEBaseTile.registerTileItem(((AEBaseTileBlock)block).getTileEntityClass(), + new BlockStackSrc(block, 0, 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 00000000..b95d120a --- /dev/null +++ b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java @@ -0,0 +1,34 @@ +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.TextFormatting; + +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( + fluid != null ? fluid.getFluidStack().getLocalizedName() : I18n.format(NameConst.TT_EMPTY), + TextFormatting.GRAY + String.format("%,d / %,d mB", + fluid != null ? fluid.getStackSize() : 0L, tank.getTankProperties()[index].getCapacity())); + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java b/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java new file mode 100644 index 00000000..cf455ecc --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java @@ -0,0 +1,23 @@ +package com.glodblock.github.integration.jei; + +import com.glodblock.github.integration.jei.interfaces.IngredientExtractor; +import mezz.jei.api.gui.IRecipeLayout; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nullable; +import java.util.stream.Stream; + +public class ExtraExtractors { + + @Nullable + private final IngredientExtractor extModMach; + + public ExtraExtractors(@Nullable IngredientExtractor extModMach) { + this.extModMach = extModMach; + } + + public Stream> extractFluids(IRecipeLayout recipeLayout) { + return extModMach != null ? extModMach.extract(recipeLayout) : Stream.empty(); + } + +} 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 00000000..a2c83ec1 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java @@ -0,0 +1,35 @@ +package com.glodblock.github.integration.jei; + +import com.glodblock.github.integration.jei.interfaces.IngredientExtractor; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.IModRegistry; +import mezz.jei.api.JEIPlugin; +import mezz.jei.config.Constants; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.common.Loader; + +import javax.annotation.Nullable; +import java.util.Objects; + +@JEIPlugin +public class FCJeiPlugin implements IModPlugin { + + @Nullable + private static ExtraExtractors ext = null; + + public static ExtraExtractors getExtraExtractors() { + return Objects.requireNonNull(ext); + } + + @Override + public void register(IModRegistry registry) { + IngredientExtractor extModMach = Loader.isModLoaded("modularmachinery") + ? new ModMachHybridFluidStackExtractor(registry) : null; + ext = new ExtraExtractors(extModMach); + registry.getRecipeTransferRegistry().addRecipeTransferHandler( + new FluidPatternEncoderRecipeTransferHandler(ext), Constants.UNIVERSAL_RECIPE_TRANSFER_UID); + registry.getRecipeTransferRegistry().addRecipeTransferHandler( + new FluidPatternTerminalRecipeTransferHandler(ext), Constants.UNIVERSAL_RECIPE_TRANSFER_UID); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java new file mode 100644 index 00000000..e45f082d --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java @@ -0,0 +1,105 @@ +package com.glodblock.github.integration.jei; + +import appeng.api.storage.data.IAEItemStack; +import appeng.util.item.AEItemStack; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerFluidPatternEncoder; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.tile.TileFluidPatternEncoder; +import com.glodblock.github.network.CPacketLoadPattern; +import com.glodblock.github.util.NameConst; +import mezz.jei.api.gui.IGuiIngredient; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.VanillaRecipeCategoryUid; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.transfer.RecipeTransferErrorTooltip; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Iterator; + +public class FluidPatternEncoderRecipeTransferHandler implements IRecipeTransferHandler { + + private final ExtraExtractors ext; + + public FluidPatternEncoderRecipeTransferHandler(ExtraExtractors ext) { + this.ext = ext; + } + + @Override + @Nonnull + public Class getContainerClass() { + return ContainerFluidPatternEncoder.class; + } + + @Nullable + @Override + public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternEncoder container, IRecipeLayout recipeLayout, + @Nonnull EntityPlayer player, boolean maxTransfer, boolean doTransfer) { + if (recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_PROCESSING_RECIPE_ONLY)); + } + if (doTransfer) { + TileFluidPatternEncoder tile = container.getTile(); + IAEItemStack[] crafting = new IAEItemStack[tile.getCraftingSlots().getSlotCount()]; + IAEItemStack[] output = new IAEItemStack[tile.getOutputSlots().getSlotCount()]; + transferRecipeSlots(recipeLayout, crafting, output, false, ext); + FluidCraft.proxy.netHandler.sendToServer(new CPacketLoadPattern(crafting, output)); + } + return null; + } + + public static void transferRecipeSlots(IRecipeLayout recipeLayout, IAEItemStack[] crafting, IAEItemStack[] output, + boolean retainEmptyInputs, ExtraExtractors ext) { + int ndxCrafting = 0, ndxOutput = 0; + for (IGuiIngredient ing : recipeLayout.getItemStacks().getGuiIngredients().values()) { + if (ing.isInput()) { + if (ndxCrafting < crafting.length) { + ItemStack stack = ing.getDisplayedIngredient(); + if (stack != null) { + crafting[ndxCrafting++] = AEItemStack.fromItemStack(stack); + } else if (retainEmptyInputs) { + crafting[ndxCrafting++] = null; + } + } + } else { + if (ndxOutput < output.length) { + ItemStack stack = ing.getDisplayedIngredient(); + if (stack != null) { + output[ndxOutput++] = AEItemStack.fromItemStack(stack); + } + } + } + } + for (IGuiIngredient ing : recipeLayout.getFluidStacks().getGuiIngredients().values()) { + if (ing.isInput()) { + if (ndxCrafting < crafting.length) { + crafting[ndxCrafting++] = ItemFluidPacket.newAeStack(ing.getDisplayedIngredient()); + } + } else { + if (ndxOutput < output.length) { + output[ndxOutput++] = ItemFluidPacket.newAeStack(ing.getDisplayedIngredient()); + } + } + } + Iterator> iter = ext.extractFluids(recipeLayout).iterator(); + while (iter.hasNext()) { + WrappedIngredient ing = iter.next(); + if (ing.isInput()) { + if (ndxCrafting < crafting.length) { + crafting[ndxCrafting++] = ItemFluidPacket.newAeStack(ing.getIngredient()); + } + } else { + if (ndxOutput < output.length) { + output[ndxOutput++] = ItemFluidPacket.newAeStack(ing.getIngredient()); + } + } + } + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java new file mode 100644 index 00000000..5b41dec0 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java @@ -0,0 +1,55 @@ +package com.glodblock.github.integration.jei; + +import appeng.api.storage.data.IAEItemStack; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.network.CPacketLoadPattern; +import com.glodblock.github.util.NameConst; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.VanillaRecipeCategoryUid; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.transfer.RecipeTransferErrorTooltip; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.EntityPlayer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidPatternTerminalRecipeTransferHandler implements IRecipeTransferHandler { + + private final ExtraExtractors ext; + + FluidPatternTerminalRecipeTransferHandler(ExtraExtractors ext) { + this.ext = ext; + } + + @Override + @Nonnull + public Class getContainerClass() { + return ContainerFluidPatternTerminal.class; + } + + @Nullable + @Override + public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternTerminal container, @Nonnull IRecipeLayout recipeLayout, + @Nonnull EntityPlayer player, boolean maxTransfer, boolean doTransfer) { + if (container.craftingMode) { + if (!recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_CRAFTING_RECIPE_ONLY)); + } + } else if (recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_PROCESSING_RECIPE_ONLY)); + } + if (doTransfer && container.getPatternTerminal() instanceof PartFluidPatternTerminal) { + PartFluidPatternTerminal tile = (PartFluidPatternTerminal)container.getPatternTerminal(); + IAEItemStack[] crafting = new IAEItemStack[tile.getInventoryByName("crafting").getSlots()]; + IAEItemStack[] output = new IAEItemStack[tile.getInventoryByName("output").getSlots()]; + FluidPatternEncoderRecipeTransferHandler.transferRecipeSlots(recipeLayout, crafting, output, container.craftingMode, ext); + FluidCraft.proxy.netHandler.sendToServer(new CPacketLoadPattern(crafting, output)); + } + return null; + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java b/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java new file mode 100644 index 00000000..ffa75f43 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java @@ -0,0 +1,29 @@ +package com.glodblock.github.integration.jei; + +import com.glodblock.github.integration.jei.interfaces.IngredientExtractor; +import hellfirepvp.modularmachinery.common.integration.ingredient.HybridFluid; +import mezz.jei.api.IModRegistry; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.IIngredientType; +import net.minecraftforge.fluids.FluidStack; + +import java.util.Objects; +import java.util.stream.Stream; + +public class ModMachHybridFluidStackExtractor implements IngredientExtractor { + + private final IIngredientType ingTypeHybridFluid; + + ModMachHybridFluidStackExtractor(IModRegistry registry) { + ingTypeHybridFluid = Objects.requireNonNull(registry.getIngredientRegistry().getIngredientType(HybridFluid.class)); + } + + public Stream> extract(IRecipeLayout recipeLayout) { + return recipeLayout.getIngredientsGroup(ingTypeHybridFluid).getGuiIngredients().values().stream() + .map(ing -> { + HybridFluid hf = ing.getDisplayedIngredient(); + return new WrappedIngredient<>(hf != null ? hf.asFluidStack() : null, ing.isInput()); + }); + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java b/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java new file mode 100644 index 00000000..4d158e4a --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java @@ -0,0 +1,25 @@ +package com.glodblock.github.integration.jei; + +import javax.annotation.Nullable; + +public class WrappedIngredient { + + @Nullable + private final T ingredient; + private final boolean isInput; + + public WrappedIngredient(@Nullable T ingredient, boolean isInput) { + this.ingredient = ingredient; + this.isInput = isInput; + } + + @Nullable + public T getIngredient() { + return ingredient; + } + + public boolean isInput() { + return isInput; + } + +} diff --git a/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java b/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java new file mode 100644 index 00000000..75347312 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java @@ -0,0 +1,12 @@ +package com.glodblock.github.integration.jei.interfaces; + +import com.glodblock.github.integration.jei.WrappedIngredient; +import mezz.jei.api.gui.IRecipeLayout; + +import java.util.stream.Stream; + +public interface IngredientExtractor { + + Stream> extract(IRecipeLayout recipeLayout); + +} diff --git a/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java b/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java new file mode 100644 index 00000000..617c6da7 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java @@ -0,0 +1,19 @@ +package com.glodblock.github.integration.pauto; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import thelm.packagedauto.api.RecipeTypeRegistry; + +public class PackagedFluidCrafting { + + public static void init() { + RecipeTypeRegistry.registerRecipeType(RecipeTypeFluidProcessing.INSTANCE); + } + + @SideOnly(Side.CLIENT) + public static void initClient() { + MinecraftForge.EVENT_BUS.register(new RecipeEncoderFluidTooltipHandler()); + } + +} diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java new file mode 100644 index 00000000..d00e9519 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java @@ -0,0 +1,34 @@ +package com.glodblock.github.integration.pauto; + +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.loader.FCItems; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import thelm.packagedauto.client.gui.GuiEncoder; + +import java.util.List; + +public class RecipeEncoderFluidTooltipHandler { + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onItemTooltip(ItemTooltipEvent event) { + if (Minecraft.getMinecraft().currentScreen instanceof GuiEncoder) { + ItemStack stack = event.getItemStack(); + if (stack.getItem() == FCItems.FLUID_PACKET) { + FluidStack fluid = ItemFluidPacket.getFluidStack(stack); + if (fluid != null) { + List tooltip = event.getToolTip(); + tooltip.clear(); + tooltip.add(fluid.getLocalizedName()); + tooltip.add(String.format(TextFormatting.GRAY + "%,d mB", fluid.amount)); + } + } + } + } + +} diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java new file mode 100644 index 00000000..a23e79ef --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java @@ -0,0 +1,13 @@ +package com.glodblock.github.integration.pauto; + +import thelm.packagedauto.api.IRecipeType; +import thelm.packagedauto.recipe.RecipeInfoProcessing; + +public class RecipeInfoFluidProcessing extends RecipeInfoProcessing { + + @Override + public IRecipeType getRecipeType() { + return RecipeTypeFluidProcessing.INSTANCE; + } + +} diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java new file mode 100644 index 00000000..0ad3e602 --- /dev/null +++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java @@ -0,0 +1,150 @@ +package com.glodblock.github.integration.pauto; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.integration.jei.FCJeiPlugin; +import com.glodblock.github.integration.jei.WrappedIngredient; +import com.glodblock.github.loader.FCBlocks; +import it.unimi.dsi.fastutil.ints.*; +import mezz.jei.api.gui.IGuiIngredient; +import mezz.jei.api.gui.IRecipeLayout; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.translation.I18n; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.common.Optional; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import thelm.packagedauto.api.IRecipeInfo; +import thelm.packagedauto.api.IRecipeType; +import thelm.packagedauto.integration.jei.PackagedAutoJEIPlugin; + +import java.awt.*; +import java.util.Iterator; +import java.util.List; + +public class RecipeTypeFluidProcessing implements IRecipeType { + + public static final RecipeTypeFluidProcessing INSTANCE = new RecipeTypeFluidProcessing(); + + private static final ResourceLocation NAME = FluidCraft.resource("fluid_processing"); + private static final IntSet SLOTS; + private static final Color SLOT_COLOUR = new Color(0x8B8BAC); + private static final int NUM_SLOTS_CRAFT = 81, NUM_SLOTS_OUT = 9; + + static { + IntSet slots = new IntOpenHashSet(); + for (int i = 0; i < 90; i++) { + slots.add(i); + } + SLOTS = IntSets.unmodifiable(slots); + } + + private RecipeTypeFluidProcessing() { + // NO-OP + } + + @Override + public ResourceLocation getName() { + return NAME; + } + + @SuppressWarnings("deprecation") + @Override + public String getLocalizedName() { + return I18n.translateToLocal("ae2fc.pauto.fluid_processing.name"); + } + + @SuppressWarnings("deprecation") + @Override + public String getLocalizedNameShort() { + return I18n.translateToLocal("ae2fc.pauto.fluid_processing.name_short"); + } + + @Override + public IRecipeInfo getNewRecipeInfo() { + return new RecipeInfoFluidProcessing(); + } + + @Override + public IntSet getEnabledSlots() { + return SLOTS; + } + + @Override + public boolean canSetOutput() { + return true; + } + + @Override + public boolean hasMachine() { + return false; + } + + @Override + public List getJEICategories() { + return PackagedAutoJEIPlugin.getAllRecipeCategories(); + } + + @SideOnly(Side.CLIENT) + @Override + public Object getRepresentation() { + return new ItemStack(FCBlocks.FLUID_PATTERN_ENCODER); + } + + @SideOnly(Side.CLIENT) + @Override + public Color getSlotColor(int slot) { + return SLOT_COLOUR; + } + + @Optional.Method(modid = "jei") + @Override + public Int2ObjectMap getRecipeTransferMap(IRecipeLayout recipeLayout, String category) { + Int2ObjectMap tfrs = new Int2ObjectOpenHashMap<>(); + int ndxCrafting = 0, ndxOutput = 0; + for (IGuiIngredient ing : recipeLayout.getItemStacks().getGuiIngredients().values()) { + if (ing.isInput()) { + if (ndxCrafting < NUM_SLOTS_CRAFT) { + ItemStack stack = ing.getDisplayedIngredient(); + if (stack != null) { + tfrs.put(ndxCrafting++, stack); + } + } + } else { + if (ndxOutput < NUM_SLOTS_OUT) { + ItemStack stack = ing.getDisplayedIngredient(); + if (stack != null) { + tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, stack); + } + } + } + } + for (IGuiIngredient ing : recipeLayout.getFluidStacks().getGuiIngredients().values()) { + if (ing.isInput()) { + if (ndxCrafting < NUM_SLOTS_CRAFT) { + tfrs.put(ndxCrafting++, ItemFluidPacket.newStack(ing.getDisplayedIngredient())); + } + } else { + if (ndxOutput < NUM_SLOTS_OUT) { + tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, ItemFluidPacket.newStack(ing.getDisplayedIngredient())); + } + } + } + Iterator> iter = FCJeiPlugin.getExtraExtractors().extractFluids(recipeLayout).iterator(); + while (iter.hasNext()) { + WrappedIngredient ing = iter.next(); + if (ing.isInput()) { + if (ndxCrafting < NUM_SLOTS_CRAFT) { + tfrs.put(ndxCrafting++, ItemFluidPacket.newStack(ing.getIngredient())); + } + } else { + if (ndxOutput < NUM_SLOTS_OUT) { + tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, ItemFluidPacket.newStack(ing.getIngredient())); + } + } + } + return tfrs; + } + +} 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 00000000..bc8b01af --- /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/FCPriorityHost.java b/src/main/java/com/glodblock/github/interfaces/FCPriorityHost.java new file mode 100644 index 00000000..af5f3aef --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/FCPriorityHost.java @@ -0,0 +1,16 @@ +package com.glodblock.github.interfaces; + +import appeng.core.sync.GuiBridge; +import appeng.helpers.IPriorityHost; +import com.glodblock.github.inventory.GuiType; + +public interface FCPriorityHost extends IPriorityHost { + + GuiType getGuiType(); + + @Override + default GuiBridge getGuiBridge() { + return GuiBridge.GUI_Handler; + } + +} \ No newline at end of file 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 00000000..6fab7cb3 --- /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 00000000..48ee0120 --- /dev/null +++ b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java @@ -0,0 +1,9 @@ +package com.glodblock.github.interfaces; + +import appeng.api.storage.data.IAEItemStack; + +public interface PatternConsumer { + + void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs); + +} \ 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 00000000..506ad2b2 --- /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 00000000..97361d8d --- /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/AeStackInventoryImpl.java b/src/main/java/com/glodblock/github/inventory/AeStackInventoryImpl.java new file mode 100644 index 00000000..59e669e2 --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/AeStackInventoryImpl.java @@ -0,0 +1,98 @@ +package com.glodblock.github.inventory; + +import appeng.api.storage.IStorageChannel; +import appeng.api.storage.data.IAEStack; +import appeng.util.inv.IAEAppEngInventory; +import com.glodblock.github.interfaces.AeStackInventory; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.common.util.Constants; +import org.apache.logging.log4j.core.util.ObjectArrayIterator; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Iterator; +import java.util.stream.Stream; + +public class AeStackInventoryImpl > implements AeStackInventory { + + private final IStorageChannel channel; + private final T[] inv; + @Nullable + private final IAEAppEngInventory owner; + + @SuppressWarnings("unchecked") + public AeStackInventoryImpl(IStorageChannel channel, int slotCount, @Nullable IAEAppEngInventory owner) { + this.channel = channel; + this.inv = (T[])new IAEStack[slotCount]; + this.owner = owner; + } + + public AeStackInventoryImpl(IStorageChannel channel, int slotCount) { + this(channel, slotCount, null); + } + + @Override + public int getSlotCount() { + return inv.length; + } + + @Override + @Nullable + public T getStack(int slot) { + return inv[slot]; + } + + @Override + public void setStack(int slot, @Nullable T stack) { + inv[slot] = stack; + if (owner != null) { + owner.saveChanges(); + } + } + + @Override + @Nonnull + public Iterator iterator() { + return new ObjectArrayIterator<>(inv); + } + + @Override + public Stream stream() { + return Arrays.stream(inv); + } + + public void writeToNbt(NBTTagCompound tag) { + NBTTagList stacksTag = new NBTTagList(); + for (T stack : inv) { + if (stack == null) { + stacksTag.appendTag(new NBTTagCompound()); + } else { + NBTTagCompound stackTag = new NBTTagCompound(); + stack.writeToNBT(stackTag); + stacksTag.appendTag(stackTag); + } + } + tag.setTag("Contents", stacksTag); + } + + public void writeToNbt(NBTTagCompound parentTag, String key) { + NBTTagCompound tag = new NBTTagCompound(); + writeToNbt(tag); + parentTag.setTag(key, tag); + } + + public void readFromNbt(NBTTagCompound tag) { + NBTTagList stacksTag = tag.getTagList("Contents", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < inv.length; i++) { + NBTTagCompound stackTag = stacksTag.getCompoundTagAt(i); + inv[i] = stackTag.isEmpty() ? null : channel.createFromNBT(stackTag); + } + } + + public void readFromNbt(NBTTagCompound parentTag, String key) { + readFromNbt(parentTag.getCompoundTag(key)); + } + +} \ 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 00000000..f463f647 --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java @@ -0,0 +1,166 @@ +package com.glodblock.github.inventory; + +import appeng.api.config.FuzzyMode; +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.ItemFluidPacket; +import com.glodblock.github.util.Ae2Reflect; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +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.fluids.capability.IFluidTankProperties; +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 FluidConvertingInventoryAdaptor wrap(ICapabilityProvider capProvider, EnumFacing face) { + // sometimes i wish i had the monadic version from 1.15 + return new FluidConvertingInventoryAdaptor( + capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face) + ? Objects.requireNonNull(capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face)) + : null, + capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face) + ? Objects.requireNonNull(capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face)) + : null); + } + + @Nullable + private final InventoryAdaptor invItems; + @Nullable + private final IFluidHandler invFluids; + + public FluidConvertingInventoryAdaptor(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids) { + this.invItems = invItems != null ? new AdaptorItemHandler(invItems) : null; + this.invFluids = invFluids; + } + + @Override + public ItemStack addItems(ItemStack toBeAdded) { + if (toBeAdded.getItem() instanceof ItemFluidPacket) { + if (invFluids != null) { + FluidStack fluid = ItemFluidPacket.getFluidStack(toBeAdded); + if (fluid != null) { + int filled = invFluids.fill(fluid, true); + if (filled > 0) { + fluid.amount -= 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) { + if (invFluids != null) { + FluidStack fluid = ItemFluidPacket.getFluidStack(toBeSimulated); + if (fluid != null) { + int filled = invFluids.fill(fluid, false); + if (filled > 0) { + fluid.amount -= 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() { + if (invFluids != null) { + for (IFluidTankProperties tank : invFluids.getTankProperties()) { + FluidStack fluid = tank.getContents(); + if (fluid != null && fluid.amount > 0) { + return true; + } + } + } + return invItems != null && invItems.containsItems(); + } + + @Override + public boolean hasSlots() { + return (invFluids != null && invFluids.getTankProperties().length > 0) + || (invItems != null && invItems.hasSlots()); + } + + @Override + @Nonnull + public Iterator iterator() { + return new SlotIterator( + invFluids != null ? invFluids.getTankProperties() : new IFluidTankProperties[0], + invItems != null ? invItems.iterator() : Collections.emptyIterator()); + } + + private static class SlotIterator implements Iterator { + + private final IFluidTankProperties[] tanks; + private final Iterator itemSlots; + private int nextSlotIndex = 0; + + SlotIterator(IFluidTankProperties[] tanks, Iterator itemSlots) { + this.tanks = tanks; + this.itemSlots = itemSlots; + } + + @Override + public boolean hasNext() { + return nextSlotIndex < tanks.length || itemSlots.hasNext(); + } + + @Override + public ItemSlot next() { + if (nextSlotIndex < tanks.length) { + FluidStack fluid = tanks[nextSlotIndex].getContents(); + ItemSlot slot = new ItemSlot(); + slot.setSlot(nextSlotIndex++); + slot.setItemStack(fluid != null ? 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/inventory/FluidConvertingInventoryCrafting.java b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java new file mode 100644 index 00000000..f3fdb7bb --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java @@ -0,0 +1,32 @@ +package com.glodblock.github.inventory; + +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +public class FluidConvertingInventoryCrafting extends InventoryCrafting { + + public FluidConvertingInventoryCrafting(Container container, int width, int height) { + super(container, width, height); + } + + @Override + public void setInventorySlotContents(int index, ItemStack stack) { + if (stack.getItem() instanceof ItemFluidDrop) { + FluidStack fluid = ItemFluidDrop.getFluidStack(stack); + if (fluid != null) { + super.setInventorySlotContents(index, ItemFluidPacket.newStack(new FluidStack(fluid, stack.getCount()))); + } else { + // wtf? + super.setInventorySlotContents(index, ItemFluidPacket.newStack(new FluidStack(FluidRegistry.WATER, 1000))); + } + } else { + super.setInventorySlotContents(index, stack); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/inventory/GuiType.java b/src/main/java/com/glodblock/github/inventory/GuiType.java new file mode 100644 index 00000000..b6d824bc --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/GuiType.java @@ -0,0 +1,265 @@ +package com.glodblock.github.inventory; + +import appeng.api.parts.IPart; +import appeng.api.parts.IPartHost; +import appeng.api.storage.ITerminalHost; +import appeng.api.util.AEPartLocation; +import appeng.container.AEBaseContainer; +import appeng.container.ContainerOpenContext; +import appeng.container.implementations.ContainerCraftingStatus; +import appeng.container.implementations.ContainerInterface; +import appeng.container.implementations.ContainerPriority; +import appeng.fluids.container.ContainerFluidInterface; +import appeng.fluids.helper.IFluidInterfaceHost; +import appeng.helpers.IInterfaceHost; +import com.glodblock.github.client.*; +import com.glodblock.github.client.container.*; +import com.glodblock.github.common.part.PartFluidPatternTerminal; +import com.glodblock.github.common.tile.TileBurette; +import com.glodblock.github.common.tile.TileFluidPacketDecoder; +import com.glodblock.github.common.tile.TileFluidPatternEncoder; +import com.glodblock.github.common.tile.TileIngredientBuffer; +import com.glodblock.github.interfaces.FCPriorityHost; +import com.google.common.collect.ImmutableList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.List; + +public enum GuiType { + + INGREDIENT_BUFFER(new TileGuiFactory(TileIngredientBuffer.class) { + @Override + protected Object createServerGui(EntityPlayer player, TileIngredientBuffer inv) { + return new ContainerIngredientBuffer(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, TileIngredientBuffer inv) { + return new GuiIngredientBuffer(player.inventory, inv); + } + }), + + FLUID_PATTERN_ENCODER(new TileGuiFactory(TileFluidPatternEncoder.class) { + @Override + protected Object createServerGui(EntityPlayer player, TileFluidPatternEncoder inv) { + return new ContainerFluidPatternEncoder(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, TileFluidPatternEncoder inv) { + return new GuiFluidPatternEncoder(player.inventory, inv); + } + }), + + FLUID_PACKET_DECODER(new TileGuiFactory(TileFluidPacketDecoder.class) { + @Override + protected Object createServerGui(EntityPlayer player, TileFluidPacketDecoder inv) { + return new ContainerFluidPacketDecoder(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, TileFluidPacketDecoder inv) { + return new GuiFluidPacketDecoder(player.inventory, inv); + } + }), + + PRECISION_BURETTE(new TileGuiFactory(TileBurette.class) { + @Override + protected Object createServerGui(EntityPlayer player, TileBurette inv) { + return new ContainerBurette(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, TileBurette inv) { + return new GuiBurette(player.inventory, inv); + } + }), + + DUAL_ITEM_INTERFACE(new PartOrTileGuiFactory(IInterfaceHost.class) { + @Override + protected Object createServerGui(EntityPlayer player, IInterfaceHost inv) { + return new ContainerInterface(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, IInterfaceHost inv) { + return new GuiItemDualInterface(player.inventory, inv); + } + }), + + DUAL_FLUID_INTERFACE(new PartOrTileGuiFactory(IFluidInterfaceHost.class) { + @Override + protected Object createServerGui(EntityPlayer player, IFluidInterfaceHost inv) { + return new ContainerFluidInterface(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, IFluidInterfaceHost inv) { + return new GuiFluidDualInterface(player.inventory, inv); + } + }), + + FLUID_PAT_TERM_CRAFTING_STATUS(new PartGuiFactory(PartFluidPatternTerminal.class) { + @Override + protected Object createServerGui(EntityPlayer player, PartFluidPatternTerminal inv) { + return new ContainerCraftingStatus(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, PartFluidPatternTerminal inv) { + return new GuiFluidPatternTerminalCraftingStatus(player.inventory, inv); + } + }), + + FLUID_PATTERN_TERMINAL(new PartGuiFactory(ITerminalHost.class) { + @Override + protected Object createServerGui(EntityPlayer player, ITerminalHost inv) { + return new ContainerFluidPatternTerminal(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, ITerminalHost inv) { + return new GuiFluidPatternTerminal(player.inventory, inv); + } + }), + + PRIORITY(new PartOrTileGuiFactory(FCPriorityHost.class) { + @Override + protected Object createServerGui(EntityPlayer player, FCPriorityHost inv) { + return new ContainerPriority(player.inventory, inv); + } + + @Override + protected Object createClientGui(EntityPlayer player, FCPriorityHost inv) { + return new GuiFCPriority(player.inventory, inv); + } + }); + + public static final List VALUES = ImmutableList.copyOf(values()); + + @Nullable + public static GuiType getByOrdinal(int ordinal) { + return ordinal < 0 || ordinal >= VALUES.size() ? null : VALUES.get(ordinal); + } + + final GuiFactory guiFactory; + + GuiType(GuiFactory guiFactory) { + this.guiFactory = guiFactory; + } + + public interface GuiFactory { + + @Nullable + Object createServerGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face); + + @SideOnly(Side.CLIENT) + @Nullable + Object createClientGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face); + + } + + private static abstract class TileGuiFactory implements GuiFactory { + + protected final Class invClass; + + TileGuiFactory(Class invClass) { + this.invClass = invClass; + } + + @Nullable + protected T getInventory(TileEntity tile, EnumFacing face) { + return invClass.isInstance(tile) ? invClass.cast(tile) : null; + } + + @Nullable + @Override + public Object createServerGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face) { + TileEntity tile = world.getTileEntity(new BlockPos(x, y, z)); + if (tile == null) { + return null; + } + T inv = getInventory(tile, face); + if (inv == null) { + return null; + } + Object gui = createServerGui(player, inv); + if (gui instanceof AEBaseContainer) { + ContainerOpenContext ctx = new ContainerOpenContext(inv); + ctx.setWorld(world); + ctx.setX(x); + ctx.setY(y); + ctx.setZ(z); + ctx.setSide(AEPartLocation.fromFacing(face)); + ((AEBaseContainer)gui).setOpenContext(ctx); + } + return gui; + } + + @Nullable + protected abstract Object createServerGui(EntityPlayer player, T inv); + + @Nullable + @Override + public Object createClientGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face) { + TileEntity tile = world.getTileEntity(new BlockPos(x, y, z)); + if (tile == null) { + return null; + } + T inv = getInventory(tile, face); + return inv != null ? createClientGui(player, inv) : null; + } + + @Nullable + protected abstract Object createClientGui(EntityPlayer player, T inv); + + } + + private static abstract class PartOrTileGuiFactory extends TileGuiFactory { + + PartOrTileGuiFactory(Class invClass) { + super(invClass); + } + + @Nullable + @Override + protected T getInventory(TileEntity tile, EnumFacing face) { + if (tile instanceof IPartHost) { + IPart part = ((IPartHost)tile).getPart(face); + if (invClass.isInstance(part)) { + return invClass.cast(part); + } + } + return super.getInventory(tile, face); + } + + } + + private static abstract class PartGuiFactory extends TileGuiFactory { + + PartGuiFactory(Class invClass) { + super(invClass); + } + + @Nullable + @Override + protected T getInventory(TileEntity tile, EnumFacing face) { + if (tile instanceof IPartHost) { + IPart part = ((IPartHost)tile).getPart(face); + if (invClass.isInstance(part)) { + return invClass.cast(part); + } + } + return null; + } + + } + +} diff --git a/src/main/java/com/glodblock/github/inventory/InventoryHandler.java b/src/main/java/com/glodblock/github/inventory/InventoryHandler.java new file mode 100644 index 00000000..4561fdb4 --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/InventoryHandler.java @@ -0,0 +1,51 @@ +package com.glodblock.github.inventory; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.network.CPacketSwitchGuis; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.network.IGuiHandler; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; + +public class InventoryHandler implements IGuiHandler { + + public static void switchGui(GuiType guiType) { + FluidCraft.proxy.netHandler.sendToServer(new CPacketSwitchGuis(guiType)); + } + + public static void openGui(EntityPlayer player, World world, BlockPos pos, EnumFacing face, GuiType guiType) { + player.openGui(FluidCraft.INSTANCE, + (guiType.ordinal() << 3) | face.ordinal(), world, pos.getX(), pos.getY(), pos.getZ()); + } + + @Nullable + @Override + public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) { + int faceOrd = id & 0x7; + if (faceOrd > EnumFacing.VALUES.length) { + return null; + } + EnumFacing face = EnumFacing.VALUES[faceOrd]; + GuiType type = GuiType.getByOrdinal(id >>> 3); + return type != null ? type.guiFactory.createServerGui(player, world, x, y, z, face) : null; + } + + @SideOnly(Side.CLIENT) + @Nullable + @Override + public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) { + int faceOrd = id & 0x7; + if (faceOrd > EnumFacing.VALUES.length) { + return null; + } + EnumFacing face = EnumFacing.VALUES[faceOrd]; + GuiType type = GuiType.getByOrdinal(id >>> 3); + return type != null ? type.guiFactory.createClientGui(player, world, x, y, z, face) : null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java b/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java new file mode 100644 index 00000000..d0026120 --- /dev/null +++ b/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java @@ -0,0 +1,139 @@ +package com.glodblock.github.inventory.slot; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +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 EntityPlayer 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); + } + + @Override + @SideOnly(Side.CLIENT) + @Nullable + public String getSlotTexture() { + return delegate.getSlotTexture(); + } + + @Override + @Nonnull + public ItemStack decrStackSize(int amount) { + return delegate.decrStackSize(amount); + } + + @Override + public boolean isHere(@Nonnull IInventory inv, int slotIn) { + return delegate.isHere(inv, slotIn); + } + + @Override + public boolean canTakeStack(@Nonnull EntityPlayer playerIn) { + return delegate.canTakeStack(playerIn); + } + + @Override + @SideOnly(Side.CLIENT) + public boolean isEnabled() { + return delegate.isEnabled(); + } + + @Override + @SideOnly(Side.CLIENT) + @Nonnull + public ResourceLocation getBackgroundLocation() { + return delegate.getBackgroundLocation(); + } + + @Override + @SideOnly(Side.CLIENT) + public void setBackgroundLocation(@Nonnull ResourceLocation texture) { + delegate.setBackgroundLocation(texture); + } + + @Override + public void setBackgroundName(@Nullable String name) { + delegate.setBackgroundName(name); + } + + @Override + @SideOnly(Side.CLIENT) + @Nullable + public TextureAtlasSprite getBackgroundSprite() { + return delegate.getBackgroundSprite(); + } + + @Override + public int getSlotIndex() { + return delegate.getSlotIndex(); + } + + @Override + public boolean isSameInventory(@Nonnull Slot other) { + return delegate.isSameInventory(other); + } + +} 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 00000000..ec3db543 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/ChannelLoader.java @@ -0,0 +1,19 @@ +package com.glodblock.github.loader; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.network.*; +import net.minecraftforge.fml.relauncher.Side; + +public class ChannelLoader implements Runnable { + + @Override + public void run() { + int id = 0; + FluidCraft.proxy.netHandler.registerMessage(new CPacketSwitchGuis.Handler(), CPacketSwitchGuis.class, id ++, Side.SERVER); + FluidCraft.proxy.netHandler.registerMessage(new CPacketDumpTank.Handler(), CPacketDumpTank.class, id ++, Side.SERVER); + FluidCraft.proxy.netHandler.registerMessage(new CPacketTransposeFluid.Handler(), CPacketTransposeFluid.class, id ++, Side.SERVER); + FluidCraft.proxy.netHandler.registerMessage(new CPacketEncodePattern.Handler(), CPacketEncodePattern.class, id ++, Side.SERVER); + FluidCraft.proxy.netHandler.registerMessage(new CPacketLoadPattern.Handler(), CPacketLoadPattern.class, id ++, Side.SERVER); + } + +} 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 00000000..14fde4b5 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/FCBlocks.java @@ -0,0 +1,33 @@ +package com.glodblock.github.loader; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.block.*; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.util.NameConst; +import net.minecraftforge.fml.common.registry.GameRegistry; + +public class FCBlocks { + + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_DISCRETIZER) + public static BlockFluidDiscretizer FLUID_DISCRETIZER; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_PATTERN_ENCODER) + public static BlockFluidPatternEncoder FLUID_PATTERN_ENCODER; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_PACKET_DECODER) + public static BlockFluidPacketDecoder FLUID_PACKET_DECODER; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_INGREDIENT_BUFFER) + public static BlockIngredientBuffer INGREDIENT_BUFFER; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_BURETTE) + public static BlockBurette BURETTE; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_DUAL_INTERFACE) + public static BlockDualInterface DUAL_INTERFACE; + + public static void init(RegistryHandler regHandler) { + regHandler.block(NameConst.BLOCK_FLUID_DISCRETIZER, new BlockFluidDiscretizer()); + regHandler.block(NameConst.BLOCK_FLUID_PATTERN_ENCODER, new BlockFluidPatternEncoder()); + regHandler.block(NameConst.BLOCK_FLUID_PACKET_DECODER, new BlockFluidPacketDecoder()); + regHandler.block(NameConst.BLOCK_INGREDIENT_BUFFER, new BlockIngredientBuffer()); + regHandler.block(NameConst.BLOCK_BURETTE, new BlockBurette()); + regHandler.block(NameConst.BLOCK_DUAL_INTERFACE, new BlockDualInterface()); + } + +} 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 00000000..db116d35 --- /dev/null +++ b/src/main/java/com/glodblock/github/loader/FCItems.java @@ -0,0 +1,39 @@ +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.creativetab.CreativeTabs; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; + +public class FCItems { + + public static final CreativeTabs TAB_AE2FC = new CreativeTabs(FluidCraft.MODID) { + @Override + public ItemStack createIcon() { + return new ItemStack(DENSE_ENCODED_PATTERN); + } + }; + + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_FLUID_DROP) + public static ItemFluidDrop FLUID_DROP; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_FLUID_PACKET) + public static ItemFluidPacket FLUID_PACKET; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_DENSE_ENCODED_PATTERN) + public static ItemFluidEncodedPattern DENSE_ENCODED_PATTERN; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_PART_DUAL_INTERFACE) + public static ItemPartDualInterface PART_DUAL_INTERFACE; + @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL) + public static ItemPartFluidPatternTerminal PART_FLUID_PATTERN_TERMINAL; + + public static void init(RegistryHandler regHandler) { + regHandler.item(NameConst.ITEM_FLUID_DROP, new ItemFluidDrop()); + regHandler.item(NameConst.ITEM_FLUID_PACKET, new ItemFluidPacket()); + regHandler.item(NameConst.ITEM_DENSE_ENCODED_PATTERN, new ItemFluidEncodedPattern()); + regHandler.item(NameConst.ITEM_PART_DUAL_INTERFACE, new ItemPartDualInterface()); + regHandler.item(NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL, new ItemPartFluidPatternTerminal()); + } + +} diff --git a/src/main/java/com/glodblock/github/network/CPacketDumpTank.java b/src/main/java/com/glodblock/github/network/CPacketDumpTank.java new file mode 100644 index 00000000..4a4962da --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketDumpTank.java @@ -0,0 +1,50 @@ +package com.glodblock.github.network; + +import com.glodblock.github.interfaces.TankDumpable; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; + +public class CPacketDumpTank implements IMessage { + + private int index; + + public CPacketDumpTank(int index) { + this.index = index; + } + + public CPacketDumpTank() { + // NO-OP + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeShort(index); + } + + @Override + public void fromBytes(ByteBuf buf) { + index = buf.readShort(); + } + + public static class Handler implements IMessageHandler { + + @Nullable + @Override + public IMessage onMessage(CPacketDumpTank message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + player.getServerWorld().addScheduledTask(() -> { + if (player.openContainer instanceof TankDumpable) { + ((TankDumpable)player.openContainer).dumpTank(message.index); + } + }); + return null; + } + + } + +} diff --git a/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java b/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java new file mode 100644 index 00000000..56e083ef --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java @@ -0,0 +1,40 @@ +package com.glodblock.github.network; + +import com.glodblock.github.client.container.ContainerFluidPatternEncoder; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; + +public class CPacketEncodePattern implements IMessage { + + @Override + public void toBytes(ByteBuf buf) { + // NO-OP + } + + @Override + public void fromBytes(ByteBuf buf) { + // NO-OP + } + + public static class Handler implements IMessageHandler { + + @Nullable + @Override + public IMessage onMessage(CPacketEncodePattern message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + player.getServerWorld().addScheduledTask(() -> { + if (player.openContainer instanceof ContainerFluidPatternEncoder) { + ((ContainerFluidPatternEncoder)player.openContainer).encodePattern(); + } + }); + return null; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java b/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java new file mode 100644 index 00000000..b0a04c4d --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java @@ -0,0 +1,87 @@ +package com.glodblock.github.network; + +import appeng.api.storage.data.IAEItemStack; +import appeng.util.item.AEItemStack; +import com.glodblock.github.interfaces.PatternConsumer; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; +import java.io.IOException; + +public class CPacketLoadPattern implements IMessage { + + private IAEItemStack[] crafting, output; + + public CPacketLoadPattern(IAEItemStack[] crafting, IAEItemStack[] output) { + this.crafting = crafting; + this.output = output; + } + + public CPacketLoadPattern() { + // NO-OP + } + + @Override + public void toBytes(ByteBuf buf) { + writeStacksWithNulls(buf, crafting); + writeStacksWithNulls(buf, output); + } + + @Override + public void fromBytes(ByteBuf buf) { + crafting = readStacksWithNulls(buf); + output = readStacksWithNulls(buf); + } + + private static IAEItemStack[] readStacksWithNulls(ByteBuf buf) { + IAEItemStack[] stacks = new IAEItemStack[buf.readByte()]; + int mask = buf.readInt(); + for (int i = 0; i < stacks.length; i++) { + if ((mask & (1 << i)) != 0) { + stacks[i] = AEItemStack.fromPacket(buf); + } + } + return stacks; + } + + private static void writeStacksWithNulls(ByteBuf buf, IAEItemStack[] stacks) { + buf.writeByte(stacks.length); + int mask = 0; + for (int i = 0; i < stacks.length; i++) { + if (stacks[i] != null) { + mask |= 1 << i; + } + } + buf.writeInt(mask); + for (IAEItemStack stack : stacks) { + try { + if (stack != null) { + stack.writeToPacket(buf); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to write AE item stack!", e); + } + } + } + + public static class Handler implements IMessageHandler { + + @Nullable + @Override + public IMessage onMessage(CPacketLoadPattern message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + player.getServerWorld().addScheduledTask(() -> { + if (player.openContainer instanceof PatternConsumer) { + ((PatternConsumer)player.openContainer).acceptPattern(message.crafting, message.output); + } + }); + return null; + } + + } + +} diff --git a/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java b/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java new file mode 100644 index 00000000..6aec08da --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java @@ -0,0 +1,64 @@ +package com.glodblock.github.network; + +import appeng.container.AEBaseContainer; +import appeng.container.ContainerOpenContext; +import com.glodblock.github.inventory.GuiType; +import com.glodblock.github.inventory.InventoryHandler; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; + +public class CPacketSwitchGuis implements IMessage { + + private GuiType guiType; + + public CPacketSwitchGuis(GuiType guiType) { + this.guiType = guiType; + } + + public CPacketSwitchGuis() { + // NO-OP + } + + @Override + public void fromBytes(ByteBuf byteBuf) { + guiType = GuiType.getByOrdinal(byteBuf.readByte()); + } + + @Override + public void toBytes(ByteBuf byteBuf) { + byteBuf.writeByte(guiType != null ? guiType.ordinal() : 0); + } + + public static class Handler implements IMessageHandler { + @Nullable + @Override + public IMessage onMessage(CPacketSwitchGuis message, MessageContext ctx) { + if (message.guiType == null) { + return null; + } + EntityPlayerMP player = ctx.getServerHandler().player; + Container cont = player.openContainer; + if (!(cont instanceof AEBaseContainer)) { + return null; + } + ContainerOpenContext context = ((AEBaseContainer)cont).getOpenContext(); + if (context == null) { + return null; + } + TileEntity te = context.getTile(); + if (te == null) { + return null; + } + InventoryHandler.openGui(player, player.world, te.getPos(), context.getSide().getFacing(), message.guiType); + return null; + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java b/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java new file mode 100644 index 00000000..dbafb30f --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java @@ -0,0 +1,53 @@ +package com.glodblock.github.network; + +import com.glodblock.github.client.container.ContainerBurette; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; + +public class CPacketTransposeFluid implements IMessage { + + private int amount; + private boolean into; + + public CPacketTransposeFluid(int amount, boolean into) { + this.amount = amount; + this.into = into; + } + + public CPacketTransposeFluid() { + // NO-OP + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(amount).writeBoolean(into); + } + + @Override + public void fromBytes(ByteBuf buf) { + amount = buf.readInt(); + into = buf.readBoolean(); + } + + public static class Handler implements IMessageHandler { + + @Nullable + @Override + public IMessage onMessage(CPacketTransposeFluid message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + player.getServerWorld().addScheduledTask(() -> { + if (player.openContainer instanceof ContainerBurette) { + ((ContainerBurette)player.openContainer).tryTransferFluid(message.amount, message.into); + } + }); + return null; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/proxy/ClientProxy.java b/src/main/java/com/glodblock/github/proxy/ClientProxy.java new file mode 100644 index 00000000..873e1558 --- /dev/null +++ b/src/main/java/com/glodblock/github/proxy/ClientProxy.java @@ -0,0 +1,66 @@ +package com.glodblock.github.proxy; + +import appeng.api.util.AEColor; +import com.glodblock.github.client.render.DropColourHandler; +import com.glodblock.github.client.render.RenderIngredientBuffer; +import com.glodblock.github.common.item.ItemFluidDrop; +import com.glodblock.github.common.item.ItemFluidPacket; +import com.glodblock.github.common.tile.TileIngredientBuffer; +import com.glodblock.github.handler.ClientRegistryHandler; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.integration.pauto.PackagedFluidCrafting; +import com.glodblock.github.loader.FCItems; +import net.minecraft.client.Minecraft; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + +public class ClientProxy extends CommonProxy { + + private final DropColourHandler dropColourHandler = new DropColourHandler(); + + @Override + public RegistryHandler createRegistryHandler() { + return new ClientRegistryHandler(); + } + + @Override + public void preInit(FMLPreInitializationEvent event){ + super.preInit(event); + MinecraftForge.EVENT_BUS.register(dropColourHandler); + ClientRegistry.bindTileEntitySpecialRenderer(TileIngredientBuffer.class, new RenderIngredientBuffer()); + } + + @Override + protected void initPackagedAutoIntegration() { + super.initPackagedAutoIntegration(); + PackagedFluidCrafting.initClient(); + } + + @Override + public void init(FMLInitializationEvent event){ + super.init(event); + Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> { + FluidStack fluid = ItemFluidDrop.getFluidStack(s); + return fluid != null ? dropColourHandler.getColour(fluid) : -1; + }, FCItems.FLUID_DROP); + Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> { + if (i == 0) { + return -1; + } + FluidStack fluid = ItemFluidPacket.getFluidStack(s); + return fluid != null ? fluid.getFluid().getColor(fluid) : -1; + }, FCItems.FLUID_PACKET); + Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_FLUID_PATTERN_TERMINAL); + + } + + @Override + public void postInit(FMLPostInitializationEvent event){ + super.postInit(event); + } + +} \ No newline at end of file diff --git a/src/main/java/com/glodblock/github/proxy/CommonProxy.java b/src/main/java/com/glodblock/github/proxy/CommonProxy.java new file mode 100644 index 00000000..ea0dde14 --- /dev/null +++ b/src/main/java/com/glodblock/github/proxy/CommonProxy.java @@ -0,0 +1,86 @@ +package com.glodblock.github.proxy; + +import appeng.api.AEApi; +import appeng.api.definitions.IItemDefinition; +import appeng.core.AppEng; +import appeng.core.features.ItemDefinition; +import appeng.recipes.game.DisassembleRecipe; +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.tile.*; +import com.glodblock.github.handler.RegistryHandler; +import com.glodblock.github.integration.pauto.PackagedFluidCrafting; +import com.glodblock.github.inventory.InventoryHandler; +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.ModAndClassUtil; +import com.glodblock.github.util.NameConst; +import net.minecraft.item.Item; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.relauncher.Side; + +import java.util.Objects; + +public class CommonProxy { + + public final RegistryHandler regHandler = createRegistryHandler(); + public final SimpleNetworkWrapper netHandler = NetworkRegistry.INSTANCE.newSimpleChannel(FluidCraft.MODID); + + public RegistryHandler createRegistryHandler() { + return new RegistryHandler(); + } + + public void preInit(FMLPreInitializationEvent event) { + MinecraftForge.EVENT_BUS.register(regHandler); + FCBlocks.init(regHandler); + FCItems.init(regHandler); + GameRegistry.registerTileEntity(TileFluidDiscretizer.class, FluidCraft.resource(NameConst.BLOCK_FLUID_DISCRETIZER)); + GameRegistry.registerTileEntity(TileFluidPatternEncoder.class, FluidCraft.resource(NameConst.BLOCK_FLUID_PATTERN_ENCODER)); + GameRegistry.registerTileEntity(TileFluidPacketDecoder.class, FluidCraft.resource(NameConst.BLOCK_FLUID_PACKET_DECODER)); + GameRegistry.registerTileEntity(TileIngredientBuffer.class, FluidCraft.resource(NameConst.BLOCK_INGREDIENT_BUFFER)); + GameRegistry.registerTileEntity(TileBurette.class, FluidCraft.resource(NameConst.BLOCK_BURETTE)); + GameRegistry.registerTileEntity(TileDualInterface.class, FluidCraft.resource(NameConst.BLOCK_DUAL_INTERFACE)); + (new ChannelLoader()).run(); + if (ModAndClassUtil.AUTO_P) { + initPackagedAutoIntegration(); + } + } + + protected void initPackagedAutoIntegration() { + PackagedFluidCrafting.init(); + } + + public void init(FMLInitializationEvent event) { + regHandler.onInit(); + IRecipe disassembleRecipe = ForgeRegistries.RECIPES.getValue(new ResourceLocation(AppEng.MOD_ID, "disassemble")); + if (disassembleRecipe instanceof DisassembleRecipe) { + Ae2Reflect.getDisassemblyNonCellMap((DisassembleRecipe)disassembleRecipe).put( + createItemDefn(FCItems.DENSE_ENCODED_PATTERN), + AEApi.instance().definitions().materials().blankPattern()); + } + } + + public void postInit(FMLPostInitializationEvent event) { + NetworkRegistry.INSTANCE.registerGuiHandler(FluidCraft.INSTANCE, new InventoryHandler()); + } + + private static IItemDefinition createItemDefn(Item item) { + return new ItemDefinition(Objects.requireNonNull(item.getRegistryName()).toString(), item); + } + + public SimpleNetworkWrapper getNetHandler() { + return netHandler; + } + +} \ No newline at end of file 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 00000000..ed01916f --- /dev/null +++ b/src/main/java/com/glodblock/github/util/Ae2Reflect.java @@ -0,0 +1,112 @@ +package com.glodblock.github.util; + +import appeng.api.definitions.IItemDefinition; +import appeng.container.implementations.ContainerPatternTerm; +import appeng.container.slot.OptionalSlotFake; +import appeng.container.slot.SlotFakeCraftingMatrix; +import appeng.container.slot.SlotRestrictedInput; +import appeng.recipes.game.DisassembleRecipe; +import appeng.util.inv.ItemSlot; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.InventoryCrafting; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; + +public class Ae2Reflect { + + private static final Method mItemSlot_setExtractable; + private static final Field fDisassembleRecipe_nonCellMappings; + private static final Field fContainerPatternTerm_craftingSlots; + private static final Field fContainerPatternTerm_outputSlots; + private static final Field fContainerPatternTerm_patternSlotIN; + private static final Field fContainerPatternTerm_patternSlotOUT; + private static final Field fInventory_container; + + static { + try { + mItemSlot_setExtractable = reflectMethod(ItemSlot.class, "setExtractable", boolean.class); + fInventory_container = reflectField(InventoryCrafting.class, "eventHandler", "field_70465_c", "c"); + fDisassembleRecipe_nonCellMappings = reflectField(DisassembleRecipe.class, "nonCellMappings"); + fContainerPatternTerm_craftingSlots = reflectField(ContainerPatternTerm.class, "craftingSlots"); + fContainerPatternTerm_outputSlots = reflectField(ContainerPatternTerm.class, "outputSlots"); + fContainerPatternTerm_patternSlotIN = reflectField(ContainerPatternTerm.class, "patternSlotIN"); + fContainerPatternTerm_patternSlotOUT = reflectField(ContainerPatternTerm.class, "patternSlotOUT"); + } 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 { + Method m = owner.getDeclaredMethod(name, paramTypes); + m.setAccessible(true); + return m; + } + + 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 Container getCraftContainer(InventoryCrafting inv) { + return Ae2Reflect.readField(inv, fInventory_container); + } + + 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 SlotFakeCraftingMatrix[] getCraftingSlots(ContainerPatternTerm cont) { + return readField(cont, fContainerPatternTerm_craftingSlots); + } + + public static OptionalSlotFake[] getOutputSlots(ContainerPatternTerm cont) { + return readField(cont, fContainerPatternTerm_outputSlots); + } + + public static SlotRestrictedInput getPatternSlotIn(ContainerPatternTerm cont) { + return readField(cont, fContainerPatternTerm_patternSlotIN); + } + + public static SlotRestrictedInput getPatternSlotOut(ContainerPatternTerm cont) { + return readField(cont, fContainerPatternTerm_patternSlotOUT); + } + +} \ No newline at end of file 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 00000000..2d6078a4 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java @@ -0,0 +1,90 @@ +package com.glodblock.github.util; + +import appeng.client.gui.AEBaseGui; +import appeng.client.gui.implementations.*; +import appeng.client.gui.widgets.GuiTabButton; +import appeng.client.render.StackSizeRenderer; +import appeng.fluids.client.gui.GuiFluidInterface; +import com.glodblock.github.client.container.ContainerFluidPatternTerminal; +import com.google.common.collect.ImmutableMap; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraftforge.common.model.TRSRTransformation; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +@SuppressWarnings("unchecked") +public class Ae2ReflectClient { + + private static final Field fAEBaseGui_stackSizeRenderer; + private static final Constructor cItemEncodedPatternBakedModel; + private static final Field fGuiPriority_originalGuiBtn; + private static final Field fGuiCraftingStatus_originalGuiBtn; + private static final Field fGuiPatternTerm_container; + private static final Field fGuiMEMonitorable_monitorableContainer; + private static final Field fGuiMEMonitorable_configSrc; + private static final Field fGuiMEMonitorable_craftingStatusBtn; + private static final Field fGuiInterface_priority; + private static final Field fGuiFluidInterface_priority; + + static { + try { + fAEBaseGui_stackSizeRenderer = Ae2Reflect.reflectField(AEBaseGui.class, "stackSizeRenderer"); + cItemEncodedPatternBakedModel = (Constructor)Class + .forName("appeng.client.render.crafting.ItemEncodedPatternBakedModel") + .getDeclaredConstructor(IBakedModel.class, ImmutableMap.class); + cItemEncodedPatternBakedModel.setAccessible(true); + fGuiPriority_originalGuiBtn = Ae2Reflect.reflectField(GuiPriority.class, "originalGuiBtn"); + fGuiCraftingStatus_originalGuiBtn = Ae2Reflect.reflectField(GuiCraftingStatus.class, "originalGuiBtn"); + fGuiPatternTerm_container = Ae2Reflect.reflectField(GuiPatternTerm.class, "container"); + fGuiMEMonitorable_monitorableContainer = Ae2Reflect.reflectField(GuiMEMonitorable.class, "monitorableContainer"); + fGuiMEMonitorable_configSrc = Ae2Reflect.reflectField(GuiMEMonitorable.class, "configSrc"); + fGuiMEMonitorable_craftingStatusBtn = Ae2Reflect.reflectField(GuiMEMonitorable.class, "craftingStatusBtn"); + fGuiInterface_priority = Ae2Reflect.reflectField(GuiInterface.class, "priority"); + fGuiFluidInterface_priority = Ae2Reflect.reflectField(GuiFluidInterface.class, "priority"); + } catch (Exception e) { + throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e); + } + } + + public static StackSizeRenderer getStackSizeRenderer(AEBaseGui gui) { + return Ae2Reflect.readField(gui, fAEBaseGui_stackSizeRenderer); + } + + public static IBakedModel bakeEncodedPatternModel(IBakedModel baseModel, + ImmutableMap transforms) { + try { + return cItemEncodedPatternBakedModel.newInstance(baseModel, transforms); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke constructor: " + cItemEncodedPatternBakedModel, e); + } + } + + public static GuiTabButton getOriginalGuiButton(GuiPriority gui) { + return Ae2Reflect.readField(gui, fGuiPriority_originalGuiBtn); + } + + public static GuiTabButton getOriginalGuiButton(GuiCraftingStatus gui) { + return Ae2Reflect.readField(gui, fGuiCraftingStatus_originalGuiBtn); + } + + public static void setGuiContainer(GuiPatternTerm instance, ContainerFluidPatternTerminal container) { + Ae2Reflect.writeField(instance, fGuiPatternTerm_container, container); + Ae2Reflect.writeField(instance, fGuiMEMonitorable_monitorableContainer, container); + Ae2Reflect.writeField(instance, fGuiMEMonitorable_configSrc, container.getConfigManager()); + } + + public static GuiTabButton getCraftingStatusButton(GuiMEMonitorable gui) { + return Ae2Reflect.readField(gui, fGuiMEMonitorable_craftingStatusBtn); + } + + public static GuiTabButton getPriorityButton(GuiInterface gui) { + return Ae2Reflect.readField(gui, fGuiInterface_priority); + } + + public static GuiTabButton getPriorityButton(GuiFluidInterface gui) { + return Ae2Reflect.readField(gui, fGuiFluidInterface_priority); + } + +} diff --git a/src/main/java/com/glodblock/github/util/FluidKey.java b/src/main/java/com/glodblock/github/util/FluidKey.java new file mode 100644 index 00000000..f09dbc39 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/FluidKey.java @@ -0,0 +1,38 @@ +package com.glodblock.github.util; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class FluidKey { + + private final Fluid fluid; + @Nullable + private final NBTTagCompound tag; + + public FluidKey(Fluid fluid, @Nullable NBTTagCompound tag) { + this.fluid = fluid; + this.tag = tag; + } + + public FluidKey(FluidStack fluid) { + this(fluid.getFluid(), fluid.tag); + } + + @Override + public int hashCode() { + return fluid.getName().hashCode() ^ (tag != null ? Integer.rotateLeft(tag.hashCode(), 17) : 0x4e6f5467); // NoTg + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof FluidKey)) { + return false; + } + return fluid.getName().equals(((FluidKey)obj).fluid.getName()) && Objects.equals(tag, ((FluidKey)obj).tag); + } + +} \ No newline at end of file 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 00000000..8fdf5352 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java @@ -0,0 +1,199 @@ +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.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class FluidPatternDetails implements ICraftingPatternDetails, Comparable { + + private final ItemStack patternStack; + private IAEItemStack patternStackAe; + private IAEItemStack[] inputs = null, inputsCond = null, outputs = null, outputsCond = null; + private int priority = 0; + + public FluidPatternDetails(ItemStack stack) { + this.patternStack = stack; + this.patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(stack)); // s2g + } + + @Override + public ItemStack getPattern() { + return patternStack; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public void setPriority(int priority) { + this.priority = priority; + } + + @Override + public boolean isCraftable() { + return false; + } + + @Override + public boolean canSubstitute() { + return false; + } + + @Override + public IAEItemStack[] getInputs() { + return checkInitialized(inputs); + } + + @Override + public IAEItemStack[] getCondensedInputs() { + return checkInitialized(inputsCond); + } + + public boolean setInputs(IAEItemStack[] inputs) { + for (IAEItemStack stack : inputs) { // see note at top of class + if (stack == null) { + return false; + } + } + IAEItemStack[] condensed = condenseStacks(inputs); + if (condensed.length == 0) { + return false; + } + this.inputs = inputs; + this.inputsCond = condensed; + return true; + } + + @Override + public IAEItemStack[] getOutputs() { + return checkInitialized(outputs); + } + + @Override + public IAEItemStack[] getCondensedOutputs() { + return checkInitialized(outputsCond); + } + + public boolean setOutputs(IAEItemStack[] outputs) { + for (IAEItemStack stack : outputs) { // see note at top of class + if (stack == null) { + return false; + } + } + IAEItemStack[] condensed = condenseStacks(outputs); + if (condensed.length == 0) { + return false; + } + this.outputs = outputs; + this.outputsCond = condensed; + return true; + } + + private static IAEItemStack[] condenseStacks(IAEItemStack[] stacks) { + // AE item stacks are equivalent iff they are of the same item type (not accounting for stack size) + // thus, it's not the semantically-correct definition of "equal" but it's useful for matching item types + 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 accMap.values().toArray(new IAEItemStack[0]); + } + + @Override + public ItemStack getOutput(InventoryCrafting craftingInv, World world) { + throw new IllegalStateException("Not a crafting recipe!"); + } + + @Override + public boolean isValidItemForSlot(int slotIndex, ItemStack itemStack, World world) { + throw new IllegalStateException("Not a crafting recipe!"); + } + + private static T checkInitialized(@Nullable T value) { + if (value == null) { + throw new IllegalStateException("Pattern is not initialized!"); + } + return value; + } + + @Override + public int hashCode() { + return patternStackAe.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // ae2 null-checks the pattern stack here for some reason, but doesn't null-check in hashCode() + // this is inconsistent, so i've just decided to assert non-null in the constructor, which is to say that + // the pattern stack can never be null here + return obj instanceof FluidPatternDetails && patternStackAe.equals(((FluidPatternDetails)obj).patternStackAe); + } + + @Override + public int compareTo(FluidPatternDetails o) { + return Integer.compare(o.priority, this.priority); + } + + public ItemStack writeToStack() { + NBTTagCompound tag = new NBTTagCompound(); + tag.setTag("Inputs", writeStackArray(checkInitialized(inputs))); + tag.setTag("Outputs", writeStackArray(checkInitialized(outputs))); + patternStack.setTagCompound(tag); + patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(patternStack)); + return patternStack; + } + + public static NBTTagList writeStackArray(IAEItemStack[] stacks) { + NBTTagList listTag = new NBTTagList(); + for (IAEItemStack stack : stacks) { + if (stack != null) { + // see note at top of class + NBTTagCompound stackTag = new NBTTagCompound(); + stack.writeToNBT(stackTag); + listTag.appendTag(stackTag); + } + } + return listTag; + } + + public boolean readFromStack() { + if (!patternStack.hasTagCompound()) { + return false; + } + NBTTagCompound tag = Objects.requireNonNull(patternStack.getTagCompound()); + // may be possible to enter a partially-correct state if setInputs succeeds but setOutputs failed + // but outside code should treat it as completely incorrect and not attempt to make calls + return setInputs(readStackArray(tag.getTagList("Inputs", Constants.NBT.TAG_COMPOUND), 9)) + && setOutputs(readStackArray(tag.getTagList("Outputs", Constants.NBT.TAG_COMPOUND), 3)); + } + + public static IAEItemStack[] readStackArray(NBTTagList listTag, int maxCount) { + // see note at top of class + IAEItemStack[] stacks = new IAEItemStack[Math.min(listTag.tagCount(), maxCount)]; + for (int i = 0; i < stacks.length; i++) { + stacks[i] = AEItemStack.fromNBT(listTag.getCompoundTagAt(i)); + } + return stacks; + } + +} \ 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 00000000..20bee1e2 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java @@ -0,0 +1,15 @@ +package com.glodblock.github.util; + +import net.minecraftforge.fml.common.Loader; + +public final class ModAndClassUtil { + + public static boolean AUTO_P = false; + + public static void init() { + if (Loader.isModLoaded("packagedauto")) { + AUTO_P = 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 00000000..14ac897f --- /dev/null +++ b/src/main/java/com/glodblock/github/util/MouseRegionManager.java @@ -0,0 +1,82 @@ +package com.glodblock.github.util; + +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.init.SoundEvents; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class MouseRegionManager { + + private final GuiContainer gui; + private final List regions = new ArrayList<>(); + + public MouseRegionManager(GuiContainer 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(int mX, int mY, int button) { + mX -= gui.getGuiLeft(); + mY -= gui.getGuiTop(); + for (Region region : regions) { + if (region.containsMouse(mX, mY) && region.handler.onClick(button)) { + gui.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1F)); + return false; + } + } + return true; + } + + public void render(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.drawHoveringText(tooltip, mX, mY); + 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(int mX, int 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 00000000..2cf43fa5 --- /dev/null +++ b/src/main/java/com/glodblock/github/util/NameConst.java @@ -0,0 +1,41 @@ +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_BURETTE = "burette"; + public static final String BLOCK_DUAL_INTERFACE = "dual_interface"; + + 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 = "dense_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 TT_KEY = FluidCraft.MODID + ".tooltip."; + public static final String TT_FLUID_PACKET = TT_KEY + "fluid_packet"; + 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_BURETTE = GUI_KEY + BLOCK_BURETTE; + + public static final ResourceLocation MODEL_DENSE_ENCODED_PATTERN = FluidCraft.resource("builtin/dense_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/java/com/glodblock/github/util/SetBackedMachineSet.java b/src/main/java/com/glodblock/github/util/SetBackedMachineSet.java new file mode 100644 index 00000000..3b645a2a --- /dev/null +++ b/src/main/java/com/glodblock/github/util/SetBackedMachineSet.java @@ -0,0 +1,61 @@ +package com.glodblock.github.util; + +import appeng.api.networking.IGridHost; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IMachineSet; + +import javax.annotation.Nonnull; +import java.util.Iterator; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class SetBackedMachineSet implements IMachineSet { + + private final Class machineClass; + private final Set backingSet; + + public SetBackedMachineSet(Class machineClass, Set backingSet) { + this.machineClass = machineClass; + this.backingSet = backingSet; + } + + @Nonnull + @Override + public Class getMachineClass() { + return machineClass; + } + + @Override + public int size() { + return backingSet.size(); + } + + @Override + public boolean isEmpty() { + return backingSet.isEmpty(); + } + + @Override + public boolean contains(Object o) { + //noinspection SuspiciousMethodCalls + return backingSet.contains(o); + } + + @Override + @Nonnull + public Iterator iterator() { + return backingSet.iterator(); + } + + @Override + public void forEach(Consumer action) { + backingSet.forEach(action); + } + + @Override + public Spliterator spliterator() { + return backingSet.spliterator(); + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2fc/blockstates/burette.json b/src/main/resources/assets/ae2fc/blockstates/burette.json new file mode 100644 index 00000000..d1ed555f --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/burette.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "ae2fc:burette" + } + } +} 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 00000000..40f5dd3d --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json @@ -0,0 +1,35 @@ +{ + "forge_marker": 1, + "variants": { + "omnidirectional": { + "true": { + "model": "ae2fc:dual_interface" + }, + "false": { + "model": "ae2fc:dual_interface_oriented" + } + }, + "facing": { + "east": { + "x": 90, + "y": 90 + }, + "west": { + "x": 90, + "y": 270 + }, + "south": { + "x": 270 + }, + "north": { + "x": 90 + }, + "up": { + "x": 0 + }, + "down": { + "x": 180 + } + } + } +} 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 00000000..2aece3ea --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "ae2fc: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 00000000..e008e19a --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "ae2fc:fluid_packet_decoder" + } + } +} diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json new file mode 100644 index 00000000..28ddc775 --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "ae2fc:fluid_pattern_encoder" + } + } +} 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 00000000..15db1e22 --- /dev/null +++ b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "ae2fc:ingredient_buffer" + } + } +} diff --git a/src/main/resources/assets/ae2fc/lang/en_us.lang b/src/main/resources/assets/ae2fc/lang/en_us.lang new file mode 100644 index 00000000..0234a8b9 --- /dev/null +++ b/src/main/resources/assets/ae2fc/lang/en_us.lang @@ -0,0 +1,33 @@ +itemGroup.ae2fc=AE2 Fluid Crafting + +tile.ae2fc:fluid_discretizer.name=ME Fluid Discretizer +tile.ae2fc:fluid_pattern_encoder.name=Fluid Pattern Encoder +tile.ae2fc:fluid_packet_decoder.name=ME Fluid Packet Decoder +tile.ae2fc:ingredient_buffer.name=Ingredient Buffer +tile.ae2fc:burette.name=Precision Burette +tile.ae2fc:dual_interface.name=ME Dual Interface +tile.ae2fc:part_dual_interface.name=ME Dual Interface + +item.ae2fc:fluid_drop.name=Drop of %s +item.ae2fc:fluid_packet.name=Fluid Packet +item.ae2fc:dense_encoded_pattern.name=Encoded Pattern +item.ae2fc:part_dual_interface.name=ME Dual Interface +item.ae2fc:part_fluid_pattern_terminal.name=ME Fluid Pattern Terminal + +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.burette=Precision Burette + +ae2fc.tooltip.fluid_packet=Use a Fluid Packet Decoder to\nconvert this back into fluid. +ae2fc.tooltip.invalid_fluid=Invalid fluid! +ae2fc.tooltip.processing_recipe_only=Not a processing recipe! +ae2fc.tooltip.crafting_recipe_only=Not a crafting recipe! +ae2fc.tooltip.encode_pattern=Encode Pattern +ae2fc.tooltip.empty=Empty +ae2fc.tooltip.dump_tank=Dump Tank Contents +ae2fc.tooltip.transpose_in=Transfer Into Tank +ae2fc.tooltip.transpose_out=Transfer Into Item + +ae2fc.pauto.fluid_processing.name=Fluid Processing +ae2fc.pauto.fluid_processing.name_short=Fluid 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 00000000..4c773d93 --- /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 00000000..c118eccf --- /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 00000000..72db6779 --- /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_discretizer.json b/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json new file mode 100644 index 00000000..fea0d3f8 --- /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_packet_decoder.json b/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json new file mode 100644 index 00000000..622135b4 --- /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 00000000..3452f91b --- /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 00000000..b5b2fb4d --- /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/item/burette.json b/src/main/resources/assets/ae2fc/models/item/burette.json new file mode 100644 index 00000000..b4f5de04 --- /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_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/dense_encoded_pattern.json new file mode 100644 index 00000000..41c52e0a --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/dense_encoded_pattern.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/dense_encoded_pattern" + } +} 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 00000000..24aed260 --- /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_discretizer.json b/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json new file mode 100644 index 00000000..54ada676 --- /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 00000000..ca55bf1d --- /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_packet.json b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json new file mode 100644 index 00000000..134d55d6 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json @@ -0,0 +1,7 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ae2fc:items/fluid_packet", + "layer1": "ae2fc:items/fluid_packet_overlay" + } +} 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 00000000..b34f43ed --- /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 00000000..2d6ab9fd --- /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 00000000..8e58f24c --- /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/part_dual_interface.json b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json new file mode 100644 index 00000000..e4a14e79 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json @@ -0,0 +1,106 @@ +{ + "parent": "appliedenergistics2:item/part/part", + "textures": { + "sides": "appliedenergistics2:parts/monitor_sides", + "back": "appliedenergistics2:parts/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_terminal.json b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json new file mode 100644 index 00000000..d0516315 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json @@ -0,0 +1,9 @@ +{ + "parent": "appliedenergistics2:item/part/display", + "textures": { + "front": "appliedenergistics2:items/part/pattern_terminal", + "front_bright": "ae2fc:parts/pattern_terminal_bright", + "front_medium": "ae2fc:parts/pattern_terminal_medium", + "front_dark": "ae2fc:parts/pattern_terminal_dark" + } +} 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 00000000..217560bc --- /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 00000000..124b4051 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json @@ -0,0 +1,76 @@ +{ + "ae2_uvl_marker": true, + "textures": { + "lightsBright": "ae2fc:parts/pattern_terminal_bright", + "lightsMedium": "ae2fc:parts/pattern_terminal_medium", + "lightsDark": "ae2fc:parts/pattern_terminal_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 + } + } + } + } + ] +} 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 00000000..685f8f0b --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_base.json @@ -0,0 +1,104 @@ +{ + "textures": { + "sides": "appliedenergistics2:parts/export_bus_sides", + "sidesStatus": "appliedenergistics2:parts/monitor_sides_status", + "back": "appliedenergistics2:parts/monitor_back", + "front": "ae2fc:blocks/interface", + "particle": "appliedenergistics2:parts/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 00000000..f47216bf --- /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:parts/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 00000000..3ab8507f --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_off.json @@ -0,0 +1,33 @@ +{ + "textures": { + "indicator": "appliedenergistics2:parts/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 00000000..cd41b8f7 --- /dev/null +++ b/src/main/resources/assets/ae2fc/models/part/interface_on.json @@ -0,0 +1,54 @@ +{ + "ae2_uvl_marker": true, + "textures": { + "indicator": "appliedenergistics2:parts/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/recipes/burette.json b/src/main/resources/assets/ae2fc/recipes/burette.json new file mode 100644 index 00000000..6c52cd1b --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/burette.json @@ -0,0 +1,31 @@ +{ + "type": "forge:ore_shaped", + "result": { + "item": "ae2fc:burette", + "count": 1 + }, + "pattern": [ + "ihi", + "gbg", + "ici" + ], + "key": { + "i": { + "type": "forge:ore_dict", + "ore": "ingotIron" + }, + "h": { + "item": "minecraft:hopper" + }, + "g": { + "item": "appliedenergistics2:quartz_glass" + }, + "b": { + "item": "minecraft:bucket" + }, + "c": { + "type": "appliedenergistics2:part", + "part": "material.calculation_processor" + } + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/dual_interface.json b/src/main/resources/assets/ae2fc/recipes/dual_interface.json new file mode 100644 index 00000000..fca5492a --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/dual_interface.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "item": "appliedenergistics2:interface" + }, + { + "item": "appliedenergistics2:fluid_interface" + } + ], + "result": { + "item": "ae2fc:dual_interface", + "nbt": {} + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json b/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json new file mode 100644 index 00000000..a68c62de --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json @@ -0,0 +1,11 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "item": "ae2fc:part_dual_interface" + } + ], + "result": { + "item": "ae2fc:dual_interface" + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json b/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json new file mode 100644 index 00000000..1684ef4f --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json @@ -0,0 +1,33 @@ +{ + "type": "forge:ore_shaped", + "result": { + "item": "ae2fc:fluid_discretizer", + "count": 1 + }, + "pattern": [ + "ipi", + "smt", + "ipi" + ], + "key": { + "i": { + "type": "forge:ore_dict", + "ore": "ingotIron" + }, + "p": { + "type": "appliedenergistics2:part", + "part": "material.engineering_processor" + }, + "s": { + "type": "appliedenergistics2:part", + "part": "part.fluid_storage_bus" + }, + "m": { + "item": "appliedenergistics2:condenser" + }, + "t": { + "type": "appliedenergistics2:part", + "part": "part.storage_bus" + } + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json new file mode 100644 index 00000000..1b5155c9 --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json @@ -0,0 +1,32 @@ +{ + "type": "forge:ore_shaped", + "result": { + "item": "ae2fc:fluid_packet_decoder", + "count": 1 + }, + "pattern": [ + "ihi", + "cfc", + "ipi" + ], + "key": { + "i": { + "type": "forge:ore_dict", + "ore": "ingotIron" + }, + "h": { + "item": "minecraft:hopper" + }, + "c": { + "type": "appliedenergistics2:part", + "part": "cable_glass.fluix" + }, + "f": { + "item": "appliedenergistics2:fluid_interface" + }, + "p": { + "type": "appliedenergistics2:part", + "part": "material.calculation_processor" + } + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json new file mode 100644 index 00000000..cec58226 --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json @@ -0,0 +1,30 @@ +{ + "type": "forge:ore_shaped", + "result": { + "item": "ae2fc:fluid_pattern_encoder", + "count": 1 + }, + "pattern": [ + "lpl", + "iwi", + "iii" + ], + "key": { + "l": { + "type": "forge:ore_dict", + "ore": "blockLapis" + }, + "p": { + "type": "appliedenergistics2:part", + "part": "material.engineering_processor" + }, + "i": { + "type": "forge:ore_dict", + "ore": "ingotIron" + }, + "w": { + "type": "forge:ore_dict", + "ore": "workbench" + } + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json b/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json new file mode 100644 index 00000000..f08fc818 --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json @@ -0,0 +1,37 @@ +{ + "type": "forge:ore_shaped", + "result": { + "item": "ae2fc:ingredient_buffer", + "count": 1 + }, + "pattern": [ + "ili", + "agf", + "isi" + ], + "key": { + "i": { + "type": "forge:ore_dict", + "ore": "ingotIron" + }, + "g": { + "item": "appliedenergistics2:quartz_glass" + }, + "l": { + "type": "appliedenergistics2:part", + "part": "material.cell1k_part" + }, + "a": { + "type": "appliedenergistics2:part", + "part": "material.annihilation_core" + }, + "f": { + "type": "appliedenergistics2:part", + "part": "material.formation_core" + }, + "s": { + "type": "appliedenergistics2:part", + "part": "material.fluid_cell1k_part" + } + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json b/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json new file mode 100644 index 00000000..dc5d91f7 --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json @@ -0,0 +1,11 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "item": "ae2fc:dual_interface" + } + ], + "result": { + "item": "ae2fc:part_dual_interface" + } +} diff --git a/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json b/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json new file mode 100644 index 00000000..bd2103a9 --- /dev/null +++ b/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "type": "appliedenergistics2:part", + "part": "part.pattern_terminal" + }, + { + "item": "ae2fc:fluid_pattern_encoder" + } + ], + "result": { + "item": "ae2fc:part_fluid_pattern_terminal" + } +} 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_discretizer.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png new file mode 100644 index 0000000000000000000000000000000000000000..d08e7976c6388cc6641e5aa5336e2fbd3688951c GIT binary patch literal 2004 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 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..1fd54c43dad4babdf9bb7168db117121d64bed4c GIT binary patch literal 1910 zcmb_d-;3Nt98WzxYs=xGXRQhn(?cpY$;{-(ZicR9yLVxptXp8R!C;N*#+5WH?NMaJSNprR3>Z%y$^LPIv;`_;kwk?Jy;^ntfNjms+{_c}5etuYzX1@*^ zYuTFnCZ;^ph{u~u>xHpEOVaTZy_nE-mdQ=l3Zjy7>xUl|Iq*u#8OsH3Tw(2Cc`IS3 zw^kZ-Yn?ita^f}lcn=E!A3D{4IE@1lM#Rg9TaqEr~qPMW5bb0k0P`so{v|nua9(zzmn3fq$v3i+A}k%c5XKX+Q925f z2$5km(<`&CfH01Tln_X5Aa7YdCAG_Yx6RM-O*gz-p5 z?hE!P_IVN#;cgI;7SrRXrO1;Y;tKEZL?{;48N1hOl}bI~e$Wvc>FTMIa&4(%L&vsN zq``r?u8V6?nvsaITD7Ez&}c#6VPwG~@eNZgnwF_r&?8Z6OB{q$1yURAia9N&sc5ZQ2GjC}>j08lQhF2}A*s&O^(|%Q9yUUJg=` z)BdP%PP22PStpPOWWj{yMNm>`o_*#iqvc>q1Rl(Tq1bMl38DAk#SoTqKkJf&m0KcO z_hFrWH`7zHd2ho#pCLx6FH*;VYB7WnMfQcjve1?_=u$jMrh2^|8Dpw zq_j<<78AFrt~|Ue55~f2Ihay#|CcLzeuWOXrT?3DgS}nXtw`cvN!;Q2bkaP;&!qjZ zNJJ?S&E>r-7Y~ZYBDBE+LPJqJf0-h#va%9 zZjzOtRp0S%!|IA0Vl_T?W`{yJ{ny*&MtK$7=_B`}}`2YBH%M!1Lqsm)}B<=%B0EnRcoz?26X@wm&hZV(ByBrP23Mue`jRCZa@4~xo;@2)Dc?-iBfIR`jViM9O7 zR?LoWt<>q(I<-CJ&};HS7YhRcONrbK+F^pbMP*oKuvH@UT2YDSL6+jFujBI)IQ-rtJYv9%L;gK)#Ra9DO8ev`U zbUIoGX*_P~(6((I7`kDo0-+|oFeP0zOb(1KlvzS!KT3Tb%9%yt@{P2ph)9Pi1kuE7 zn2cx=gy~%p=}-e%N&}!rC%9-MZV#M$RA+4#urN&o7EZ8Hi>Ewk@!Oc5Ebnq4sCAr) zkEyW)!Nf(9E^mr7Mubd7C-q*$^i`JdjhM3KO%djSESm_IVn$LP*Ew&G3soD3ESF01 z!Q+1D@lNvM4mc~5lob^N7(j(k1-UvbU}$4#y$lK%fFac3p5N&0hC)<_2%9E03cH{} zFdj+CUBMp34IT$Xr0WNy$@C~}D)NLxT;gpW3&$clv~#{%D%E1%@Y`Y`Sv|5OSC>l` zv@J_DG&l&?ad0(EQW8>DEf*C*jpq9vwus3ZE-I+Vq47n<5Scuuw&AT}^-v4Ioiv}d^`?iy{Wu_cl z@e@(g-l%bovJ<0G+m{Do!GvZ_P*iAEedZ~nX@3d>_m;uXY^TM9(L3m32upY)?U0x) zHU(REQJsFf&=azG=fDjM*#uHUg)RiD?V3;}4WU?SA=t>XoZ)(fapJcGZqhm98UAXY|)yMkavk%ipp1pYf&l|s8e*cB-?d|?g&;L66$9`?$?~~WB zpM2cBHuL*?r=&f-!@ry>_vc$=>DV*ZPE^m#R`7GP;FGi8&-`pX`2)JS$IEWU>henY JtEG4P{{qKgT(STF 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..f86bc6759ad21a85e836877059512cbed4d69253 GIT binary patch literal 1927 zcmb_dON`q@7 z3(10g47 z>U7;*U33vu4!$B!cCir9W(kqI?N$)uZc*vU#bTXqYl_^1B#T9*lq!@L>>0VtBPQpv zmg+)Nm-8qKEr4|MfUET!0bSE|RUp)OB}hnD4dT5+2^AK*ksl^L59CxLarjbFRD`8{7uw-SY!DB^ zBm$;&NvOdrNL}gysXM}jOHr$*oVprou{H~mSYY7@8#Z~u<0ij@=+W^e1tPSzJ<>7O zmUerjB2K23g&Tu_j77)wm5^z(Eapp*%chovnR`=jLR^j*NqAJ}yfsYJ%+O@HT$cCE z`vK*h_{9xyRv`&1Dmu`C3ZV*eby&au0&3L-A^7LSBtVV$vYzg8~KMBMXR;vk+qdRVSal`V)Y zOVzWmXRdAIS`a5Ba9OQVR77a9zE82oSk43xsTAslN|EWPh&Xvw_e{re3XlTo4)0gE zyOa`Oc%O>24zBvK z$mz5 zTK8a`b|=$gvV3>LJ(SBko?)pV?-W$0U;-5}22|I8L^qr~azrZ|IDVVqR$Z_em>A@S zEdA$(4bU7Lx z;$t*^SRkSliQzK;=YCxbrgv(UQhnb^chAh_hP?G60N~VVy8DYOml`kac>5dn*OTiP z-g@0WYaLoU_w&n>7rwgs_&WIX{p68nE}p%0YF)=~6 zF0|IpAMvjLx^`~c+@n`Mk$*X~yz9Zz3x7Pc=e=DAj(zLmcb%uNAFrLtRPl2e@bQ@+ awtW8Kvp@Z2JpXk%Dr-~Il`jv!x&Ako17ctR literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/gui/burette.png b/src/main/resources/assets/ae2fc/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/ae2fc/textures/gui/fluid_packet_decoder.png b/src/main/resources/assets/ae2fc/textures/gui/fluid_packet_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..4e17ee57f57343de084152ca5880f992b338e240 GIT binary patch literal 5240 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 Ss@OFDaR5_PQ>d1E9r|BoPFNKH literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png b/src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png new file mode 100644 index 0000000000000000000000000000000000000000..045611f803177de95e5f09ef81db6f0cf749c8a1 GIT binary patch literal 7072 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/gui/ingredient_buffer.png b/src/main/resources/assets/ae2fc/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/ae2fc/textures/items/dense_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/dense_encoded_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..1a19d070b68e39ad4939f3925def3c08c5b67a41 GIT binary patch literal 801 zcmV++1K#|JP)EX>4Tx04R}tkvm8OK@>$#;wK`Cg(*ZVwooet@rR{EQ3QpML@;XM?#4tRyN2C} zjiq2KSomrD6*kt|TG$GL;0MIY(n`@rWPP&cHW7N zN3@ybtQLBqmH;q7NH^WwRD3K!#r1jT*K*sfvMSg8xf)Z8t_cB6c-YK2dEo`&!L@u& z@vd;#T+&m*XTn{MG${O_bfdtpiZeb3Rc6>p#72a}!dj+`jWRZu980)gxIJark{_HY za7yvIVk^697W-5$Y*@6X<4Tv^#yAs95k6X(L#U&>9kqZ7+-yap1A+BXi zag4Hph3@-NfAD*@Zo03xkkk-?nrF{@?|{%Yd~bQ)*OBLaA4Am>T-qi7${`GWh`v?Q z+7F;<1J18Xny~{Ho6z!@(;eNHs$>V#X?Q!8Z%RV*4b;yS=2pDtt0R%G_223g10003XNklb=24O&M%DZtqZtnJEUh(u4tkwRVWs5q^zm9l=^VObn!y5QXq0M|LL8OcVlA z%KvAO$2F=p_4(%fz~=n$dDW&E&&W6E-ktxTOFc0elXa>6J6FTprint3CUdeVgx^m8 z3q0Y$xJZ{8N+~F%CgiG3C#yY~!Q5mXV=}pka}$c}BA~9(G{)~n_wLAx{G)Vv!hF-sOPCYe9iD^00000NkvXXu0mjfv0-#Z 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_packet.png b/src/main/resources/assets/ae2fc/textures/items/fluid_packet.png new file mode 100644 index 0000000000000000000000000000000000000000..bb301cdbe18243826576c2e1da123d6b83b34a41 GIT binary patch literal 825 zcmV-91IGM`P)EX>4Tx04R}tkv&MmKpe$iTT4YM4p#6B%ut;yh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|}?mh0_0p5CFSdOA5z93mD9Z7j7hE9wgIG;vr~RLb{e z9F{q6aaPJz*1RWwVKAqzq_|FV7%?m%jsyfqsG@{2Ohjl^NimS5{iut7(DoAO zrG<}xo^9abx}`~bz~v6m|D=h!XiGkt-h3W-KcjET0=>6D*P7E?b04PqKZ*#H0l24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jm4G6(1cnDmYO9000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0003HNkl(Si3`L)0n+47Q4ITHt3&3^xFF zv1Fwon=r|yp^7@Gc>jOdl8E4)Qr@h$0r`8g-|wllQfr-NW=7X_xI4ozP)gx^KK~A= zwGt7bs)z^x5rOKL(Z}NA zyVLhQ%jJ@s^E{x|N=k`{Kt$MXw*Xu&7w-2v!!TfG^MHt;wMJ`g+Ck2loHHpUN+~b< z_%%Y#ne}?jIF6i7rEX>4Tx04R}tkv&MmKpe$iTT4YM4p#6B%ut;yh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|}?mh0_0p5CFSdOA5z93mD9Z7j7hE9wgIG;vr~RLb{e z9F{q6aaPJz*1RWwVKAqzq_|FV7%?m%jsyfqsG@{2Ohjl^NimS5{iut7(DoAO zrG<}xo^9abx}`~bz~v6m|D=h!XiGkt-h3W-KcjET0=>6D*P7E?b04PqKZ*#H0l24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jm4G6(AL3FPdKf000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0000%Nkl}#P_x&QzG07*qoM6N<$ Ef?_TDPyhe` literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png new file mode 100644 index 0000000000000000000000000000000000000000..11e59a01db4e2f999c968fe7e6fd8ff09cce6b63 GIT binary patch literal 1488 zcmbVMOKjXk72AV-$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_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/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 00000000..2e1f9cd4 --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,18 @@ +[ + { + "modid": "ae2fc", + "name": "AE2 Fluid Crafting", + "description": "Lets you do Crafting... with Fluids!", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "", + "updateUrl": "", + "authorList": ["GlodBlock"], + "credits":"phantamanta44", + "logoFile": "", + "screenshots": [], + "useDependencyInformation": true, + "dependencies": ["appliedenergistics2"], + "requiredMods": ["appliedenergistics2"] + } +] \ 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 00000000..5744378f --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Cool Mod", + "pack_format": 3 + } +}