diff options
author | NotAPenguin <michiel.vandeginste@gmail.com> | 2024-08-03 23:21:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-03 23:21:44 +0200 |
commit | b65ace37de4f585b8089ad413ee877b792da11ca (patch) | |
tree | 2e0445785c5008df15151f729da148fb70dbc21d /src/main/java/gregtech/common | |
parent | e180e49fc6305f71b1f1b18141b946f794a7012b (diff) | |
download | GT5-Unofficial-b65ace37de4f585b8089ad413ee877b792da11ca.tar.gz GT5-Unofficial-b65ace37de4f585b8089ad413ee877b792da11ca.tar.bz2 GT5-Unofficial-b65ace37de4f585b8089ad413ee877b792da11ca.zip |
Waterline rework (#2577)
* Add barebones PurificationPlant class
* Make simple 3x3 structure to form purification plant
* Add base purification unit class and dummy sifter unit MTE
* Make sifter unit form
* Fix accidental wildcard import
* Implement basic linking of units to controller using data stick
* Make linking more robust, save bidirectional links, add scanner output
* add linking range, error message and unregister old controller when re-linking
* Add link status of purification units to waila body
* Disable maintenance issues on purification plant units
* spotless
* Check structure of linked purification units in main controller
* Remove all star imports
* Small refactor to avoid updating status from main controller
* spotless
* Attempt to document current code
* Convert some comments to javadoc
* Implement basic processing cycle, sync it with linked purification units
* Make water purification plant drain power
* Calculate power drain from active units and remove controller power drain
* spotless
* Add very barebones recipemap
* Fix recipemap name in lang file
* spotless
* Fix purification unit recipemap name
* spotless
* more sane amount of max fluid outputs
* add some item outputs to sifter unit
* Very simple recipe processing, may be buggy
* spotless
* Implement recipe failure
* Implement void protection for purification units
* spotless
* buff item output odds slightly
* Add WIP grade 1 structure
* spotless
* Store base success chance in recipe metadata and display it in NEI
* Fill sifter plant with water
* Add comment
* Allow construction sifter unit in survival
* Implement water boost
* Fix water boost allowing output chance to go over 100%
* Implement failed recipes outputting lower tier water
* Fix typo
* Fix deformed purification unit still drawing power
* Slightly refactor recipe check so base class can read result
* Create empty ModularUI container for purification plant
* The great gui struggle part 1
* More gui struggles, we have a button now
* Adjust button text and size
* gui wars: the rise of the sync
* gui wars: a new hope
* fix the sync
* is pengu old enough to know exceeder?
* Fix being able to link the same unit multiple times
* Sync status string to client
* Sign sifter with my name
* Show status somewhat properly
* Adjust sifter base chance and structure
* Fully implement sifter unit
* More tooltip refactoring
* Add structure info to sifter tooltip.
* nitpicking tooltips
* Adding sound to Purification Plant Main Unit.
* fix star imports
* Add basic coagulator unit, add recipemap for it
* Write coagulator tooltip
* comma nitpicking
* more tooltip work
* small refactor to purification plant controller
* start work on custom recipemap frontend
* Fully implement coagulator
* Update structure requirements in tooltips
* Move controller text in structure tooltips to be consistent
* fix NPE on world load
* Add base ph adjustment unit MTE
* Add info to main controller and energy hatch check
* Fixing tooltip of Main Controller & Energy/Exotic Hatch check.
* Create full pH adjustment structure
* disallow any voiding on purification unit
* Small custom RecipeMap frontend for ph adjustment
* Generate random initial pH value
* Implement inserting NaOH and HCl to adjust pH
* Add easter egg
* Implement pH sensor hatch
* Properly consume HCl and round pH value to 2 digits
* Write ph adjustment unit tooltip
* Tooltip nitpicking
* Try to fix some structurelib hints
* More trying to fix hints
* Add industrial strength concrete casing block
* Add water loop sound to the game
* Document random initial pH in tooltip
* Add glass material base
* Fix spotless formatting in Textures docs because I cannot take it anymore
* Add glass texture
* Try adding transparent glass
* Transparent glass working
* Create pH resistant glass and update pH structure to use it
* Create full structure for main purification plant
* Create custom water purification casing block
* Properly balance ferrous wastewater reprocessing and reduce input by a factor 10
* Add pH factor to NEI tooltip and fix coagulator structure
* Structure tooltip for Purification Plant base
* Add GT_Block_Glass2 and properly set maxMeta
* Add Tinted Industrial Glass blocks
* Fix BlockCasing9 not showing custom tooltip
* Register tinted glass as EV glass
* Add sterile water plant casing and revert tooltip change
* Mention required water in sifter tooltip
* Add more textures and casings
* Add more textures, sounds and add structure info for pH adjustment
* Rename sifter unit to clarifier
* Rename coagulation unit to flocculation unit
* Add activated carbon line
* Fix unintended activated carbon recipe
* Add activated carbon filter
* Add polyaluminium chloride + solution
* Add new custom textures by @BlueHero233
* Wip recipe page with new background for flocculation
* Fix flocculation background image mostly
* Finally aligned the slots
* angery mumbles
* Finish flocculation recipe page
* All the recipe pages!
* Add new reworked textures
* Fix ph adjustment being t3 instead of t4
* Fix invisible casing
* apply chembalance to polyaluminium chloride properly
* Fix ferrous wastewater -> flocculation waste liquid
* Move flocculation to grade 3
* create ozonation unit with placeholder blocks
* add new blocks for ozonation with placeholder textures
* Add water to ozonation structure
* Create ozone gas material
* Add ozone recipe
* Add textures for ozone unit
* Add sound loop for ozonation
* fix
* implement ozonation mechanics
* Finalize ozonation tooltip
* Create dummy plasma heater multi
* Update textures for plasma heater
* Add grade 5 recipemap
* Add hatches to plasma heater multi
* Add basic plasma heating unit variables
* Implement plasma heating unit mechanics
* Add plasma heater tooltip
* Add structure info to plasma heater tooltip
* fix ozonation tooltip, add frontend
* Fix positioning on ozonation tooltip and fix plasma heater crash
* Add UV treatment MTE and structure without textures
* Revert accidental addition of debug dependencies
* Add initial version of uv unit textures
* update naquadria casing, add water color gradient
* Some minor cleanup and added docs
* Create lens housing bus
* Add lens bus to UV treatment unit
* Add lens indicator hatch
* Merge GT_MetaGeneratedItem_03.java
* Add lens indicator hatch
* Add the lens cycle, uv treatment recipe map and fix eut of flocculation recipe
* Implement lens swapping mechanic
* Clean up first lens swap
* Fix uv recipemap lang and move lens cycle to recipe
* Write uv treatment tooltip
* Add sounds for uv and plasma steps
* Create empty degasifier class
* Create temporary debug structure for degasifier
* set temp casing index for degasifier
* create degasifier control hatch
* create slightly less temporary but still temporary structure for degasifier
* Start impl of degasifier
* fix fluid consumption and nbt loading of degasifier
* Degasifier implementation should work now
* Rename and reformat some things and start work on degasser tooltiop
* give last bit much lower chance of being on to avoid cheesing
* Finish degasifier tooltip
* Integrate some deleno lore
* hopefully fix all moved casing meta ids after merge
* Create finalized degasser structure
* Integrate more deleno lore
* Add even more lore
* Create placeholder particle catalysts and fetch particle items from gt++
* Fix wrong casing and recipemap localization
* Create parallel config menu
* refactor purification recipecheck slightly
* implement parallel amount on water i/o and power
* add tooltip info about parallel config
* fix text
* update block names in structure tooltips
* create structure tooltip for degasser
* create textureless quark catalyst items
* add the purple glass
* fix lore typos
* fix some casing indices
* remove concrete floor from water plant and reword tooltip
* fix main plant structure and add placeholder structure for t8 step
* fix structurecheck for main plant and add random catalyst generation for t8
* implement basic mechanics for particle extractor (wip)
* Create plasma heater frontend
* implement final mechanics and bugfixes for particle extractor
* add recipes for re-aligning quark catalysts
* add simple recipes for catalyst alignment
* initial replacement of purified water in engraver recipes
* add purified water to all wafer cutting recipes
* fix purified water amounts
* buff quark cyclotron recipe again
* extract t8 unit casings into their own icons
* Write initial tooltip for t8 module
* add purified water to mask recipes
* Add recipe comparator to show low tier purified water recipes first
* add min casing check to waterline multis
* buff ozone production
* update t8 structure
* make purified water optional again for naq wafers
* Fix blockrenderer for purification plant
* fix nei previews
* fix nei
* really fix nei this time
* add t8 lore
* fix hatch recipe locking blocking automation on some steps
* try to solve weirdness with grade 3 recipe
* fix issues with recipecheck
* fix missing null check
* make ph sensor use a strict inequality check
* fix min casings on t5
* significantly nerf purified water usage for beamline masks
* disable void protection for waterline
* small adjustments to t6 unit
* more small adjustments to t6 unit to prevent easy automation cheese
* fix degasser redstone output and missing return statement
* remove water QFT catalyst recipes
---------
Co-authored-by: Tianyou Mei <meitianyou94@gmail.com>
Co-authored-by: OlliedeLeeuw <ollie.riemersma@xs4all.nl>
Co-authored-by: Ollie_de_Leeuw <154506304+OlliedeLeeuw@users.noreply.github.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>
Diffstat (limited to 'src/main/java/gregtech/common')
29 files changed, 6829 insertions, 1 deletions
diff --git a/src/main/java/gregtech/common/blocks/GT_Block_Casings10.java b/src/main/java/gregtech/common/blocks/GT_Block_Casings10.java index 660c709886..83219f45ee 100644 --- a/src/main/java/gregtech/common/blocks/GT_Block_Casings10.java +++ b/src/main/java/gregtech/common/blocks/GT_Block_Casings10.java @@ -20,7 +20,10 @@ public class GT_Block_Casings10 extends GT_Block_Casings_Abstract { GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".0.name", "MagTech Casing"); GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".1.name", "Laser Containment Casing"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".2.name", "Quark Exclusion Casing"); + ItemList.Casing_Electromagnetic_Separator.set(new ItemStack(this, 1, 0)); + ItemList.BlockQuarkContainmentCasing.set(new ItemStack(this, 1, 2)); ItemList.Casing_Laser.set(new ItemStack(this, 1, 1)); } @@ -35,6 +38,7 @@ public class GT_Block_Casings10 extends GT_Block_Casings_Abstract { return switch (aMeta) { case 0 -> Textures.BlockIcons.MACHINE_CASING_EMS.getIcon(); case 1 -> Textures.BlockIcons.MACHINE_CASING_LASER.getIcon(); + case 2 -> Textures.BlockIcons.BLOCK_QUARK_CONTAINMENT_CASING.getIcon(); default -> Textures.BlockIcons.MACHINE_CASING_ROBUST_TUNGSTENSTEEL.getIcon(); }; } diff --git a/src/main/java/gregtech/common/blocks/GT_Block_Casings9.java b/src/main/java/gregtech/common/blocks/GT_Block_Casings9.java index 4441885f30..6f33cda013 100644 --- a/src/main/java/gregtech/common/blocks/GT_Block_Casings9.java +++ b/src/main/java/gregtech/common/blocks/GT_Block_Casings9.java @@ -22,15 +22,51 @@ public class GT_Block_Casings9 extends GT_Block_Casings_Abstract { GT_LanguageManager .addStringLocalization(getUnlocalizedName() + ".1.tooltip", "Less than five 0.1μm particles per m^3"); GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".2.name", "Primitive Wooden Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".3.name", "Superplasticizer-Treated High Strength Concrete"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".4.name", "Sterile Water Plant Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".5.name", "Reinforced Sterile Water Plant Casing"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".6.name", "Slick Sterile Flocculation Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".7.name", "Stabilized Naquadah Water Plant Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".8.name", "Inert Neutralization Water Plant Casing"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".9.name", "Reactive Gas Containment Casing"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".10.name", "Inert Filtration Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".11.name", "Heat-Resistant Trinium Plated Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".12.name", "Naquadria-Reinforced Water Plant Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".13.name", "High Energy Ultraviolet Emitter Casing"); + // placeholder name + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".14.name", "Particle Beam Guidance Pipe Casing"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".15.name", "Femtometer-Calibrated Particle Beam Casing"); ItemList.Casing_Pipe_Polybenzimidazole.set(new ItemStack(this, 1, 0)); ItemList.Casing_Vent_T2.set(new ItemStack(this, 1, 1)); ItemList.WoodenCasing.set(new ItemStack(this, 1, 2)); + ItemList.BlockIndustrialStrengthConcrete.set(new ItemStack(this, 1, 3)); + ItemList.BlockIndustrialWaterPlantCasing.set(new ItemStack(this, 1, 4)); + ItemList.BlockSterileWaterPlantCasing.set(new ItemStack(this, 1, 5)); + ItemList.BlockFlocculationCasing.set(new ItemStack(this, 1, 6)); + ItemList.BlockNaquadahReinforcedWaterPlantCasing.set(new ItemStack(this, 1, 7)); + ItemList.BlockExtremeCorrosionResistantCasing.set(new ItemStack(this, 1, 8)); + ItemList.BlockHighPressureResistantCasing.set(new ItemStack(this, 1, 9)); + ItemList.BlockOzoneCasing.set(new ItemStack(this, 1, 10)); + ItemList.BlockPlasmaHeatingCasing.set(new ItemStack(this, 1, 11)); + ItemList.BlockNaquadriaReinforcedWaterPlantCasing.set(new ItemStack(this, 1, 12)); + ItemList.BlockUltraVioletLaserEmitter.set(new ItemStack(this, 1, 13)); + ItemList.BlockQuarkPipe.set(new ItemStack(this, 1, 14)); + ItemList.BlockQuarkReleaseChamber.set(new ItemStack(this, 1, 15)); } @Override public int getTextureIndex(int aMeta) { - return (1 << 7) | (aMeta + 64); + return (16 << 7) | (aMeta + 16); } @Override @@ -41,6 +77,19 @@ public class GT_Block_Casings9 extends GT_Block_Casings_Abstract { case 1 -> Textures.BlockIcons.MACHINE_CASING_VENT_T2.getIcon(); case 2 -> ordinalSide >= 2 ? Textures.BlockIcons.TEXTURE_METAL_PANEL_E.getIcon() : Textures.BlockIcons.TEXTURE_METAL_PANEL_E_A.getIcon(); + case 3 -> Textures.BlockIcons.INDUSTRIAL_STRENGTH_CONCRETE.getIcon(); + case 4 -> Textures.BlockIcons.MACHINE_CASING_INDUSTRIAL_WATER_PLANT.getIcon(); + case 5 -> Textures.BlockIcons.WATER_PLANT_CONCRETE_CASING.getIcon(); + case 6 -> Textures.BlockIcons.MACHINE_CASING_FLOCCULATION.getIcon(); + case 7 -> Textures.BlockIcons.MACHINE_CASING_NAQUADAH_REINFORCED_WATER_PLANT.getIcon(); + case 8 -> Textures.BlockIcons.MACHINE_CASING_EXTREME_CORROSION_RESISTANT.getIcon(); + case 9 -> Textures.BlockIcons.MACHINE_CASING_HIGH_PRESSURE_RESISTANT.getIcon(); + case 10 -> Textures.BlockIcons.MACHINE_CASING_OZONE.getIcon(); + case 11 -> Textures.BlockIcons.MACHINE_CASING_PLASMA_HEATER.getIcon(); + case 12 -> Textures.BlockIcons.NAQUADRIA_REINFORCED_WATER_PLANT_CASING.getIcon(); + case 13 -> Textures.BlockIcons.UV_BACKLIGHT_STERILIZER_CASING.getIcon(); + case 14 -> Textures.BlockIcons.BLOCK_QUARK_PIPE.getIcon(); + case 15 -> Textures.BlockIcons.BLOCK_QUARK_RELEASE_CHAMBER.getIcon(); default -> Textures.BlockIcons.MACHINE_CASING_ROBUST_TUNGSTENSTEEL.getIcon(); }; } diff --git a/src/main/java/gregtech/common/blocks/GT_Block_Glass1.java b/src/main/java/gregtech/common/blocks/GT_Block_Glass1.java new file mode 100644 index 0000000000..0665411259 --- /dev/null +++ b/src/main/java/gregtech/common/blocks/GT_Block_Glass1.java @@ -0,0 +1,106 @@ +package gregtech.common.blocks; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Facing; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.api.enums.ItemList; +import gregtech.api.enums.Textures; +import gregtech.api.util.GT_LanguageManager; + +/** + * The glass is split into separate files because they are registered as regular blocks, and a regular block can have + * 16 subtypes at most. + * + * This class hosts various special types of tiered glass with not many tiers. + */ +public class GT_Block_Glass1 extends GT_Block_Casings_Abstract { + + public GT_Block_Glass1() { + super(GT_Item_Glass1.class, "gt.blockglass1", Material.glass, 4); + this.opaque = false; + + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".0.name", "Chemical Grade Glass"); + GT_LanguageManager.addStringLocalization( + getUnlocalizedName() + ".0.tooltip", + "Able to resist the most extreme chemical conditions."); + + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".1.name", "Electron-Permeable Neutronium Coated Glass"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".2.name", "Omni-Purpose Infinity Fused Glass"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".3.name", "Non-Photonic Matter Exclusion Glass"); + + ItemList.GlassPHResistant.set(new ItemStack(this, 1, 0)); + ItemList.GlassUVResistant.set(new ItemStack(this, 1, 1)); + ItemList.GlassOmniPurposeInfinityFused.set(new ItemStack(this, 1, 2)); + ItemList.GlassQuarkContainment.set(new ItemStack(this, 1, 3)); + } + + @Override + public int getTextureIndex(int aMeta) { + // Page 16, 0-16 + return (16 << 7) | (aMeta); + } + + @Override + public boolean isNormalCube(IBlockAccess aWorld, int aX, int aY, int aZ) { + return false; + } + + @Override + public boolean isOpaqueCube() { + return false; + } + + @Override + @SideOnly(Side.CLIENT) + public int getRenderBlockPass() { + return 1; + } + + @Override + public boolean renderAsNormalBlock() { + return false; + } + + @Override + @SideOnly(Side.CLIENT) + public IIcon getIcon(int ordinalSide, int aMeta) { + return switch (aMeta) { + case 0 -> Textures.BlockIcons.GLASS_PH_RESISTANT.getIcon(); + case 1 -> Textures.BlockIcons.NEUTRONIUM_COATED_UV_RESISTANT_GLASS.getIcon(); + case 2 -> Textures.BlockIcons.OMNI_PURPOSE_INFINITY_FUSED_GLASS.getIcon(); + case 3 -> Textures.BlockIcons.GLASS_QUARK_CONTAINMENT.getIcon(); + default -> Textures.BlockIcons.MACHINE_CASING_ROBUST_TUNGSTENSTEEL.getIcon(); + }; + } + + /** + * Returns true if the given side of this block type should be rendered, if the adjacent block is at the given + * coordinates. Args: blockAccess, x, y, z, side + */ + @SideOnly(Side.CLIENT) + @Override + public boolean shouldSideBeRendered(IBlockAccess worldIn, int x, int y, int z, int side) { + Block block = worldIn.getBlock(x, y, z); + + if (worldIn.getBlockMetadata(x, y, z) != worldIn.getBlockMetadata( + x - Facing.offsetsXForSide[side], + y - Facing.offsetsYForSide[side], + z - Facing.offsetsZForSide[side])) { + return true; + } + + if (block == this) { + return false; + } + + return super.shouldSideBeRendered(worldIn, x, y, z, side); + } +} diff --git a/src/main/java/gregtech/common/blocks/GT_Block_TintedIndustrialGlass.java b/src/main/java/gregtech/common/blocks/GT_Block_TintedIndustrialGlass.java new file mode 100644 index 0000000000..93c6ebb298 --- /dev/null +++ b/src/main/java/gregtech/common/blocks/GT_Block_TintedIndustrialGlass.java @@ -0,0 +1,98 @@ +package gregtech.common.blocks; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Facing; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraftforge.oredict.OreDictionary; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.api.enums.ItemList; +import gregtech.api.enums.Textures; +import gregtech.api.util.GT_LanguageManager; + +public class GT_Block_TintedIndustrialGlass extends GT_Block_Casings_Abstract { + + public GT_Block_TintedIndustrialGlass() { + super(GT_Item_Glass1.class, "gt.blocktintedglass", Material.glass, 4); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".0.name", "Tinted Industrial Glass (White)"); + GT_LanguageManager + .addStringLocalization(getUnlocalizedName() + ".1.name", "Tinted Industrial Glass (Light Gray)"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".2.name", "Tinted Industrial Glass (Gray)"); + GT_LanguageManager.addStringLocalization(getUnlocalizedName() + ".3.name", "Tinted Industrial Glass (Black)"); + ItemList.GlassTintedIndustrialWhite.set(new ItemStack(this, 1, 0)); + ItemList.GlassTintedIndustrialLightGray.set(new ItemStack(this, 1, 1)); + ItemList.GlassTintedIndustrialGray.set(new ItemStack(this, 1, 2)); + ItemList.GlassTintedIndustrialBlack.set(new ItemStack(this, 1, 3)); + + // Register tinted industrial glass as EV-Tier glass + OreDictionary.registerOre("blockGlassEV", this); + + this.opaque = false; + } + + @Override + public int getTextureIndex(int aMeta) { + // Page 16, 32-47 + return (16 << 7) | (aMeta + 32); + } + + @Override + public boolean isNormalCube(IBlockAccess aWorld, int aX, int aY, int aZ) { + return false; + } + + @Override + public boolean isOpaqueCube() { + return false; + } + + @Override + @SideOnly(Side.CLIENT) + public int getRenderBlockPass() { + return 1; + } + + @Override + public boolean renderAsNormalBlock() { + return false; + } + + @Override + @SideOnly(Side.CLIENT) + public IIcon getIcon(int ordinalSide, int aMeta) { + return switch (aMeta) { + case 0 -> Textures.BlockIcons.GLASS_TINTED_INDUSTRIAL_WHITE.getIcon(); + case 1 -> Textures.BlockIcons.GLASS_TINTED_INDUSTRIAL_LIGHT_GRAY.getIcon(); + case 2 -> Textures.BlockIcons.GLASS_TINTED_INDUSTRIAL_GRAY.getIcon(); + case 3 -> Textures.BlockIcons.GLASS_TINTED_INDUSTRIAL_BLACK.getIcon(); + default -> Textures.BlockIcons.MACHINE_CASING_ROBUST_TUNGSTENSTEEL.getIcon(); + }; + } + + /** + * Returns true if the given side of this block type should be rendered, if the adjacent block is at the given + * coordinates. Args: blockAccess, x, y, z, side + */ + @SideOnly(Side.CLIENT) + @Override + public boolean shouldSideBeRendered(IBlockAccess worldIn, int x, int y, int z, int side) { + Block block = worldIn.getBlock(x, y, z); + + if (worldIn.getBlockMetadata(x, y, z) != worldIn.getBlockMetadata( + x - Facing.offsetsXForSide[side], + y - Facing.offsetsYForSide[side], + z - Facing.offsetsZForSide[side])) { + return true; + } + + if (block == this) { + return false; + } + + return super.shouldSideBeRendered(worldIn, x, y, z, side); + } +} diff --git a/src/main/java/gregtech/common/blocks/GT_Item_Casings9.java b/src/main/java/gregtech/common/blocks/GT_Item_Casings9.java index 678a2424be..e9b39ee2cd 100644 --- a/src/main/java/gregtech/common/blocks/GT_Item_Casings9.java +++ b/src/main/java/gregtech/common/blocks/GT_Item_Casings9.java @@ -11,4 +11,18 @@ public class GT_Item_Casings9 extends GT_Item_Casings_Abstract { public GT_Item_Casings9(Block block) { super(block); } + + /* + * @Override + * public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List<String> aList, boolean aF3_H) { + * // Add tooltip info if it was given + * String localizedTooltip = GT_LanguageManager.getTranslation(aStack.getUnlocalizedName() + ".tooltip"); + * // getTranslation returns the key if no translation was found, but this just means + * // no tooltip was set. + * if (localizedTooltip.startsWith("gt.")) { + * aList.add(localizedTooltip); + * } + * super.addInformation(aStack, aPlayer, aList, aF3_H); + * } + */ } diff --git a/src/main/java/gregtech/common/blocks/GT_Item_Glass1.java b/src/main/java/gregtech/common/blocks/GT_Item_Glass1.java new file mode 100644 index 0000000000..7fbb33ff6e --- /dev/null +++ b/src/main/java/gregtech/common/blocks/GT_Item_Glass1.java @@ -0,0 +1,15 @@ +package gregtech.common.blocks; + +import net.minecraft.block.Block; + +/** + * The glass types are split into separate files because they are registered as regular blocks, and a regular block can + * have + * 16 subtypes at most. + */ +public class GT_Item_Glass1 extends GT_Item_Casings_Abstract { + + public GT_Item_Glass1(Block block) { + super(block); + } +} diff --git a/src/main/java/gregtech/common/blocks/GT_Item_TintedIndustrialGlass.java b/src/main/java/gregtech/common/blocks/GT_Item_TintedIndustrialGlass.java new file mode 100644 index 0000000000..9f633922ac --- /dev/null +++ b/src/main/java/gregtech/common/blocks/GT_Item_TintedIndustrialGlass.java @@ -0,0 +1,10 @@ +package gregtech.common.blocks; + +import net.minecraft.block.Block; + +public class GT_Item_TintedIndustrialGlass extends GT_Item_Casings_Abstract { + + public GT_Item_TintedIndustrialGlass(Block block) { + super(block); + } +} diff --git a/src/main/java/gregtech/common/gui/modularui/widget/TextButtonWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/TextButtonWidget.java new file mode 100644 index 0000000000..492eef0a0a --- /dev/null +++ b/src/main/java/gregtech/common/gui/modularui/widget/TextButtonWidget.java @@ -0,0 +1,57 @@ +package gregtech.common.gui.modularui.widget; + +import com.gtnewhorizons.modularui.api.math.Size; +import com.gtnewhorizons.modularui.api.widget.Widget; +import com.gtnewhorizons.modularui.common.widget.ButtonWidget; +import com.gtnewhorizons.modularui.common.widget.MultiChildWidget; +import com.gtnewhorizons.modularui.common.widget.TextWidget; + +public class TextButtonWidget extends MultiChildWidget { + + private ButtonWidget mButton; + private TextWidget mText; + + private int leftMargin; + + public TextButtonWidget(String text) { + mButton = new ButtonWidget(); + mText = new TextWidget(text); + mText.setPos(0, 0); + mButton.setPos(0, 0); + + this.addChild(mButton); + this.addChild(mText); + } + + public ButtonWidget button() { + return mButton; + } + + public TextWidget text() { + return mText; + } + + @Override + public Widget setPos(int x, int y) { + return super.setPos(x, y); + } + + @Override + public Widget setSize(int width, int height) { + this.mButton.setSize(width, height); + this.mText.setSize(width, height); + return super.setSize(width, height); + } + + @Override + public Widget setSize(Size size) { + this.mButton.setSize(size); + return super.setSize(size); + } + + public TextButtonWidget setLeftMargin(int margin) { + this.leftMargin = margin; + this.mText.setPos(this.leftMargin, this.mText.getPos().y); + return this; + } +} diff --git a/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java b/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java index 49419aa47f..52a268baf9 100644 --- a/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java +++ b/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java @@ -22,6 +22,7 @@ import static gregtech.client.GT_TooltipHandler.Tier.UV; import static gregtech.client.GT_TooltipHandler.Tier.UXV; import static gregtech.client.GT_TooltipHandler.Tier.ZPM; import static gregtech.client.GT_TooltipHandler.registerTieredTooltip; +import static gregtech.common.items.ID_MetaItem_03.Activated_Carbon_Filter_Mesh; import static gregtech.common.items.ID_MetaItem_03.Circuit_Biomainframe; import static gregtech.common.items.ID_MetaItem_03.Circuit_Bioprocessor; import static gregtech.common.items.ID_MetaItem_03.Circuit_Biowarecomputer; @@ -203,6 +204,14 @@ import static gregtech.common.items.ID_MetaItem_03.NuclearStar; import static gregtech.common.items.ID_MetaItem_03.Optical_Cpu_Containment_Housing; import static gregtech.common.items.ID_MetaItem_03.Optically_Compatible_Memory; import static gregtech.common.items.ID_MetaItem_03.Optically_Perfected_CPU; +import static gregtech.common.items.ID_MetaItem_03.Quark_Catalyst_Housing; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Bottom; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Charm; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Down; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Strange; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Top; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Unaligned; +import static gregtech.common.items.ID_MetaItem_03.Quark_Creation_Catalyst_Up; import static gregtech.common.items.ID_MetaItem_03.Spinneret; import static gregtech.common.items.ID_MetaItem_03.Timepiece; import static gregtech.common.items.ID_MetaItem_03.Transdimensional_Alignment_Matrix; @@ -1103,6 +1112,62 @@ public class GT_MetaGenerated_Item_03 extends GT_MetaGenerated_Item_X32 { "Removing this cover will destroy the linked card", GT_Values.AuthorQuerns))); + ItemList.ActivatedCarbonFilterMesh.set( + addItem( + Activated_Carbon_Filter_Mesh.ID, + "Activated Carbon Filter Mesh", + "The most granular filter you could possibly make.", + SubTag.NO_UNIFICATION)); + + ItemList.Quark_Catalyst_Housing.set( + addItem( + Quark_Catalyst_Housing.ID, + "Empty Quark Release Catalyst Housing", + "Capable of holding Quark Release Catalysts", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Up.set( + addItem( + Quark_Creation_Catalyst_Up.ID, + "Up-Quark Releasing Catalyst", + "Can release up-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Down.set( + addItem( + Quark_Creation_Catalyst_Down.ID, + "Down-Quark Releasing Catalyst", + "Can release down-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Strange.set( + addItem( + Quark_Creation_Catalyst_Strange.ID, + "Strange-Quark Releasing Catalyst", + "Can release strange-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Charm.set( + addItem( + Quark_Creation_Catalyst_Charm.ID, + "Charm-Quark Releasing Catalyst", + "Can release charm-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Bottom.set( + addItem( + Quark_Creation_Catalyst_Bottom.ID, + "Top-Quark Releasing Catalyst", + "Can release top-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Top.set( + addItem( + Quark_Creation_Catalyst_Top.ID, + "Bottom-Quark Releasing Catalyst", + "Can release bottom-quarks into environment to reshape matter", + SubTag.NO_UNIFICATION)); + ItemList.Quark_Creation_Catalyst_Unaligned.set( + addItem( + Quark_Creation_Catalyst_Unaligned.ID, + "Unaligned Quark Releasing Catalyst", + "Needs to be realigned before use", + SubTag.NO_UNIFICATION)); + ItemList.Optical_Cpu_Containment_Housing .set(addItem(Optical_Cpu_Containment_Housing.ID, "Optical CPU Containment Housing", "CPU Housing", o)); ItemList.Optically_Perfected_CPU diff --git a/src/main/java/gregtech/common/items/ID_MetaItem_03.java b/src/main/java/gregtech/common/items/ID_MetaItem_03.java index fac8fd3841..fea5a687df 100644 --- a/src/main/java/gregtech/common/items/ID_MetaItem_03.java +++ b/src/main/java/gregtech/common/items/ID_MetaItem_03.java @@ -183,6 +183,15 @@ public enum ID_MetaItem_03 { NuclearStar(230), IndustrialApiary_Upgrade_UNLIGHT(231), Cover_Metrics_Transmitter(232), + Activated_Carbon_Filter_Mesh(233), + Quark_Catalyst_Housing(234), + Quark_Creation_Catalyst_Up(235), + Quark_Creation_Catalyst_Down(236), + Quark_Creation_Catalyst_Strange(237), + Quark_Creation_Catalyst_Charm(238), + Quark_Creation_Catalyst_Bottom(239), + Quark_Creation_Catalyst_Top(240), + Quark_Creation_Catalyst_Unaligned(241), Circuit_Silicon_Ingot6(721), Circuit_Silicon_Wafer6(722), Circuit_Silicon_Wafer7(723), diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_Hatch_DegasifierControlHatch.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_Hatch_DegasifierControlHatch.java new file mode 100644 index 0000000000..3d49f05dca --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_Hatch_DegasifierControlHatch.java @@ -0,0 +1,122 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.ForgeDirection; + +import gregtech.api.enums.Textures; +import gregtech.api.interfaces.IIconContainer; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.render.TextureFactory; + +public class GT_MetaTileEntity_Hatch_DegasifierControlHatch extends GT_MetaTileEntity_Hatch { + + private byte outputStrength = 0; + + private static final IIconContainer textureFont = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR; + private static final IIconContainer textureFont_Glow = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR_GLOW; + + public GT_MetaTileEntity_Hatch_DegasifierControlHatch(int aID, String aName, String aNameRegional, int aTier) { + super(aID, aName, aNameRegional, aTier, 0, "Outputs a control signal for the Degasser Purification Unit"); + } + + public GT_MetaTileEntity_Hatch_DegasifierControlHatch(String aName, int aTier, String[] aDescription, + ITexture[][][] aTextures) { + super(aName, aTier, 0, aDescription, aTextures); + } + + @Override + public boolean isValidSlot(int aIndex) { + return false; + } + + @Override + public boolean isSimpleMachine() { + return true; + } + + @Override + public boolean allowGeneralRedstoneOutput() { + return true; + } + + @Override + public boolean allowPullStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection Side, + ItemStack aStack) { + return false; + } + + @Override + public boolean allowPutStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection side, + ItemStack aStack) { + return false; + } + + @Override + public boolean isFacingValid(ForgeDirection facing) { + return true; + } + + @Override + public void initDefaultModes(NBTTagCompound aNBT) { + getBaseMetaTileEntity().setActive(true); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_Hatch_DegasifierControlHatch(mName, mTier, mDescriptionArray, mTextures); + } + + @Override + public String[] getDescription() { + return new String[] { "Can be installed in the Degasser Purification Unit.", + "Outputs Redstone Signal Strength based on the current control signal." }; + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + outputStrength = aNBT.getByte("mOutputStrength"); + super.loadNBTData(aNBT); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + aNBT.setByte("mOutputStrength", outputStrength); + super.saveNBTData(aNBT); + } + + // Pass zero signal to disable output + public void updateOutputSignal(byte signal) { + outputStrength = signal; + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + if (outputStrength > 0) { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setStrongOutputRedstoneSignal(side, outputStrength); + } + } else { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setStrongOutputRedstoneSignal(side, (byte) 0); + } + } + super.onPostTick(aBaseMetaTileEntity, aTick); + } + + @Override + public ITexture[] getTexturesActive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont), TextureFactory.builder() + .addIcon(textureFont_Glow) + .glow() + .build() }; + } + + @Override + public ITexture[] getTexturesInactive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont) }; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensHousing.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensHousing.java new file mode 100644 index 0000000000..bd025e0e69 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensHousing.java @@ -0,0 +1,52 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; + +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus; +import gregtech.client.GT_TooltipHandler; + +public class GT_MetaTileEntity_LensHousing extends GT_MetaTileEntity_Hatch_InputBus { + + public GT_MetaTileEntity_LensHousing(int id, String name, String nameRegional) { + super( + id, + name, + nameRegional, + GT_TooltipHandler.Tier.UV.ordinal(), + 1, + new String[] { "Holds a lens for UV laser focusing." }); + } + + public GT_MetaTileEntity_LensHousing(String aName, int aTier, String[] aDescription, ITexture[][][] aTextures) { + super(aName, aTier, aDescription, aTextures); + } + + @Override + public MetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_LensHousing(this.mName, this.mTier, this.mDescriptionArray, this.mTextures); + } + + @Override + public int getSizeInventory() { + return 1; + } + + @Override + public int getCircuitSlot() { + return -1; + } + + @Override + public boolean allowSelectCircuit() { + return false; + } + + @Override + public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { + getBaseMetaTileEntity().add1by1Slot(builder); + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensIndicator.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensIndicator.java new file mode 100644 index 0000000000..c017025d42 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_LensIndicator.java @@ -0,0 +1,117 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.ForgeDirection; + +import gregtech.api.enums.Textures; +import gregtech.api.interfaces.IIconContainer; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.render.TextureFactory; + +public class GT_MetaTileEntity_LensIndicator extends GT_MetaTileEntity_Hatch { + + private boolean isOn = false; + + private static final IIconContainer textureFont = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR; + private static final IIconContainer textureFont_Glow = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR_GLOW; + + public GT_MetaTileEntity_LensIndicator(int aID, String aName, String aNameRegional, int aTier) { + super(aID, aName, aNameRegional, aTier, 0, "Indicates required lens swaps."); + } + + public GT_MetaTileEntity_LensIndicator(String aName, int aTier, String[] aDescription, ITexture[][][] aTextures) { + super(aName, aTier, 0, aDescription, aTextures); + } + + @Override + public boolean isValidSlot(int aIndex) { + return false; + } + + @Override + public boolean isSimpleMachine() { + return true; + } + + @Override + public boolean isFacingValid(ForgeDirection facing) { + return true; + } + + @Override + public boolean isAccessAllowed(EntityPlayer aPlayer) { + return true; + } + + @Override + public boolean allowGeneralRedstoneOutput() { + return true; + } + + @Override + public boolean allowPullStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection Side, + ItemStack aStack) { + return false; + } + + @Override + public boolean allowPutStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection side, + ItemStack aStack) { + return false; + } + + @Override + public void initDefaultModes(NBTTagCompound aNBT) { + getBaseMetaTileEntity().setActive(true); + } + + @Override + public String[] getDescription() { + return new String[] { "Can be installed in the UV Treatment Purification Unit.", + "Outputs Redstone Signal when a lens swap is requested." }; + } + + /** + * Updates redstone output strength based on the pH of the multiblock. + */ + public void updateRedstoneOutput(boolean enabled) { + isOn = enabled; + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + if (isOn) { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setInternalOutputRedstoneSignal(side, (byte) 15); + } + } else { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setInternalOutputRedstoneSignal(side, (byte) 0); + } + } + super.onPostTick(aBaseMetaTileEntity, aTick); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_LensIndicator(mName, mTier, mDescriptionArray, mTextures); + } + + @Override + public ITexture[] getTexturesActive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont), TextureFactory.builder() + .addIcon(textureFont_Glow) + .glow() + .build() }; + } + + @Override + public ITexture[] getTexturesInactive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont) }; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationPlant.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationPlant.java new file mode 100644 index 0000000000..42bfd06d7a --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationPlant.java @@ -0,0 +1,740 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockAnyMeta; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.api.enums.GT_HatchElement.Energy; +import static gregtech.api.enums.GT_HatchElement.ExoticEnergy; +import static gregtech.api.enums.GT_HatchElement.Maintenance; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_GLOW; +import static gregtech.api.util.GT_RecipeBuilder.SECONDS; +import static gregtech.api.util.GT_StructureUtility.ofFrame; +import static gregtech.common.tileentities.machines.multi.purification.GT_MetaTileEntity_PurificationUnitBase.WATER_BOOST_BONUS_CHANCE; +import static gregtech.common.tileentities.machines.multi.purification.GT_MetaTileEntity_PurificationUnitBase.WATER_BOOST_NEEDED_FLUID; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.common.util.ForgeDirection; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; +import com.gtnewhorizons.modularui.api.drawable.shapes.Rectangle; +import com.gtnewhorizons.modularui.api.forge.ItemStackHandler; +import com.gtnewhorizons.modularui.api.math.Alignment; +import com.gtnewhorizons.modularui.api.math.Color; +import com.gtnewhorizons.modularui.api.math.Size; +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; +import com.gtnewhorizons.modularui.api.widget.Widget; +import com.gtnewhorizons.modularui.common.widget.ButtonWidget; +import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn; +import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow; +import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; +import com.gtnewhorizons.modularui.common.widget.Scrollable; +import com.gtnewhorizons.modularui.common.widget.SlotWidget; +import com.gtnewhorizons.modularui.common.widget.TextWidget; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.ItemList; +import gregtech.api.enums.Materials; +import gregtech.api.enums.Textures; +import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_ExtendedPowerMultiBlockBase; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Log; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.shutdown.ShutDownReasonRegistry; +import gregtech.common.gui.modularui.widget.ShutDownReasonSyncer; +import gregtech.common.gui.modularui.widget.TextButtonWidget; + +public class GT_MetaTileEntity_PurificationPlant + extends GT_MetaTileEntity_ExtendedPowerMultiBlockBase<GT_MetaTileEntity_PurificationPlant> + implements ISurvivalConstructable { + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final String STRUCTURE_PIECE_MAIN_SURVIVAL = "main_survival"; + + /** + * Maximum distance in each axis between the purification plant main controller and the controller blocks of the + * purification plant units. + */ + public static final int MAX_UNIT_DISTANCE = 32; + + /** + * Time in ticks for a full processing cycle to complete. + */ + public static final int CYCLE_TIME_TICKS = 120 * SECONDS; + + /** + * Stores all purification units linked to this controller. + * Normally all units in this list should be valid and unique, if not then there is a bug where they are not being + * unlinked properly on block destruction/relinking. + */ + private final List<LinkedPurificationUnit> mLinkedUnits = new ArrayList<>(); + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationPlant> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationPlant>builder() + .addShape(STRUCTURE_PIECE_MAIN, PurificationPlantStructureString.STRUCTURE_STRING) + // Create an identical structure for survival autobuild, with water replaced with air + .addShape( + STRUCTURE_PIECE_MAIN_SURVIVAL, + Arrays.stream(PurificationPlantStructureString.STRUCTURE_STRING) + .map( + sa -> Arrays.stream(sa) + .map(s -> s.replaceAll("F", " ")) + .toArray(String[]::new)) + .toArray(String[][]::new)) + // Superplasticizer-treated high strength concrete + .addElement('A', ofBlock(GregTech_API.sBlockCasings9, 3)) + // Sterile Water Plant Casing + .addElement('B', ofBlock(GregTech_API.sBlockCasings9, 4)) + // Reinforced Sterile Water Plant Casing + .addElement('C', ofBlock(GregTech_API.sBlockCasings9, 5)) + // Tinted Industrial Glass + .addElement('D', ofBlockAnyMeta(GregTech_API.sBlockTintedGlass, 0)) + .addElement('F', ofBlock(Blocks.water, 0)) + .addElement('G', ofFrame(Materials.Tungsten)) + // Hatch space + .addElement( + 'H', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationPlant>buildHatchAdder() + .atLeastList(t.getAllowedHatches()) + .dot(1) + .casingIndex(GT_Utility.getCasingTextureIndex(GregTech_API.sBlockCasings9, 4)) + .build()), + ofBlock(GregTech_API.sBlockCasings9, 4))) + .build(); + + public GT_MetaTileEntity_PurificationPlant(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationPlant(String aName) { + super(aName); + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece(STRUCTURE_PIECE_MAIN, stackSize, hintsOnly, 3, 6, 0); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + int built = survivialBuildPiece(STRUCTURE_PIECE_MAIN_SURVIVAL, stackSize, 3, 6, 0, elementBudget, env, true); + if (built == -1) { + GT_Utility.sendChatToPlayer( + env.getActor(), + EnumChatFormatting.GREEN + "Auto placing done ! Now go place the water yourself !"); + return 0; + } + return built; + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationPlant> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Plant") + .addInfo("Main controller block for the Water Purification Plant.") + .addInfo( + "Freely place " + EnumChatFormatting.YELLOW + + "Purification Units " + + EnumChatFormatting.GRAY + + "within " + + EnumChatFormatting.RED + + MAX_UNIT_DISTANCE + + EnumChatFormatting.GRAY + + " blocks along each axis.") + .addInfo("Left click this controller with a data stick, then right click a purification unit to link.") + .addInfo("Supplies power to linked purification units. This multiblock accepts TecTech energy hatches.") + .addSeparator() + .addInfo( + "Works in fixed time processing cycles of " + EnumChatFormatting.RED + + CYCLE_TIME_TICKS / SECONDS + + EnumChatFormatting.GRAY + + " seconds.") + .addInfo("All linked units follow this cycle.") + .addSeparator() + .addInfo("Every recipe has a base chance of success. Success rate can be boosted") + .addInfo("by using a portion of the target output as a secondary input.") + .addInfo( + EnumChatFormatting.RED + GT_Utility.formatNumbers(WATER_BOOST_NEEDED_FLUID * 100) + + "%" + + EnumChatFormatting.GRAY + + " of output yield will be consumed in exchange for an") + .addInfo( + "additive " + EnumChatFormatting.RED + + GT_Utility.formatNumbers(WATER_BOOST_BONUS_CHANCE * 100) + + "%" + + EnumChatFormatting.GRAY + + " increase to success.") + .addInfo( + "On recipe failure, each purification unit has a " + EnumChatFormatting.RED + + "50%" + + EnumChatFormatting.GRAY + + " chance") + .addInfo("to return water of the same quality as the input or lower.") + .addSeparator() + .addInfo("Every purification unit has a configuration window to configure maximum parallel amount.") + .addInfo( + "This will only scale purified water I/O and power usage. Other catalysts and outputs are unchanged.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "Contaminants and ionized particles in water can cause significant imperfections in delicate") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "processes related to the cutting and engraving of silicon wafers and chips. It is crucial that") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "the water is systematically purified through a series of increasingly precise and complex") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "purification processes, and this multiblock is the heart of the operation.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(7, 9, 8, false) + .addCasingInfoExactlyColored( + "Superplasticizer-Treated High Strength Concrete", + EnumChatFormatting.GRAY, + 56, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 77, + EnumChatFormatting.GOLD, + false) + .addCasingInfoRangeColored( + "Reinforced Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 71, + 72, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Tungsten Frame Box", + EnumChatFormatting.GRAY, + 30, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Tinted Industrial Glass", + EnumChatFormatting.GRAY, + 6, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored("Reinforced Door", EnumChatFormatting.GRAY, 1, EnumChatFormatting.GOLD, false) + .addController("Front center") + .addEnergyHatch(EnumChatFormatting.GOLD + "1", 1) + .addMaintenanceHatch(EnumChatFormatting.GOLD + "1", 1) + .addStructureInfo("Requires water to be placed in the tank.") + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationPlant(this.mName); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection side, ForgeDirection facing, + int colorIndex, boolean active, boolean redstoneLevel) { + if (side == facing) { + if (active) return new ITexture[] { + Textures.BlockIcons + .getCasingTextureForId(GT_Utility.getCasingTextureIndex(GregTech_API.sBlockCasings9, 4)), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { + Textures.BlockIcons + .getCasingTextureForId(GT_Utility.getCasingTextureIndex(GregTech_API.sBlockCasings9, 4)), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons + .getCasingTextureForId(GT_Utility.getCasingTextureIndex(GregTech_API.sBlockCasings9, 4)) }; + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + private List<IHatchElement<? super GT_MetaTileEntity_PurificationPlant>> getAllowedHatches() { + return ImmutableList.of(Maintenance, Energy, ExoticEnergy); + } + + @Override + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + // Check self + if (!checkPiece(STRUCTURE_PIECE_MAIN, 3, 6, 0)) { + return false; + } + + // Check hatches + if (!checkHatches()) { + return false; + } + + // using nano forge method of detecting hatches. + if (!checkExoticAndNormalEnergyHatches()) { + return false; + } + + return true; + } + + private boolean checkHatches() { + // Exactly one maintenance hatch is required + return mMaintenanceHatches.size() == 1; + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.onPostTick(aBaseMetaTileEntity, aTick); + + if (aBaseMetaTileEntity.isServerSide()) { + // Trigger structure check of linked units, but never all in the same tick, and at most once per cycle. + for (int i = 0; i < mLinkedUnits.size(); ++i) { + if (aTick % CYCLE_TIME_TICKS == i) { + LinkedPurificationUnit unit = mLinkedUnits.get(i); + boolean structure = unit.metaTileEntity() + .checkStructure(true); + // If unit was active but deformed, set as inactive + if (unit.isActive() && !structure) { + unit.setActive(false); + // Also remember to recalculate power usage, since otherwise the deformed unit will + // keep drawing power + this.lEUt = -calculateEffectivePowerUsage(); + } + } + } + } + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + updateCycleProgress(); + // Calculate efficiency based on maintenance issues + if (mMaxProgresstime > 0) { + mEfficiency = Math.max( + 0, + Math.min( + mEfficiency + mEfficiencyIncrease, + getMaxEfficiency(mInventory[1]) - ((getIdealStatus() - getRepairStatus()) * 1000))); + } + } + + private void updateCycleProgress() { + // Since the plant does not run recipes directly, we just continuously loop the base cycle + if (mMachine) { + // cycle is running, so simply advance it + if (mMaxProgresstime > 0) { + // onRunningTick is responsible for draining power + if (onRunningTick(mInventory[1])) { + markDirty(); + mProgresstime += 1; + // Update progress time for active units + for (LinkedPurificationUnit unit : this.mLinkedUnits) { + if (unit.isActive()) { + GT_MetaTileEntity_PurificationUnitBase<?> metaTileEntity = unit.metaTileEntity(); + metaTileEntity.mProgresstime = mProgresstime; + } + } + // Cycle finished + if (mProgresstime >= mMaxProgresstime) { + this.endCycle(); + } + } else { + // Power drain failed, shut down all other units due to power loss. + // Note that we do not need to shut down self, as this is done in + // onRunningTick already + for (LinkedPurificationUnit unit : mLinkedUnits) { + if (unit.isActive()) { + unit.metaTileEntity() + .stopMachine(ShutDownReasonRegistry.POWER_LOSS); + } + } + } + } + + // No cycle running, start a new cycle if the machine is turned on + if (mMaxProgresstime == 0 && isAllowedToWork()) { + this.startCycle(); + } + } + } + + private void startCycle() { + mProgresstime = 0; + mMaxProgresstime = CYCLE_TIME_TICKS; + mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); + + // Find active units and notify them that the cycle started + for (LinkedPurificationUnit unit : this.mLinkedUnits) { + GT_MetaTileEntity_PurificationUnitBase<?> metaTileEntity = unit.metaTileEntity(); + PurificationUnitStatus status = metaTileEntity.status(); + // Unit needs to be online to be considered active. + if (status == PurificationUnitStatus.ONLINE) { + // Perform recipe check for unit and start it if successful + if (metaTileEntity.doPurificationRecipeCheck()) { + unit.setActive(true); + metaTileEntity.startCycle(mMaxProgresstime, mProgresstime); + } + } + } + + // After activating all units, calculate power usage + lEUt = -calculateEffectivePowerUsage(); + } + + private void endCycle() { + mMaxProgresstime = 0; + + // Mark all units as inactive and reset their progress time + for (LinkedPurificationUnit unit : this.mLinkedUnits) { + GT_MetaTileEntity_PurificationUnitBase<?> metaTileEntity = unit.metaTileEntity(); + // If this unit was active, end the cycle + if (unit.isActive()) { + metaTileEntity.endCycle(); + } + unit.setActive(false); + } + } + + /** + * Calculate power usage of all units + */ + private long calculateEffectivePowerUsage() { + long euT = 0; + for (LinkedPurificationUnit unit : mLinkedUnits) { + if (unit.isActive()) { + euT += unit.metaTileEntity() + .getActualPowerUsage(); + } + } + return euT; + } + + @Override + public int getMaxEfficiency(ItemStack aStack) { + return 10000; + } + + @Override + public int getDamageToComponent(ItemStack aStack) { + return 0; + } + + @Override + public boolean explodesOnComponentBreak(ItemStack aStack) { + return false; + } + + public void registerLinkedUnit(GT_MetaTileEntity_PurificationUnitBase<?> unit) { + this.mLinkedUnits.add(new LinkedPurificationUnit(unit)); + } + + public void unregisterLinkedUnit(GT_MetaTileEntity_PurificationUnitBase<?> unit) { + this.mLinkedUnits.removeIf(link -> link.metaTileEntity() == unit); + } + + @Override + public void onLeftclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer) { + if (!(aPlayer instanceof EntityPlayerMP)) return; + + // Save link data to data stick, very similar to Crafting Input Buffer. + + ItemStack dataStick = aPlayer.inventory.getCurrentItem(); + if (!ItemList.Tool_DataStick.isStackEqual(dataStick, false, true)) return; + + NBTTagCompound tag = new NBTTagCompound(); + tag.setString("type", "PurificationPlant"); + tag.setInteger("x", aBaseMetaTileEntity.getXCoord()); + tag.setInteger("y", aBaseMetaTileEntity.getYCoord()); + tag.setInteger("z", aBaseMetaTileEntity.getZCoord()); + + dataStick.stackTagCompound = tag; + dataStick.setStackDisplayName( + "Purification Plant Link Data Stick (" + aBaseMetaTileEntity + .getXCoord() + ", " + aBaseMetaTileEntity.getYCoord() + ", " + aBaseMetaTileEntity.getZCoord() + ")"); + aPlayer.addChatMessage(new ChatComponentText("Saved Link Data to Data Stick")); + } + + @Override + public String[] getInfoData() { + var ret = new ArrayList<String>(); + // Show linked purification units and their status + ret.add("Linked Purification Units: "); + for (LinkedPurificationUnit unit : this.mLinkedUnits) { + String text = EnumChatFormatting.AQUA + unit.metaTileEntity() + .getLocalName() + ": "; + PurificationUnitStatus status = unit.metaTileEntity() + .status(); + switch (status) { + case ONLINE -> { + text = text + EnumChatFormatting.GREEN + "Online"; + } + case DISABLED -> { + text = text + EnumChatFormatting.YELLOW + "Disabled"; + } + case INCOMPLETE_STRUCTURE -> { + text = text + EnumChatFormatting.RED + "Incomplete Structure"; + } + } + ret.add(text); + } + return ret.toArray(new String[0]); + } + + @Override + public void onBlockDestroyed() { + // When the controller is destroyed we want to notify all currently linked units + for (LinkedPurificationUnit unit : this.mLinkedUnits) { + unit.metaTileEntity() + .unlinkController(); + } + super.onBlockDestroyed(); + } + + private void drawTopText(DynamicPositionedColumn screenElements) { + screenElements.setSynced(false) + .setSpace(0) + .setPos(10, 8); + + screenElements + .widget( + new TextWidget(GT_Utility.trans("138", "Incomplete Structure.")).setDefaultColor(EnumChatFormatting.RED) + .setEnabled(widget -> !mMachine)) + .widget(new FakeSyncWidget.BooleanSyncer(() -> mMachine, val -> mMachine = val)); + + screenElements.widget( + new TextWidget("Hit with Soft Mallet to start.").setDefaultColor(EnumChatFormatting.BLACK) + .setEnabled( + widget -> getBaseMetaTileEntity().getErrorDisplayID() == 0 && !getBaseMetaTileEntity().isActive())) + .widget( + new FakeSyncWidget.IntegerSyncer( + () -> getBaseMetaTileEntity().getErrorDisplayID(), + val -> getBaseMetaTileEntity().setErrorDisplayID(val))) + .widget( + new FakeSyncWidget.BooleanSyncer( + () -> getBaseMetaTileEntity().isActive(), + val -> getBaseMetaTileEntity().setActive(val))); + screenElements.widget( + new TextWidget(GT_Utility.trans("142", "Running perfectly.")).setDefaultColor(EnumChatFormatting.GREEN) + .setEnabled( + widget -> getBaseMetaTileEntity().getErrorDisplayID() == 0 && getBaseMetaTileEntity().isActive())); + screenElements.widget( + TextWidget.dynamicString( + () -> getBaseMetaTileEntity().getLastShutDownReason() + .getDisplayString()) + .setSynced(false) + .setTextAlignment(Alignment.CenterLeft) + .setEnabled( + widget -> shouldDisplayShutDownReason() && !getBaseMetaTileEntity().isActive() + && GT_Utility.isStringValid( + getBaseMetaTileEntity().getLastShutDownReason() + .getDisplayString()) + && getBaseMetaTileEntity().wasShutdown())) + .widget( + new ShutDownReasonSyncer( + () -> getBaseMetaTileEntity().getLastShutDownReason(), + reason -> getBaseMetaTileEntity().setShutDownReason(reason))) + .widget( + new FakeSyncWidget.BooleanSyncer( + () -> getBaseMetaTileEntity().wasShutdown(), + wasShutDown -> getBaseMetaTileEntity().setShutdownStatus(wasShutDown))); + screenElements.widget( + TextWidget.dynamicString(this::generateCurrentRecipeInfoString) + .setSynced(false) + .setTextAlignment(Alignment.CenterLeft) + .setEnabled(widget -> (mMaxProgresstime > 0))) + .widget(new FakeSyncWidget.IntegerSyncer(() -> mProgresstime, val -> mProgresstime = val)) + .widget(new FakeSyncWidget.IntegerSyncer(() -> mMaxProgresstime, val -> mMaxProgresstime = val)); + } + + private final int STATUS_WINDOW_ID = 10; + + private ModularWindow createStatusWindow(final EntityPlayer player) { + final int windowWidth = 260; + final int windowHeight = 200; + ModularWindow.Builder builder = ModularWindow.builder(windowWidth, windowHeight); + builder.setBackground(GT_UITextures.BACKGROUND_SINGLEBLOCK_DEFAULT); + builder.widget( + ButtonWidget.closeWindowButton(true) + .setPos(windowWidth - 15, 3)); + + // Title widget + builder.widget( + new TextWidget(EnumChatFormatting.BOLD + "Purification Unit Status").setTextAlignment(Alignment.Center) + .setPos(5, 10) + .setSize(windowWidth, 8)); + + int currentYPosition = 20; + Scrollable mainDisp = new Scrollable().setVerticalScroll(); + + int rowHeight = 20; + for (int i = 0; i < this.mLinkedUnits.size(); i++) { + mainDisp.widget(makeUnitStatusWidget(mLinkedUnits.get(i)).setPos(0, rowHeight * (i + 1))); + } + + builder.widget( + mainDisp.setPos(5, currentYPosition) + .setSize(windowWidth - 10, windowHeight - currentYPosition - 5)); + return builder.build(); + } + + private Widget makeStatusWindowButton() { + TextButtonWidget widget = (TextButtonWidget) new TextButtonWidget("Status").setLeftMargin(4) + .setSize(40, 16) + .setPos(10, 40); + widget.button() + .setOnClick( + (clickData, w) -> { + if (!w.isClient()) w.getContext() + .openSyncedWindow(STATUS_WINDOW_ID); + }) + .setBackground(GT_UITextures.BUTTON_STANDARD); + widget.text() + .setTextAlignment(Alignment.CenterLeft) + .setDefaultColor(EnumChatFormatting.BLACK); + return widget; + } + + private Widget makeUnitStatusWidget(LinkedPurificationUnit unit) { + // Draw small machine controller icon + DynamicPositionedRow row = new DynamicPositionedRow(); + ItemStackHandler machineIcon = new ItemStackHandler(1); + machineIcon.setStackInSlot( + 0, + unit.metaTileEntity() + .getStackForm(1)); + + row.widget( + SlotWidget.phantom(machineIcon, 0) + .disableInteraction() + .setPos(0, 0)) + .setSize(20, 20); + + // Display machine name and status + String name = unit.metaTileEntity() + .getLocalName(); + + row.widget( + TextWidget.dynamicString(() -> name + " " + unit.getStatusString()) + .setSynced(false) + .setTextAlignment(Alignment.CenterLeft) + .setPos(25, 0) + .setSize(0, 20)) + .widget(new FakeSyncWidget.StringSyncer(() -> name, _name -> {})) + .widget( + unit.metaTileEntity() + .makeSyncerWidgets()) + .widget(new FakeSyncWidget.BooleanSyncer(unit::isActive, unit::setActive));; + + return row; + } + + @Override + public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { + + buildContext.addSyncedWindow(STATUS_WINDOW_ID, this::createStatusWindow); + + // Draw basic recipe info + final DynamicPositionedColumn controlTextArea = new DynamicPositionedColumn(); + drawTopText(controlTextArea); + builder.widget(controlTextArea); + + // Draw line separator + builder.widget( + new Rectangle().setColor(Color.rgb(114, 120, 139)) + .asWidget() + .setSizeProvider((screenSize, window, parent) -> new Size(window.getSize().width - 8, 2)) + .setPos(3, 32)); + + // Add status window button + builder.widget(makeStatusWindowButton()); + + // Add parallel count number input + + builder.widget(createPowerSwitchButton(builder)); + + // Add value syncers, note that we do this here so + // everything is updated once the status gui opens + addSyncers(builder); + } + + private void addSyncers(ModularWindow.Builder builder) { + // Sync connection list to client + builder.widget(new FakeSyncWidget.ListSyncer<>(() -> mLinkedUnits, links -> { + mLinkedUnits.clear(); + mLinkedUnits.addAll(links); + }, (buffer, link) -> { + // Try to save link data to NBT, so we can reconstruct it on client + try { + buffer.writeNBTTagCompoundToBuffer(link.writeLinkDataToNBT()); + } catch (IOException e) { + GT_Log.err.println(e.getCause()); + } + }, buffer -> { + // Try to load link data from NBT compound as constructed above. + try { + return new LinkedPurificationUnit(buffer.readNBTTagCompoundFromBuffer()); + } catch (IOException e) { + GT_Log.err.println(e.getCause()); + } + return null; + })); + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitBase.java new file mode 100644 index 0000000000..0abf9d525f --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitBase.java @@ -0,0 +1,793 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY; +import static net.minecraft.util.StatCollector.translateToLocal; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; + +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.NotNull; + +import com.gtnewhorizons.modularui.api.drawable.IDrawable; +import com.gtnewhorizons.modularui.api.drawable.UITexture; +import com.gtnewhorizons.modularui.api.math.Alignment; +import com.gtnewhorizons.modularui.api.math.Color; +import com.gtnewhorizons.modularui.api.math.Size; +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; +import com.gtnewhorizons.modularui.api.widget.Widget; +import com.gtnewhorizons.modularui.common.widget.ButtonWidget; +import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; +import com.gtnewhorizons.modularui.common.widget.MultiChildWidget; +import com.gtnewhorizons.modularui.common.widget.TextWidget; +import com.gtnewhorizons.modularui.common.widget.textfield.NumericWidget; + +import gregtech.api.enums.ItemList; +import gregtech.api.enums.Materials; +import gregtech.api.enums.VoidingMode; +import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_ExtendedPowerMultiBlockBase; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.recipe.check.CheckRecipeResultRegistry; +import gregtech.api.recipe.metadata.PurificationPlantBaseChanceKey; +import gregtech.api.util.GT_ModHandler; +import gregtech.api.util.GT_Recipe; +import gregtech.api.util.GT_Utility; +import gregtech.common.blocks.GT_Block_Casings_Abstract; +import mcp.mobius.waila.api.IWailaConfigHandler; +import mcp.mobius.waila.api.IWailaDataAccessor; + +/** + * Base class for purification units. This class handles all shared behaviour between units. + * When inheriting from this, make sure to call super.loadNBTData() and super.saveNBTData() + * if you override these methods, or linking will break. + */ +public abstract class GT_MetaTileEntity_PurificationUnitBase<T extends GT_MetaTileEntity_ExtendedPowerMultiBlockBase<T>> + extends GT_MetaTileEntity_ExtendedPowerMultiBlockBase<T> { + + /** + * Ratio of output fluid that needs to be inserted back as input to trigger a "water boost". + * Must be in [0, 1]. + */ + public static final float WATER_BOOST_NEEDED_FLUID = 0.1f; + /** + * Additive bonus to success chance when water boost is active. + * Must be in [0, 1] + */ + public static final float WATER_BOOST_BONUS_CHANCE = 0.15f; + + /** + * Small internal enum to report back the various error cases when linking purification units to the + * purification plant. + */ + private enum LinkResult { + /** + * Link target was out of range of the main controller + */ + TOO_FAR, + /** + * No valid GT_MetaTileEntity_PurificationPlant was found at the link target position. + */ + NO_VALID_PLANT, + /** + * Link successful + */ + SUCCESS, + } + + /** + * Coordinates of the main purification plant controller. These can be used to find the controller again + * on world load. + */ + private int controllerX, controllerY, controllerZ; + + /** + * Whether a controller was previously set. + */ + private boolean controllerSet = false; + + /** + * Pointer to the main purification plant controller. + */ + private GT_MetaTileEntity_PurificationPlant controller = null; + + /** + * The current recipe being run in the purification unit. Note that purification unit recipes are a bit special, + * so input and output in the recipe might not exactly match the required inputs and produced outputs. + * For more information, always look at the purification unit tooltip and implementation. + */ + protected GT_Recipe currentRecipe = null; + + /** + * Current chance of the recipe succeeding, always in [0, 100]. A chance above 100 will be interpreted as 100. + */ + protected float currentRecipeChance = 0.0f; + + /** + * Configured parallel amount. Only water I/O and power scale. + */ + protected int maxParallel = 1; + + protected int effectiveParallel = 1; + + protected ArrayList<FluidStack> storedFluids = null; + + protected GT_MetaTileEntity_PurificationUnitBase(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + protected GT_MetaTileEntity_PurificationUnitBase(String aName) { + super(aName); + } + + @Override + public int getMaxEfficiency(ItemStack aStack) { + return 10000; + } + + @Override + public int getDamageToComponent(ItemStack aStack) { + return 0; + } + + @Override + public boolean explodesOnComponentBreak(ItemStack aStack) { + return false; + } + + @Override + public boolean doRandomMaintenanceDamage() { + // The individual purification unit structures cannot have maintenance issues, so do nothing. + return true; + } + + /** + * Used to more easily grab a correct texture index from a block + meta. + * + * @param block Block to use as base. Must implement GT_Block_Casings_Abstract + * @param meta Metadata of the block to pick the actual block + * @return The correct index into the global texture atlas. + */ + protected static int getTextureIndex(Block block, int meta) { + return ((GT_Block_Casings_Abstract) block).getTextureIndex(meta); + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTimer) { + super.onPostTick(aBaseMetaTileEntity, aTimer); + // Try to re-link to controller periodically, for example on game load. + if (aTimer % 100 == 5 && controllerSet && getController() == null) { + trySetControllerFromCoord(controllerX, controllerY, controllerZ); + } + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + // Main controller updates progress time. We can do I/O logic here. + // The logic for operating purification units is typically implemented by overriding this behaviour. + if (mMaxProgresstime > 0) { + this.markDirty(); + // Do not take maintenance into consideration, because purification units do not get + // maintenance issues. + // Technically, this entire efficiency stat is a bit useless for purification units, since + // their power draw does not actually depend on it, but it's nice to keep around for consistency with other + // multiblocks. This way, you still gradually see the efficiency go down when it powers down. + mEfficiency = Math.max(0, Math.min(mEfficiency + mEfficiencyIncrease, getMaxEfficiency(mInventory[1]))); + } + } + + protected CheckRecipeResult findRecipeForInputs(FluidStack[] fluidInputs, ItemStack... itemInputs) { + RecipeMap<?> recipeMap = this.getRecipeMap(); + + // Grab a stream of recipes and find the one with the highest success chance + Stream<GT_Recipe> recipes = recipeMap.findRecipeQuery() + .fluids(fluidInputs) + .items(itemInputs) + .findAll(); + GT_Recipe recipe = recipes + .max(Comparator.comparing(r -> r.getMetadataOrDefault(PurificationPlantBaseChanceKey.INSTANCE, 0.0f))) + .orElse(null); + + if (recipe == null) { + return CheckRecipeResultRegistry.NO_RECIPE; + } + + if (this.protectsExcessFluid() && !this.canOutputAll(recipe.mFluidOutputs)) { + return CheckRecipeResultRegistry.FLUID_OUTPUT_FULL; + } + + if (this.protectsExcessItem() && !this.canOutputAll(recipe.mOutputs)) { + return CheckRecipeResultRegistry.ITEM_OUTPUT_FULL; + } + + this.currentRecipe = recipe; + return CheckRecipeResultRegistry.SUCCESSFUL; + } + + /** + * By default, only checks fluid input. + * + * @return + */ + @NotNull + @Override + public CheckRecipeResult checkProcessing() { + this.storedFluids = this.getStoredFluids(); + CheckRecipeResult result = overrideRecipeCheck(); + if (result == null) result = findRecipeForInputs(storedFluids.toArray(new FluidStack[] {})); + + // If we had a successful result, calculate effective parallel + if (result.wasSuccessful()) { + FluidStack waterInput = this.currentRecipe.mFluidInputs[0]; + // Count total available purified water input of the previous step + long amountAvailable = 0; + for (FluidStack fluid : this.storedFluids) { + if (fluid.isFluidEqual(waterInput)) { + amountAvailable += fluid.amount; + } + } + // Determine effective parallel + effectiveParallel = (int) Math.min(maxParallel, Math.floorDiv(amountAvailable, waterInput.amount)); + // This should not happen, throw an error + if (effectiveParallel == 0) return CheckRecipeResultRegistry.INTERNAL_ERROR; + } + + return result; + } + + public CheckRecipeResult overrideRecipeCheck() { + return null; + } + + /** + * Equivalent to checkRecipe(), but public because the purification plant needs to access it and checkRecipe() + * is protected. + * + * @return True if successfully found a recipe and/or started processing/ + */ + public boolean doPurificationRecipeCheck() { + effectiveParallel = 1; + return this.checkRecipe(); + } + + /** + * Get the success chance of the recipe, from 0 to 100. Never call this while a recipe is running, because items + * or modifiers used to boost might disappear by the time recipe check comes around, + * which would invalidate this result. + */ + public float calculateBoostedSuccessChance() { + // If this.currentRecipe is null, there is a bug, so throwing a NPE is fine. + float recipeChance = this.currentRecipe.getMetadataOrDefault(PurificationPlantBaseChanceKey.INSTANCE, 0.0f); + // Apply water boost if available. + if (isWaterBoosted(this.currentRecipe)) { + recipeChance = Math.min(recipeChance + WATER_BOOST_BONUS_CHANCE * 100.0f, 100.0f); + } + return recipeChance; + } + + /** + * By default, the final recipe success chance is simply the success chance calculated on recipe check. + * This applies water boosts when needed to the base chance. Purification units can override this to perform + * more complex success chance calculations, that even take into account what happened during the runtime of the + * recipe. + * + * @return The success chance of the recipe, at the point in time the outputs are to be produced. + */ + public float calculateFinalSuccessChance() { + return this.currentRecipeChance; + } + + /** + * Get the tier of water this unit makes. Starts at 1. + */ + public abstract int getWaterTier(); + + /** + * Get the amount of water needed to execute a water boost, in mb. + */ + public FluidStack getWaterBoostAmount(GT_Recipe recipe) { + // Recipes should always be constructed so that output water is always the first fluid output + FluidStack outputWater = recipe.mFluidOutputs[0]; + int amount = Math.round(outputWater.amount * WATER_BOOST_NEEDED_FLUID); + return new FluidStack(outputWater.getFluid(), amount); + } + + /** + * Returns true if this purification unit contains enough water to apply a water boost for the selected recipe. + * This should only be called during recipe check! Never call this while a recipe is running, because water used to + * boost might disappear by the time recipe check comes around, which would invalidate this result. + * + * @param recipe The recipe to check the water boost of + */ + public boolean isWaterBoosted(GT_Recipe recipe) { + FluidStack inputWater = getWaterBoostAmount(recipe); + // Simulate input drain to see if we can water boost + return depleteInput(inputWater, true); + } + + /** + * Consumes all <b>fluid</b> inputs of the current recipe. + */ + public void depleteRecipeInputs() { + for (FluidStack input : this.currentRecipe.mFluidInputs) { + FluidStack copyWithParallel = input.copy(); + copyWithParallel.amount = input.amount * effectiveParallel; + this.depleteInput(copyWithParallel); + } + } + + /** + * Called after a recipe is found and accepted. + * + * @param cycleTime Time for a full cycle to complete + * @param progressTime Current progress time + */ + public void startCycle(int cycleTime, int progressTime) { + // Important to calculate this before depleting inputs, otherwise we may get issues with boost items + // disappearing. + this.currentRecipeChance = this.calculateBoostedSuccessChance(); + + // Deplete inputs from water boost if enabled. + if (isWaterBoosted(this.currentRecipe)) { + FluidStack inputWater = this.getWaterBoostAmount(this.currentRecipe); + this.depleteInput(inputWater); + } + + // Consume inputs + this.depleteRecipeInputs(); + // Initialize recipe and progress information. + this.mMaxProgresstime = cycleTime; + this.mProgresstime = progressTime; + this.mEfficiency = 10000; + // These need to be set so the GUI code can display the produced outputs + + // Make sure to scale purified water output with parallel amount. + // Make sure to make a full copy of the array, so we don't go modifying recipes + FluidStack[] fluidOutputs = new FluidStack[this.currentRecipe.mFluidOutputs.length]; + for (int i = 0; i < this.currentRecipe.mFluidOutputs.length; ++i) { + fluidOutputs[i] = this.currentRecipe.mFluidOutputs[i].copy(); + } + fluidOutputs[0].amount *= effectiveParallel; + this.mOutputFluids = fluidOutputs; + this.mOutputItems = this.currentRecipe.mOutputs; + // Set this value, so it can be displayed in Waila. Note that the logic for the units is + // specifically overridden so setting this value does not actually drain power. + // Instead, power is drained by the main purification plant controller. + this.lEUt = -this.getActualPowerUsage(); + } + + public void addRecipeOutputs() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + this.addFluidOutputs(mOutputFluids); + // If this recipe has random item outputs, roll on it and add outputs + if (this.currentRecipe.mChances != null) { + // Roll on each output individually + for (int i = 0; i < this.currentRecipe.mOutputs.length; ++i) { + // Recipes store probabilities as a value ranging from 1-10000 + int roll = random.nextInt(10000); + if (roll <= this.currentRecipe.mChances[i]) { + this.addOutput(this.currentRecipe.mOutputs[i]); + } + } + } else { + // Guaranteed item output + for (int i = 0; i < this.currentRecipe.mOutputs.length; ++i) { + this.addOutput(this.currentRecipe.mOutputs[i]); + } + } + } + + public void endCycle() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + + // First see if the recipe succeeded. For some reason random.nextFloat does not compile, so we use this + // hack instead. + float successRoll = random.nextInt(0, 10000) / 100.0f; + if (successRoll <= calculateFinalSuccessChance()) { + addRecipeOutputs(); + } else { + onRecipeFail(); + } + + // Reset recipe values for next iteration + this.mMaxProgresstime = 0; + this.mProgresstime = 0; + this.lEUt = 0; + this.mEfficiency = 0; + this.currentRecipe = null; + this.currentRecipeChance = 0.0f; + this.mOutputItems = null; + this.mOutputFluids = null; + this.effectiveParallel = 1; + } + + /** + * Outputs fluid when recipe fails. + */ + private void onRecipeFail() { + // Possibly output lower quality water. + // Note that if there is no space for this, it will be voided regardless of fluid void setting! + FluidStack outputWater = getDegradedOutputWater(); + this.addOutput(outputWater); + } + + /** + * On recipe fail, water quality may degrade to the same or lower tier. This function returns the water to output + * in this case, or null if no water is produced at all. + */ + private FluidStack getDegradedOutputWater() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int roll = random.nextInt(0, 2); + // 50% chance to not output anything at all + if (roll == 0) return null; + + for (int waterTier = getWaterTier(); waterTier > 0; --waterTier) { + // 50% chance every time of degrading into the previous tier + roll = random.nextInt(0, 2); + if (roll == 1) { + // Rolled good, stop the loop and output water below current tier + int amount = mOutputFluids[0].amount; + // For tier 1, this is distilled water, so we cannot use the helper function! + if (waterTier == 1) { + return GT_ModHandler.getDistilledWater(amount); + } + Materials water = PurifiedWaterHelpers.getPurifiedWaterTier(waterTier - 1); + return water.getFluid(amount); + } + // Bad roll, keep looping and degrade quality even further + } + // Rolled bad on every iteration, no output for you + return null; + } + + /** + * Get the EU/t usage of this unit while it is running. + */ + public abstract long getBasePowerUsage(); + + public long getActualPowerUsage() { + return getBasePowerUsage() * effectiveParallel; + } + + @Override + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + // The individual purification unit structures cannot have maintenance issues, so fix them all. + this.mCrowbar = true; + this.mWrench = true; + this.mHardHammer = true; + this.mSoftHammer = true; + this.mSolderingTool = true; + this.mScrewdriver = true; + return true; + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + // If a linked controller was found, load its coordinates. + // The unit will try to link to the real controller block periodically in onPostTick() + // We cannot do this linking here yet because the controller block might not be loaded yet. + // TODO: We could try though? + if (aNBT.hasKey("controller")) { + NBTTagCompound controllerNBT = aNBT.getCompoundTag("controller"); + controllerX = controllerNBT.getInteger("x"); + controllerY = controllerNBT.getInteger("y"); + controllerZ = controllerNBT.getInteger("z"); + controllerSet = true; + } + currentRecipeChance = aNBT.getFloat("currentRecipeChance"); + if (aNBT.hasKey("configuredParallel")) { + maxParallel = aNBT.getInteger("configuredParallel"); + } + if (aNBT.hasKey("effectiveParallel")) { + effectiveParallel = aNBT.getInteger("effectiveParallel"); + } + } + + public NBTTagCompound saveLinkDataToNBT() { + NBTTagCompound controllerNBT = new NBTTagCompound(); + controllerNBT.setInteger("x", controllerX); + controllerNBT.setInteger("y", controllerY); + controllerNBT.setInteger("z", controllerZ); + return controllerNBT; + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + if (controllerSet) { + NBTTagCompound controllerNBT = saveLinkDataToNBT(); + aNBT.setTag("controller", controllerNBT); + } + aNBT.setFloat("currentRecipeChance", currentRecipeChance); + aNBT.setInteger("configuredParallel", maxParallel); + aNBT.setInteger("effectiveParallel", effectiveParallel); + } + + private LinkResult trySetControllerFromCoord(int x, int y, int z) { + IGregTechTileEntity ourBaseMetaTileEntity = this.getBaseMetaTileEntity(); + // First check whether the controller we try to link to is within range. The range is defined + // as a max distance in each axis. + if (Math.abs(ourBaseMetaTileEntity.getXCoord() - x) > GT_MetaTileEntity_PurificationPlant.MAX_UNIT_DISTANCE) + return LinkResult.TOO_FAR; + if (Math.abs(ourBaseMetaTileEntity.getYCoord() - y) > GT_MetaTileEntity_PurificationPlant.MAX_UNIT_DISTANCE) + return LinkResult.TOO_FAR; + if (Math.abs(ourBaseMetaTileEntity.getZCoord() - z) > GT_MetaTileEntity_PurificationPlant.MAX_UNIT_DISTANCE) + return LinkResult.TOO_FAR; + + // Find the block at the requested coordinated and check if it is a purification plant controller. + var tileEntity = getBaseMetaTileEntity().getWorld() + .getTileEntity(x, y, z); + if (tileEntity == null) return LinkResult.NO_VALID_PLANT; + if (!(tileEntity instanceof IGregTechTileEntity gtTileEntity)) return LinkResult.NO_VALID_PLANT; + var metaTileEntity = gtTileEntity.getMetaTileEntity(); + if (!(metaTileEntity instanceof GT_MetaTileEntity_PurificationPlant)) return LinkResult.NO_VALID_PLANT; + + // Before linking, unlink from current controller, so we don't end up with units linked to multiple + // controllers. + GT_MetaTileEntity_PurificationPlant oldController = getController(); + if (oldController != null) { + oldController.unregisterLinkedUnit(this); + this.unlinkController(); + } + + // Now link to new controller + controllerX = x; + controllerY = y; + controllerZ = z; + controllerSet = true; + controller = (GT_MetaTileEntity_PurificationPlant) metaTileEntity; + controller.registerLinkedUnit(this); + return LinkResult.SUCCESS; + } + + private boolean tryLinkDataStick(EntityPlayer aPlayer) { + // Make sure the held item is a data stick + ItemStack dataStick = aPlayer.inventory.getCurrentItem(); + if (!ItemList.Tool_DataStick.isStackEqual(dataStick, false, true)) { + return false; + } + + // Make sure this data stick is a proper purification plant link data stick. + if (!dataStick.hasTagCompound() || !dataStick.stackTagCompound.getString("type") + .equals("PurificationPlant")) { + return false; + } + + // Now read link coordinates from the data stick. + NBTTagCompound nbt = dataStick.stackTagCompound; + int x = nbt.getInteger("x"); + int y = nbt.getInteger("y"); + int z = nbt.getInteger("z"); + + // Try to link, and report the result back to the player. + LinkResult result = trySetControllerFromCoord(x, y, z); + if (result == LinkResult.SUCCESS) { + aPlayer.addChatMessage(new ChatComponentText("Link successful")); + } else if (result == LinkResult.TOO_FAR) { + aPlayer.addChatMessage(new ChatComponentText("Link failed: Out of range.")); + } else if (result == LinkResult.NO_VALID_PLANT) { + aPlayer.addChatMessage(new ChatComponentText("Link failed: No Purification Plant found at link location")); + } + + return true; + } + + @Override + public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer) { + if (!(aPlayer instanceof EntityPlayerMP)) { + return false; + } + + // Right-clicking could be a data stick linking action, so try this first. + if (tryLinkDataStick(aPlayer)) { + return true; + } + + return super.onRightclick(aBaseMetaTileEntity, aPlayer); + } + + public GT_MetaTileEntity_PurificationPlant getController() { + if (controller == null) return null; + // Controller disappeared + if (controller.getBaseMetaTileEntity() == null) return null; + return controller; + } + + // If the controller is broken this can be called to explicitly unlink the controller, so we don't have any + // references lingering around + public void unlinkController() { + this.controllerSet = false; + this.controller = null; + this.controllerX = 0; + this.controllerY = 0; + this.controllerZ = 0; + } + + @Override + public void onBlockDestroyed() { + // When this block is destroyed, explicitly unlink it from the controller if there is any. + GT_MetaTileEntity_PurificationPlant controller = getController(); + if (controller != null) { + controller.unregisterLinkedUnit(this); + } + super.onBlockDestroyed(); + } + + @Override + public String[] getInfoData() { + var ret = new ArrayList<String>(); + // If this purification unit is linked to a controller, add this info to the scanner output. + if (getController() != null) { + ret.add( + "This Purification Unit is linked to the Water Purification Plant at " + controllerX + + ", " + + controllerY + + ", " + + controllerZ + + "."); + + // If recipe is running, display success chance + if (this.mMaxProgresstime != 0) { + ret.add( + "Success chance: " + EnumChatFormatting.YELLOW + + GT_Utility.formatNumbers(this.calculateFinalSuccessChance()) + + "%" + + EnumChatFormatting.RESET); + } + + } else ret.add("This Purification Unit is not linked to any Water Purification Plant."); + ret.add("Current parallel: " + EnumChatFormatting.YELLOW + this.effectiveParallel); + return ret.toArray(new String[0]); + } + + @Override + public void getWailaBody(ItemStack itemStack, List<String> currenttip, IWailaDataAccessor accessor, + IWailaConfigHandler config) { + NBTTagCompound tag = accessor.getNBTData(); + + // Display linked controller in Waila. + if (tag.getBoolean("linked")) { + currenttip.add( + EnumChatFormatting.AQUA + "Linked to Purification Plant at " + + EnumChatFormatting.WHITE + + tag.getInteger("controllerX") + + ", " + + tag.getInteger("controllerY") + + ", " + + tag.getInteger("controllerZ") + + EnumChatFormatting.RESET); + } else { + currenttip.add(EnumChatFormatting.AQUA + "Unlinked"); + } + + super.getWailaBody(itemStack, currenttip, accessor, config); + } + + @Override + public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y, + int z) { + + tag.setBoolean("linked", getController() != null); + if (getController() != null) { + tag.setInteger("controllerX", controllerX); + tag.setInteger("controllerY", controllerY); + tag.setInteger("controllerZ", controllerZ); + } + + super.getWailaNBTData(player, tile, tag, world, x, y, z); + } + + public PurificationUnitStatus status() { + if (!this.mMachine) { + return PurificationUnitStatus.INCOMPLETE_STRUCTURE; + } else if (!this.isAllowedToWork()) { + return PurificationUnitStatus.DISABLED; + } else { + return PurificationUnitStatus.ONLINE; + } + } + + /** + * Creates all widgets needed to sync this unit's status with the client + */ + public Widget makeSyncerWidgets() { + return new MultiChildWidget() + .addChild(new FakeSyncWidget.BooleanSyncer(() -> this.mMachine, machine -> this.mMachine = machine)) + .addChild(new FakeSyncWidget.BooleanSyncer(this::isAllowedToWork, _work -> {})); + } + + private static final int PARALLEL_WINDOW_ID = 10; + + @Override + public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { + buildContext.addSyncedWindow(PARALLEL_WINDOW_ID, this::createParallelWindow); + builder.widget(new ButtonWidget().setOnClick((clickData, widget) -> { + if (!widget.isClient()) { + widget.getContext() + .openSyncedWindow(PARALLEL_WINDOW_ID); + } + }) + .setPlayClickSound(true) + .setBackground(() -> { + List<UITexture> ret = new ArrayList<>(); + ret.add(GT_UITextures.BUTTON_STANDARD); + ret.add(GT_UITextures.OVERLAY_BUTTON_BATCH_MODE_ON); + return ret.toArray(new IDrawable[0]); + }) + .addTooltip(translateToLocal("GT5U.tpm.parallelwindow")) + .setTooltipShowUpDelay(TOOLTIP_DELAY) + .setPos(174, 129) + .setSize(16, 16)); + super.addUIWidgets(builder, buildContext); + } + + protected ModularWindow createParallelWindow(final EntityPlayer player) { + final int WIDTH = 158; + final int HEIGHT = 52; + final int PARENT_WIDTH = getGUIWidth(); + final int PARENT_HEIGHT = getGUIHeight(); + ModularWindow.Builder builder = ModularWindow.builder(WIDTH, HEIGHT); + builder.setBackground(GT_UITextures.BACKGROUND_SINGLEBLOCK_DEFAULT); + builder.setGuiTint(getGUIColorization()); + builder.setDraggable(true); + builder.setPos( + (size, window) -> Alignment.Center.getAlignedPos(size, new Size(PARENT_WIDTH, PARENT_HEIGHT)) + .add( + Alignment.BottomRight.getAlignedPos(new Size(PARENT_WIDTH, PARENT_HEIGHT), new Size(WIDTH, HEIGHT)) + .add(WIDTH - 3, 0) + .subtract(0, 10))); + builder.widget( + TextWidget.localised("GTPP.CC.parallel") + .setPos(3, 4) + .setSize(150, 20)) + .widget( + new NumericWidget().setSetter(val -> maxParallel = (int) val) + .setGetter(() -> maxParallel) + .setBounds(1, Integer.MAX_VALUE) + .setDefaultValue(1) + .setScrollValues(1, 4, 64) + .setTextAlignment(Alignment.Center) + .setTextColor(Color.WHITE.normal) + .setSize(150, 18) + .setPos(4, 25) + .setBackground(GT_UITextures.BACKGROUND_TEXT_FIELD) + .attachSyncer( + new FakeSyncWidget.IntegerSyncer(() -> maxParallel, (val) -> maxParallel = val), + builder)); + return builder.build(); + } + + @Override + public boolean supportsVoidProtection() { + return false; + } + + @Override + public Set<VoidingMode> getAllowedVoidingModes() { + return EnumSet.of(VoidingMode.VOID_NONE); + } + + @Override + protected boolean supportsCraftingMEBuffer() { + return false; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitClarifier.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitClarifier.java new file mode 100644 index 0000000000..fd97132ac1 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitClarifier.java @@ -0,0 +1,333 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.api.enums.GT_HatchElement.InputBus; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputBus; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_GLOW; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; + +public class GT_MetaTileEntity_PurificationUnitClarifier + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitClarifier> + implements ISurvivalConstructable { + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final String STRUCTURE_PIECE_MAIN_SURVIVAL = "main_survival"; + + private static final int STRUCTURE_X_OFFSET = 5; + private static final int STRUCTURE_Y_OFFSET = 2; + private static final int STRUCTURE_Z_OFFSET = 1; + + // Chance that the filter is damaged every cycle. + public static final float FILTER_DAMAGE_RATE = 20.0f; + + private static final int CASING_TEXTURE_INDEX = getTextureIndex(GregTech_API.sBlockCasings9, 5); + + private static final String[][] structure = + // spotless:off + new String[][] { + { " ", " ", " ", " " }, + { " ", " AAAAA ", " AH~HA ", " AAAAA " }, + { " ", " A A ", " AWWWWWA ", " AAAAAAA " }, + { " ", " A A ", " AWWWWWWWA ", " AAAAAAAAA " }, + { " ", "A A", "AWWWCCCWWWA", "AAAAFFFAAAA" }, + { " DDD ", "A A", "HWWCWWWCWWH", "AAAFFFFFAAA" }, + { "DDDDDBD ", "A B A", "AWWCWBWCWWA", "AAAFFFFFAAA" }, + { " DDD ", "A A", "HWWCWWWCWWH", "AAAFFFFFAAA" }, + { " ", "A A", "AWWWCCCWWWA", "AAAAFFFAAAA" }, + { " ", " A A ", " AWWWWWWWA ", " AAAAAAAAA " }, + { " ", " A A ", " AWWWWWA ", " AAAAAAA " }, + { " ", " AAAAA ", " AHAHA ", " AAAAA " } }; + // spotless:on + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitClarifier> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitClarifier>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + .addShape( + STRUCTURE_PIECE_MAIN_SURVIVAL, + Arrays.stream(structure) + .map( + sa -> Arrays.stream(sa) + .map(s -> s.replaceAll("W", " ")) + .toArray(String[]::new)) + .toArray(String[][]::new)) + // Hatches + .addElement( + 'H', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitClarifier>buildHatchAdder() + .atLeastList(t.getAllowedHatches()) + .casingIndex(CASING_TEXTURE_INDEX) + .dot(1) + .build()), + // Reinforced Sterile Water Plant Casing + ofBlock(GregTech_API.sBlockCasings9, 5))) + // Reinforced Sterile Water Plant Casing + .addElement('A', ofBlock(GregTech_API.sBlockCasings9, 5)) + // PTFE pipe casing + .addElement('B', ofBlock(GregTech_API.sBlockCasings8, 1)) + .addElement('C', ofFrame(Materials.Iridium)) + .addElement('D', ofFrame(Materials.DamascusSteel)) + .addElement('W', ofChain(ofBlock(Blocks.water, 0))) + // Filter machine casing + .addElement('F', ofBlock(GregTech_API.sBlockCasings3, 11)) + .build(); + + public GT_MetaTileEntity_PurificationUnitClarifier(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitClarifier(String aName) { + super(aName); + } + + @Override + public int getWaterTier() { + return 1; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_LuV; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationClarifierRecipes; + } + + @NotNull + @Override + public CheckRecipeResult overrideRecipeCheck() { + // Clarifier needs to check item inputs from recipe as well to find filter item + return findRecipeForInputs( + this.storedFluids.toArray(new FluidStack[] {}), + this.getStoredInputs() + .toArray(new ItemStack[] {})); + } + + @Override + public void depleteRecipeInputs() { + super.depleteRecipeInputs(); + + // Now do random roll to determine if the filter should be destroyed + ThreadLocalRandom random = ThreadLocalRandom.current(); + int roll = random.nextInt(1, 101); + if (roll < FILTER_DAMAGE_RATE) { + this.depleteInput(this.currentRecipe.mInputs[0]); + } + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitClarifier> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected IAlignmentLimits getInitialAlignmentLimits() { + // Rotated sifter not allowed, water will flow out. + return (d, r, f) -> d.offsetY == 0 && r.isNotRotated() && !f.isVerticallyFliped(); + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Clarifier Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo("Requires a filter made of Activated Carbon to work.") + .addInfo( + "Every cycle, has a " + EnumChatFormatting.RED + + GT_Utility.formatNumbers(FILTER_DAMAGE_RATE) + + "%" + + EnumChatFormatting.GRAY + + " chance to destroy the filter.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The first step to acquiring purified water is to filter out macroscopic contaminants through the") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "use of large physical filters. As more contaminants are captured, the efficacy of the filter") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "decreases so continual replacements must be supplied to maintain full function of the Clarifier.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(11, 4, 11, false) + .addSeparator() + .addCasingInfoRangeColored( + "Reinforced Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 123, + 131, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Filter Machine Casing", + EnumChatFormatting.GRAY, + 21, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Iridium Frame Box", + EnumChatFormatting.GRAY, + 21, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Damascus Steel Frame Box", + EnumChatFormatting.GRAY, + 12, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored("PTFE Pipe Casing", EnumChatFormatting.GRAY, 3, EnumChatFormatting.GOLD, false) + .addController("Front center") + .addInputBus(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addOutputBus(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addInputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addOutputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addStructureInfo("Requires water to be placed in the structure.") + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + int built = survivialBuildPiece( + STRUCTURE_PIECE_MAIN_SURVIVAL, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + if (built == -1) { + GT_Utility.sendChatToPlayer( + env.getActor(), + EnumChatFormatting.GREEN + "Auto placing done ! Now go place the water yourself !"); + return 0; + } + return built; + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitClarifier(this.mName); + } + + private List<IHatchElement<? super GT_MetaTileEntity_PurificationUnitClarifier>> getAllowedHatches() { + return ImmutableList.of(InputBus, InputHatch, OutputBus, OutputHatch); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection sideDirection, + ForgeDirection facingDirection, int colorIndex, boolean active, boolean redstoneLevel) { + if (sideDirection == facingDirection) { + if (active) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_TEXTURE_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_TEXTURE_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_DISTILLATION_TOWER_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_TEXTURE_INDEX) }; + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.GT_MACHINES_PURIFICATIONPLANT_LOOP.resourceLocation; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitDegasifier.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitDegasifier.java new file mode 100644 index 0000000000..d0257ca3c2 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitDegasifier.java @@ -0,0 +1,838 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import com.github.bartimaeusnek.bartworks.system.material.WerkstoffLoader; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_MultiInput; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.IGT_HatchAdder; + +public class GT_MetaTileEntity_PurificationUnitDegasifier + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitDegasifier> + implements ISurvivalConstructable { + + private static final int CASING_INDEX_MAIN = getTextureIndex(GregTech_API.sBlockCasings9, 11); + + private static final String STRUCTURE_PIECE_MAIN = "main"; + + // spotless:off + private static final String[][] structure = new String[][] { + { " AAAAA ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " AAAAA " }, + { " AAAAAAAAA ", " AAAAA ", " ", " C ", " CC ", " C ", " CC ", " C ", " ", " ", " ", " C ", " CC ", " C ", " CC ", " C ", " ", " ", " ", " C ", " CC ", " C ", " CC ", " CCAA~AA ", " AAAAAAAAA " }, + { " AAAAAAAAAAAAA ", " CAAAAAAAAA ", " BBBBB CC ", " BBBBB ", " BBBBB ", " BBB ", " B ", " ", " C ", " ", " C ", " ", " ", " ", " ", " ", " C ", " ", " B C ", " BBB ", " BBBBB ", " BBBBB ", " BBBBB ", " AAAAAAAAA ", " AAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAA ", " AAAAAAAAAAAC ", " C BBB BB ", " BB BB ", " B B ", " BB BB ", " BBB BBB ", " BBBBB ", " BBB ", " C C ", " ", " ", " ", " ", " ", " ", " BBB ", " C BBBBB C ", " BBB BBB ", " BB BB ", " B B ", " BB BB ", " BB BB ", " AAAAAAAAAAA ", " AAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAAAA ", " AAAAAAAAAAAAA ", " CB B ", " B B ", " B B ", " B B ", " B B ", " BB BB ", " BB BB C ", " BBBBB ", " C BBB ", " ", " ", " ", " BBB ", " BBBBB ", " BB BB C ", " BB BB ", " C B B ", " B B ", " B B ", " B B ", " B B ", " AAAAAAAAAAAAAC ", " AAAAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAAAA ", " AAAAAAAAAAAAA ", " B B ", " C B B ", " B B ", " B B ", " B B ", " B B C ", " B B ", " B B ", " B B ", " C BBB ", " ", " BBB ", " B B ", " B B C ", " B B ", " B B ", " B B ", " C B B ", " B B ", " B B ", " B B ", " AAAAAAAAAAAAAC ", " AAAAAAAAAAAAAAA " }, + { "AAAAAAAAAAAAAAAAA", " AAAAAAAAAAAAAAA ", " B B ", " B B ", " CB B ", " B B ", " B B C ", " B B ", " B B ", " B B ", " B B ", " BBBBB ", " C BBB ", " BBBBB ", " B B C ", " B B ", " B B ", " B B ", " B B ", " B B ", " CB B ", " B B ", " B BC ", " AAAAAAAAAAAAAAA ", "AAAAAAAAAAAAAAAAA" }, + { "AAAAAAAAAAAAAAAAA", " AAAAAAAAAAAAAAA ", " B B ", " B B ", " CB B ", " B B ", " B B C ", " B B ", " B B ", " B B ", " B B ", " BB BB ", " C BBBBB ", " BB BB ", " B B C ", " B B ", " B B ", " B B ", " B B ", " B B ", " CB B ", " B B ", " B BC ", " AAAAAAAAAAAAAAA ", "AAAAAAAAAAAAAAAAA" }, + { "AAAAAAAAAAAAAAAAA", " AAAAAAAAAAAAAAA ", " B B ", " B B ", " B B ", " CB BC ", " B B ", " B B ", " B B ", " B B ", " B B ", " BB BB ", " BBBBB ", " C BB BB C ", " B B ", " B B ", " B B ", " B B ", " B B ", " B B ", " B B ", " CB BC ", " B B ", " AAAAAAAAAAAAAAA ", "AAAAAAAAAAAAAAAAA" }, + { "AAAAAAAAAAAAAAAAA", " AAAAAAAAAAAAAAA ", " B B ", " B B ", " B BC ", " B B ", " C B B ", " B B ", " B B ", " B B ", " B B ", " BB BB ", " BBBBB C ", " BB BB ", " C B B ", " B B ", " B B ", " B B ", " B B ", " B B ", " B BC ", " B B ", " CB B ", " AAAAAAAAAAAAAAA ", "AAAAAAAAAAAAAAAAA" }, + { "AAAAAAAAAAAAAAAAA", " AAAAAAAAAAAAAAA ", " B B ", " B B ", " B BC ", " B B ", " C B B ", " B B ", " B B ", " B B ", " B B ", " BBBBB ", " BBB C ", " BBBBB ", " C B B ", " B B ", " B B ", " B B ", " B B ", " B B ", " B BC ", " B B ", " CB B ", " AAAAAAAAAAAAAAA ", "AAAAAAAAAAAAAAAAA" }, + { " AAAAAAAAAAAAAAA ", " AAAAAAAAAAAAA ", " B B ", " B B C ", " B B ", " B B ", " B B ", " C B B ", " B B ", " B B ", " B B ", " BBB C ", " ", " BBB ", " B B ", " C B B ", " B B ", " B B ", " B B ", " B B C ", " B B ", " B B ", " B B ", " CAAAAAAAAAAAAA ", " AAAAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAAAA ", " AAAAAAAAAAAAA ", " B BC ", " B B ", " B B ", " B B ", " B B ", " BB BB ", " C BB BB ", " BBBBB ", " BBB C ", " ", " ", " ", " BBB ", " BBBBB ", " C BB BB ", " BB BB ", " B B C ", " B B ", " B B ", " B B ", " B B ", " CAAAAAAAAAAAAA ", " AAAAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAA ", " CAAAAAAAAAAA ", " BB BB C ", " BB BB ", " B B ", " BB BB ", " BBB BBB ", " BBBBB ", " BBB ", " C C ", " ", " ", " ", " ", " ", " ", " BBB ", " C BBBBB C ", " BBB BBB ", " BB BB ", " B B ", " BB BB ", " BB BB ", " AAAAAAAAAAA ", " AAAAAAAAAAAAA " }, + { " AAAAAAAAAAAAA ", " AAAAAAAAAC ", " CC BBBBB ", " BBBBB ", " BBBBB ", " BBB ", " B ", " ", " C ", " ", " C ", " ", " ", " ", " ", " ", " C ", " ", " C B ", " BBB ", " BBBBB ", " BBBBB ", " BBBBB ", " AAAAAAAAA ", " AAAAAAAAAAAAA " }, + { " AAAAAAAAA ", " AAAAA ", " ", " C ", " CC ", " C ", " CC ", " C ", " ", " ", " ", " C ", " CC ", " C ", " CC ", " C ", " ", " ", " ", " C ", " CC ", " C ", " CC ", " AAAAACC ", " AAAAAAAAA " }, + { " AAAAA ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " AAAAA " } }; + // spotless:on + + private int casingCount = 0; + private static final int MIN_CASING = 780; + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitDegasifier> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitDegasifier>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + .addElement( + 'A', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitDegasifier>buildHatchAdder() + .atLeastList(Arrays.asList(InputHatch, OutputHatch, SpecialHatchElement.ControlHatch)) + .casingIndex(CASING_INDEX_MAIN) + .dot(1) + .cacheHint(() -> "Input Hatch, Output Hatch, Control Hatch") + .build()), + onElementPass(t -> t.casingCount++, ofBlock(GregTech_API.sBlockCasings9, 11)))) + // Omni-purpose infinity fused glass + .addElement('B', ofBlock(GregTech_API.sBlockGlass1, 2)) + .addElement('C', ofFrame(Materials.Bedrockium)) + .build(); + + private static final int STRUCTURE_X_OFFSET = 8; + private static final int STRUCTURE_Y_OFFSET = 23; + private static final int STRUCTURE_Z_OFFSET = 1; + + // Supplier because werkstoff loads later than multiblock controllers... fml + private static final Supplier<FluidStack[]> INERT_GASES = () -> new FluidStack[] { Materials.Helium.getGas(10000L), + WerkstoffLoader.Neon.getFluidOrGas(7500), WerkstoffLoader.Krypton.getFluidOrGas(5000), + WerkstoffLoader.Xenon.getFluidOrGas(2500) }; + + private static final class SuperconductorMaterial { + + public FluidStack fluid; + public float multiplier; + + SuperconductorMaterial(FluidStack fluid, float multiplier) { + this.fluid = fluid; + this.multiplier = multiplier; + } + } + + private static final long SUPERCON_FLUID_AMOUNT = 1440L; + + private static final Supplier<SuperconductorMaterial[]> SUPERCONDUCTOR_MATERIALS = () -> new SuperconductorMaterial[] { + new SuperconductorMaterial(Materials.Longasssuperconductornameforuvwire.getMolten(SUPERCON_FLUID_AMOUNT), 1.0f), + new SuperconductorMaterial( + Materials.Longasssuperconductornameforuhvwire.getMolten(SUPERCON_FLUID_AMOUNT), + 1.25f), + new SuperconductorMaterial(Materials.SuperconductorUEVBase.getMolten(SUPERCON_FLUID_AMOUNT), 1.5f), + new SuperconductorMaterial(Materials.SuperconductorUIVBase.getMolten(SUPERCON_FLUID_AMOUNT), 1.75f), + new SuperconductorMaterial(Materials.SuperconductorUMVBase.getMolten(SUPERCON_FLUID_AMOUNT), 2.0f), }; + + private static final FluidStack CATALYST_FLUID = Materials.Neutronium.getMolten(4608L); + private static final FluidStack COOLANT_FLUID = Materials.SuperCoolant.getFluid(10000L); + + private static final long CONSUME_INTERVAL = 20; + + private static class ControlSignal { + + private byte signal; + + public ControlSignal(byte sig) { + signal = sig; + } + + public void randomize() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + // We want to give the final bit a lower chance at being on, since this bypasses most of the automation. + // If this bit has a 50% chance of being on, you could opt to not automate the degasser at all and never + // insert anything. This way you still get 50% output which might just be good enough. + + // To do this weighting, we simply only generate the lower 3 bits, and then with a smaller chance we add + // 8 to the result + signal = (byte) random.nextInt(0, 8); + if (random.nextInt(0, 5) == 0) { + signal += 8; + } + } + + public boolean getBit(int bit) { + if (bit < 0 || bit > 3) { + throw new IllegalArgumentException("Invalid bit index for degasser control signal"); + } + + // Shift signal so the requested bit is in the lowermost bit + // Then only keep the lowermost bit + // Then test if this bit is on. + return ((signal >> bit) & 1) == 1; + } + + public byte getSignal() { + return signal; + } + + // Get integer value representing control bits 1 and 2 + public int getControlBit12() { + return (signal >> 1) & 0b11; + } + + public boolean isZero() { + return signal == (byte) 0; + } + + @Override + public String toString() { + return Integer.toBinaryString(((int) signal) & 0b1111); + } + } + + private static class ControlBitStatus { + + public FluidStack stack; + public boolean satisfied; + + public ControlBitStatus(FluidStack stack, boolean satisfied) { + this.stack = stack; + this.satisfied = satisfied; + } + } + + private ControlSignal controlSignal = new ControlSignal((byte) 0); + + private final HashMap<Fluid, FluidStack> insertedStuffThisCycle = new HashMap<>(); + + private float outputMultiplier = 1.0f; + + private GT_MetaTileEntity_Hatch_DegasifierControlHatch controlHatch = null; + + public GT_MetaTileEntity_PurificationUnitDegasifier(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + protected GT_MetaTileEntity_PurificationUnitDegasifier(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitDegasifier(mName); + } + + @Override + protected void setHatchRecipeMap(GT_MetaTileEntity_Hatch_Input hatch) { + // Do nothing, we don't want to lock hatches to recipe maps since this can cause + // them to reject our catalyst fluids + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection side, ForgeDirection facing, + int colorIndex, boolean active, boolean redstoneLevel) { + if (side == facing) { + if (active) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitDegasifier> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Residual Decontaminant Degasser Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "At the start of the operation, the " + EnumChatFormatting.WHITE + + "Degasser Control Hatch" + + EnumChatFormatting.GRAY + + " will output a redstone signal.") + .addInfo("To succeed the recipe, you will need to successfully decode the instructions in the signal.") + .addInfo("To decode the signal, interpret the signal strength as a 4-bit number from 0-15.") + .addInfo("Denote the lowest bit as bit 1, and the highest as bit 4.") + .addSeparator() + .addInfo( + EnumChatFormatting.WHITE + "" + + EnumChatFormatting.BOLD + + "Bit 1: " + + EnumChatFormatting.BLUE + + "" + + EnumChatFormatting.BOLD + + "Ozone Sparging by Inert Gas") + .addInfo( + "If this bit is on, you must insert an " + EnumChatFormatting.WHITE + + "inert gas" + + EnumChatFormatting.GRAY + + " into the machine.") + .addInfo( + "To determine which gas to insert, interpret bits " + EnumChatFormatting.WHITE + + "2-3" + + EnumChatFormatting.GRAY + + " as a 2-bit number.") + .addInfo( + EnumChatFormatting.GRAY + "0: " + + EnumChatFormatting.RED + + "10000L " + + EnumChatFormatting.WHITE + + "Helium" + + EnumChatFormatting.GRAY + + " / " + + "1: " + + EnumChatFormatting.RED + + "7500L " + + EnumChatFormatting.WHITE + + "Neon" + + EnumChatFormatting.GRAY + + " / " + + "2: " + + EnumChatFormatting.RED + + "5000L " + + EnumChatFormatting.WHITE + + "Krypton" + + EnumChatFormatting.GRAY + + " / " + + "3: " + + EnumChatFormatting.RED + + "2500L " + + EnumChatFormatting.WHITE + + "Xenon") + .addSeparator() + .addInfo( + EnumChatFormatting.WHITE + "" + + EnumChatFormatting.BOLD + + "Bit 2: " + + EnumChatFormatting.BLUE + + "" + + EnumChatFormatting.BOLD + + "Superconductive Deionization") + .addInfo( + "If this bit is on, you must insert " + EnumChatFormatting.RED + + "1440L " + + EnumChatFormatting.WHITE + + "Molten Superconductor Base.") + .addInfo("Using higher tier superconductor provides bonus output.") + .addInfo( + "Output multiplier: " + EnumChatFormatting.DARK_GREEN + + "UV" + + EnumChatFormatting.GRAY + + ": " + + EnumChatFormatting.WHITE + + "1x" + + EnumChatFormatting.GRAY + + " / " + + EnumChatFormatting.DARK_RED + + "UHV" + + EnumChatFormatting.GRAY + + ": " + + EnumChatFormatting.WHITE + + "1.25x" + + EnumChatFormatting.GRAY + + " / " + + EnumChatFormatting.DARK_PURPLE + + "UEV" + + EnumChatFormatting.GRAY + + ": " + + EnumChatFormatting.WHITE + + "1.5x" + + EnumChatFormatting.GRAY + + " / " + + EnumChatFormatting.DARK_BLUE + + "" + + EnumChatFormatting.BOLD + + "UIV" + + EnumChatFormatting.GRAY + + ": " + + EnumChatFormatting.WHITE + + "1.75x" + + EnumChatFormatting.GRAY + + " / " + + EnumChatFormatting.RED + + "" + + EnumChatFormatting.BOLD + + "UMV" + + EnumChatFormatting.GRAY + + ": " + + EnumChatFormatting.WHITE + + "2x") + .addSeparator() + .addInfo( + EnumChatFormatting.WHITE + "" + + EnumChatFormatting.BOLD + + "Bit 3: " + + EnumChatFormatting.BLUE + + "" + + EnumChatFormatting.BOLD + + "Gravitationally-Generated Differential Vacuum Extraction") + .addInfo( + "If this bit is on, you must insert " + EnumChatFormatting.RED + + "4608L " + + EnumChatFormatting.WHITE + + "Molten Neutronium") + .addSeparator() + .addInfo( + EnumChatFormatting.WHITE + "" + + EnumChatFormatting.BOLD + + "Bit 4: " + + EnumChatFormatting.BLUE + + "" + + EnumChatFormatting.BOLD + + "Seldonian Settlement Process") + .addInfo( + "If this bit is on," + EnumChatFormatting.RED + + " DISREGARD " + + EnumChatFormatting.GRAY + + "all other bits and do not insert anything.") + .addSeparator() + .addInfo( + EnumChatFormatting.WHITE + "" + + EnumChatFormatting.BOLD + + "No bits: " + + EnumChatFormatting.BLUE + + "" + + EnumChatFormatting.BOLD + + "Machine Overload") + .addInfo("In rare cases, the machine may overload and output no control signal at all.") + .addInfo( + "To prevent machine damage, insert " + EnumChatFormatting.RED + + "10000L " + + EnumChatFormatting.WHITE + + "Super Coolant.") + .addSeparator() + .addInfo("The recipe can only succeed if the entire signal is decoded correctly.") + .addInfo("Inserting any fluid not requested by the signal will always void the recipe.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The penultimate stage of water purification, step seven, is an irregular series of complex") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "processes designed to remove any residual materials left by the decontaminants from the previous") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "steps such as any energetic ions, acids, clarifiers, or gasses. Depending on what the Degasser") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "detects in the water, it will request various materials to complete the processes listed above.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(17, 25, 17, false) + .addCasingInfoRangeColored( + "Heat-Resistant Trinium Plated Casing", + EnumChatFormatting.GRAY, + MIN_CASING, + 803, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Omni-Purpose Infinity Fused Glass", + EnumChatFormatting.GRAY, + 622, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Bedrockium Frame Box", + EnumChatFormatting.GRAY, + 126, + EnumChatFormatting.GOLD, + false) + .addController("Front center") + .addOutputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+, Any Trinium Casing", 1) + .addInputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+, Any Trinium Casing", 1) + .addOtherStructurePart( + "Degasser Control Hatch", + EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + ", Any Trinium Casing", + 1) + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationDegasifierRecipes; + } + + // Whether this fluid stack is accepted as a fluid in the inputs that the unit requires at runtime. + public static boolean isValidFluid(FluidStack stack) { + return stack.isFluidEqual(CATALYST_FLUID) || stack.isFluidEqual(COOLANT_FLUID) + || Arrays.stream(INERT_GASES.get()) + .anyMatch(stack::isFluidEqual) + || Arrays.stream(SUPERCONDUCTOR_MATERIALS.get()) + .anyMatch(mat -> stack.isFluidEqual(mat.fluid)); + } + + // Check if an exact match for this FluidStack was found in the map of inserted fluids + private boolean wasFluidInsertedExact(FluidStack toFind) { + FluidStack candidate = insertedStuffThisCycle.get(toFind.getFluid()); + // Fluid was inserted if found and the amount matches + return candidate != null && candidate.amount == toFind.amount; + } + + private ControlBitStatus isBit0Satisfied() { + // Check if instructions matching the first bit are satisfied. + // Instructions: + // If bit 0 is on, insert an inert gas. Bits 1-2 of the control signal determine which inert + // gas to insert. + + if (controlSignal.getBit(0)) { + // Grab the gas to insert from the control bits + int gasToInsert = controlSignal.getControlBit12(); + FluidStack gasStack = INERT_GASES.get()[gasToInsert]; + // Check if it was inserted + if (wasFluidInsertedExact(gasStack)) return new ControlBitStatus(gasStack, true); + else return new ControlBitStatus(null, false); + } + + // Bit 0 is not on, so this is always satisfied + return new ControlBitStatus(null, true); + } + + private ControlBitStatus isBit1Satisfied() { + // Check if instructions matching the second bit (bit 1) are satisfied. + // Instructions: + // If bit 1 is on, insert molten superconductor. + // Higher tier superconductor gives a better bonus. + // Only one type of superconductor may be inserted or the operation fails, + // so we don't care about the order in which we find it. + if (controlSignal.getBit(1)) { + // Find the first superconductor material in the list that was inserted with an exact match + Optional<SuperconductorMaterial> material = Arrays.stream(SUPERCONDUCTOR_MATERIALS.get()) + .filter(candidate -> wasFluidInsertedExact(candidate.fluid)) + .findFirst(); + if (material.isPresent()) { + // Get the material and set the output multiplier, then + // report success with the matching fluid. + SuperconductorMaterial scMaterial = material.get(); + this.outputMultiplier = scMaterial.multiplier; + return new ControlBitStatus(scMaterial.fluid, true); + } + // No superconductor was inserted but bit is on fail. + return new ControlBitStatus(null, false); + } + + return new ControlBitStatus(null, true); + } + + private ControlBitStatus isBit2Satisfied() { + // Check if instructions matching the third bit (bit 2) are satisfied. + // Instructions: + // If bit 2 is on, insert molten neutronium. + if (controlSignal.getBit(2)) { + // If steel was inserted, return it and report success. + if (wasFluidInsertedExact(CATALYST_FLUID)) return new ControlBitStatus(CATALYST_FLUID, true); + // Otherwise report failure. + return new ControlBitStatus(null, false); + } + + return new ControlBitStatus(null, true); + } + + private ControlBitStatus isBit3Satisfied() { + // Check if instructions matching the fourth bit (bit 3) are satisfied. + // Instructions: + // If bit 3 is on, do not insert anything. + if (controlSignal.getBit(3)) { + // Simply check if the map of inserted fluids is empty + if (insertedStuffThisCycle.isEmpty()) return new ControlBitStatus(null, true); + return new ControlBitStatus(null, false); + } + return new ControlBitStatus(null, true); + } + + private boolean areAllBitsSatisfied() { + // Check if each individual bit is satisfied. + // Additional instructions: If no bits are on, insert super coolant + + if (controlSignal.isZero()) { + return wasFluidInsertedExact(COOLANT_FLUID); + } + + ControlBitStatus bit0 = isBit0Satisfied(); + ControlBitStatus bit1 = isBit1Satisfied(); + ControlBitStatus bit2 = isBit2Satisfied(); + ControlBitStatus bit3 = isBit3Satisfied(); + + // If bit 3 is satisfied and on, all other bits are automatically satisfied, + // with no fluids being allowed to be inserted. + if (controlSignal.getBit(3) && bit3.satisfied) { + bit0 = bit1 = bit2 = new ControlBitStatus(null, true); + } + + if (bit0.satisfied && bit1.satisfied && bit2.satisfied && bit3.satisfied) { + // Check if the map contains any other stacks than the ones in the control bit statuses + for (FluidStack inserted : insertedStuffThisCycle.values()) { + // If the inserted stack is null, or any of the fluids required, this stack is fine. + if (inserted == null) continue; + if (bit0.stack != null && inserted.isFluidEqual(bit0.stack)) continue; + if (bit1.stack != null && inserted.isFluidEqual(bit1.stack)) continue; + if (bit2.stack != null && inserted.isFluidEqual(bit2.stack)) continue; + if (bit3.stack != null && inserted.isFluidEqual(bit3.stack)) continue; + // Otherwise it's a nonrequested stack and the recipe should fail. + return false; + } + return true; + } + + return false; + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + this.controlSignal.randomize(); + this.insertedStuffThisCycle.clear(); + this.outputMultiplier = 1.0f; + // Make sure to output the hatch control signal. + this.controlHatch.updateOutputSignal(this.controlSignal.getSignal()); + } + + private static ArrayList<FluidStack> getDrainableFluidsFromHatch(GT_MetaTileEntity_Hatch_Input hatch) { + // Need special handling for quad input hatches, otherwise it only returns the first fluid in the hatch + if (hatch instanceof GT_MetaTileEntity_Hatch_MultiInput) { + return new ArrayList<>(Arrays.asList(((GT_MetaTileEntity_Hatch_MultiInput) hatch).getStoredFluid())); + } + return new ArrayList<>(Collections.singletonList(hatch.getFluid())); + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + + // If machine is running, continuously consume all valid inputs + if (mMaxProgresstime > 0 && aTick % CONSUME_INTERVAL == 0) { + // For each hatch, check if each fluid inside is one of the valid fluids. If so, consume it all. + for (GT_MetaTileEntity_Hatch_Input hatch : mInputHatches) { + ArrayList<FluidStack> drainableFluids = getDrainableFluidsFromHatch(hatch); + for (FluidStack fluid : drainableFluids) { + if (fluid != null && isValidFluid(fluid)) { + // Apparently this parameter is mostly ignored, but might as well get it right. + ForgeDirection front = hatch.getBaseMetaTileEntity() + .getFrontFacing(); + // Drain the fluid and save it + FluidStack drainedFluid = hatch.drain(front, fluid, true); + // If the fluid does not yet exist in the map, insert it. + // Otherwise, merge the amounts + insertedStuffThisCycle.merge( + fluid.getFluid(), + drainedFluid, + (a, b) -> new FluidStack(a.getFluid(), a.amount + b.amount)); + } + } + } + } + } + + @Override + public void addRecipeOutputs() { + super.addRecipeOutputs(); + if (outputMultiplier > 1.01f) { + FluidStack waterOutput = currentRecipe.mFluidOutputs[0]; + FluidStack bonusOutput = new FluidStack( + waterOutput.getFluid(), + (int) (waterOutput.amount * (outputMultiplier - 1.0f))); + this.addOutput(bonusOutput); + } + } + + @Override + public float calculateFinalSuccessChance() { + // Success chance is 100% when all bits are satisfied, 0% otherwise. + if (areAllBitsSatisfied()) { + return 100.0f; + } else { + return 0.0f; + } + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 7; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_UHV; + } + + public boolean addControlHatchToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity instanceof GT_MetaTileEntity_Hatch_DegasifierControlHatch) { + // Only allow a single control hatch, so fail structure check if there is already one + if (this.controlHatch == null) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + this.controlHatch = (GT_MetaTileEntity_Hatch_DegasifierControlHatch) aMetaTileEntity; + return true; + } + } + return false; + } + + @Override + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + this.casingCount = 0; + this.controlHatch = null; + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + if (casingCount < MIN_CASING) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + controlSignal = new ControlSignal(aNBT.getByte("controlSignal")); + outputMultiplier = aNBT.getFloat("outputMultiplier"); + NBTTagCompound fluidMap = aNBT.getCompoundTag("insertedFluidMap"); + for (String key : fluidMap.func_150296_c()) { + FluidStack fluid = FluidStack.loadFluidStackFromNBT(fluidMap.getCompoundTag(key)); + // Ignore if fluid failed to load, for example if the fluid ID was changed between versions + if (fluid == null) { + continue; + } + insertedStuffThisCycle.put(fluid.getFluid(), fluid); + } + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + aNBT.setByte("controlSignal", controlSignal.getSignal()); + aNBT.setFloat("outputMultiplier", outputMultiplier); + NBTTagCompound fluidMap = new NBTTagCompound(); + for (FluidStack stack : insertedStuffThisCycle.values()) { + NBTTagCompound compound = new NBTTagCompound(); + stack.writeToNBT(compound); + fluidMap.setTag( + stack.getFluid() + .getName(), + compound); + } + aNBT.setTag("insertedFluidMap", fluidMap); + } + + private static String generateInfoStringForBit(int i, ControlBitStatus status) { + String base = "Bit " + (i + 1) + " status: "; + if (status.satisfied) { + return base + EnumChatFormatting.GREEN + "OK"; + } else { + return base + EnumChatFormatting.RED + "NOT OK"; + } + } + + @Override + public String[] getInfoData() { + ArrayList<String> info = new ArrayList<>(Arrays.asList(super.getInfoData())); + info.add("Current control signal: " + EnumChatFormatting.YELLOW + controlSignal.toString()); + info.add("Current output multiplier: " + EnumChatFormatting.YELLOW + outputMultiplier); + for (FluidStack stack : insertedStuffThisCycle.values()) { + info.add( + "Fluid inserted this cycle: " + EnumChatFormatting.YELLOW + + stack.amount + + "L " + + stack.getLocalizedName()); + } + info.add(generateInfoStringForBit(0, isBit0Satisfied())); + info.add(generateInfoStringForBit(1, isBit1Satisfied())); + info.add(generateInfoStringForBit(2, isBit2Satisfied())); + info.add(generateInfoStringForBit(3, isBit3Satisfied())); + return info.toArray(new String[] {}); + } + + private enum SpecialHatchElement implements IHatchElement<GT_MetaTileEntity_PurificationUnitDegasifier> { + + ControlHatch(GT_MetaTileEntity_PurificationUnitDegasifier::addControlHatchToMachineList, + GT_MetaTileEntity_Hatch_DegasifierControlHatch.class) { + + @Override + public long count(GT_MetaTileEntity_PurificationUnitDegasifier mte) { + return mte.controlHatch == null ? 0 : 1; + } + }; + + private final List<Class<? extends IMetaTileEntity>> mteClasses; + private final IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitDegasifier> adder; + + @SafeVarargs + SpecialHatchElement(IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitDegasifier> adder, + Class<? extends IMetaTileEntity>... mteClasses) { + this.mteClasses = Collections.unmodifiableList(Arrays.asList(mteClasses)); + this.adder = adder; + } + + @Override + public List<? extends Class<? extends IMetaTileEntity>> mteClasses() { + return mteClasses; + } + + public IGT_HatchAdder<? super GT_MetaTileEntity_PurificationUnitDegasifier> adder() { + return adder; + } + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitFlocculation.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitFlocculation.java new file mode 100644 index 0000000000..8090b32f30 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitFlocculation.java @@ -0,0 +1,496 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockAnyMeta; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass; +import static gregtech.api.enums.GT_HatchElement.InputBus; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputBus; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.util.GT_RecipeBuilder.SECONDS; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.shutdown.ShutDownReasonRegistry; + +public class GT_MetaTileEntity_PurificationUnitFlocculation + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitFlocculation> + implements ISurvivalConstructable { + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final String STRUCTURE_PIECE_MAIN_SURVIVAL = "main_survival"; + + private static final int STRUCTURE_X_OFFSET = 4; + private static final int STRUCTURE_Y_OFFSET = 3; + private static final int STRUCTURE_Z_OFFSET = 0; + + /** + * Amount of input fluid needed to boost the success chance by another level + */ + public static final long INPUT_CHEMICAL_PER_LEVEL = 100000; + /** + * Amount of waste water produced for each success chance level. This matches the amount of input fluid + * so it can be perfectly recycled into each other. + */ + private static final long WASTE_WATER_PER_LEVEL = INPUT_CHEMICAL_PER_LEVEL; + /** + * Additive boost to success chance for each level of input fluid supplied + */ + public static final float SUCCESS_PER_LEVEL = 10.0f; + /** + * Amount of ticks between each tick the unit will try to consume input fluid + */ + private static final int CONSUME_INTERVAL = 1 * SECONDS; + + /** + * Fluid that needs to be supplied to boost success chance + */ + private static final Materials INPUT_CHEMICAL = Materials.PolyAluminiumChloride; + /** + * Output fluid to be produced as waste. The intended behaviour is that this output fluid can be cycled + * semi-perfectly into the input fluid. + */ + private static final Materials OUTPUT_WASTE = Materials.FlocculationWasteLiquid; + + /** + * Total amount of input fluid consumed during this recipe cycle. + */ + private long inputFluidConsumed = 0; + + private static final String[][] structure = new String[][] + // spotless:off + { + { " ", " ", " BBBBBBB ", " BBB~BBB ", " BBBBBBB " }, + { " ", " ", " B B ", " BWWWWWB ", " BCCCCCB " }, + { " ", " ", " B B ", " GWWWWWG ", " BCAAACB " }, + { " ", " ", " B B ", " GWWWWWG ", " BCAAACB " }, + { " ", " ", " B B ", " GWWWWWG ", " BCAAACB " }, + { " ", " EE EE ", " BE EB ", " BEWWWEB ", " BCCCCCB " }, + { "D D", "DEE EED", "DBBBBBBBD", "DBBBBBBBD", "DBBBBBBBD" }, + { "DD DD", "DD DD", "DD DD", "DD DD", "DD DD" } + }; + // spotless:on + + private static final int MAIN_CASING_INDEX = getTextureIndex(GregTech_API.sBlockCasings9, 6); + + private int casingCount = 0; + private static final int MIN_CASING = 56; + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitFlocculation> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitFlocculation>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + .addShape( + STRUCTURE_PIECE_MAIN_SURVIVAL, + Arrays.stream(structure) + .map( + sa -> Arrays.stream(sa) + .map(s -> s.replaceAll("W", " ")) + .toArray(String[]::new)) + .toArray(String[][]::new)) + // Filter machine casing + .addElement('A', ofBlock(GregTech_API.sBlockCasings3, 11)) + .addElement( + 'B', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitFlocculation>buildHatchAdder() + .atLeastList(t.getAllowedHatches()) + .casingIndex(MAIN_CASING_INDEX) + .dot(1) + .build()), + // Clean Flocculation Casing + onElementPass(t -> t.casingCount++, ofBlock(GregTech_API.sBlockCasings9, 6)))) + // Reinforced Sterile Water Plant Casing + .addElement('C', ofBlock(GregTech_API.sBlockCasings9, 5)) + // Sterile Water Plant Casing + .addElement('D', ofBlock(GregTech_API.sBlockCasings9, 4)) + .addElement('E', ofFrame(Materials.Adamantium)) + .addElement('W', ofBlock(Blocks.water, 0)) + // Tinted industrial glass + .addElement('G', ofBlockAnyMeta(GregTech_API.sBlockTintedGlass)) + .build(); + + List<IHatchElement<? super GT_MetaTileEntity_PurificationUnitFlocculation>> getAllowedHatches() { + return ImmutableList.of(InputBus, InputHatch, OutputBus, OutputHatch); + } + + public GT_MetaTileEntity_PurificationUnitFlocculation(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitFlocculation(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitFlocculation(this.mName); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection aFacing, + int colorIndex, boolean aActive, boolean redstoneLevel) { + if (side == aFacing) { + if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + int built = survivialBuildPiece( + STRUCTURE_PIECE_MAIN_SURVIVAL, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + if (built == -1) { + GT_Utility.sendChatToPlayer( + env.getActor(), + EnumChatFormatting.GREEN + "Auto placing done ! Now go place the water yourself !"); + return 0; + } + return built; + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitFlocculation> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected IAlignmentLimits getInitialAlignmentLimits() { + // Do not allow rotation when water would flow out + return (d, r, f) -> d.offsetY == 0 && r.isNotRotated() && !f.isVerticallyFliped(); + } + + @Override + protected void setHatchRecipeMap(GT_MetaTileEntity_Hatch_Input hatch) { + // Do nothing, we don't want to lock hatches to recipe maps since this can cause + // them to reject our catalyst fluids + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + casingCount = 0; + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + + // At most two input hatches allowed + if (mInputHatches.size() > 2) { + return false; + } + + // At most two output hatches allowed + if (mOutputHatches.size() > 2) { + return false; + } + + if (casingCount < MIN_CASING) return false; + + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Flocculation Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "Supply with " + EnumChatFormatting.WHITE + + INPUT_CHEMICAL.mLocalizedName + + EnumChatFormatting.GRAY + + " to operate.") + .addInfo( + "Outputs " + EnumChatFormatting.WHITE + + OUTPUT_WASTE.mLocalizedName + + EnumChatFormatting.GRAY + + " that can be recycled.") + .addSeparator() + .addInfo( + "During operation, will consume ALL " + EnumChatFormatting.WHITE + + INPUT_CHEMICAL.mLocalizedName + + EnumChatFormatting.GRAY + + " in the input hatch.") + .addInfo( + "At the end of the recipe, for every " + EnumChatFormatting.RED + + INPUT_CHEMICAL_PER_LEVEL + + "L " + + EnumChatFormatting.GRAY + + "of " + + EnumChatFormatting.WHITE + + INPUT_CHEMICAL.mLocalizedName + + EnumChatFormatting.GRAY + + " consumed") + .addInfo( + "gain an additive " + EnumChatFormatting.RED + + SUCCESS_PER_LEVEL + + "%" + + EnumChatFormatting.GRAY + + " increase to success. If total fluid supplied is not") + .addInfo( + "a multiple of " + EnumChatFormatting.RED + + INPUT_CHEMICAL_PER_LEVEL + + "L" + + EnumChatFormatting.GRAY + + ", a penalty to success is applied using the following formula:") + .addInfo(EnumChatFormatting.GREEN + "Success = Success * 2^(-10 * Overflow ratio)") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "Step three in purifying water is to remove microscopic contaminants such as dusts, microplastics and other") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "pollutants using a clarifying agent (In this case, polyaluminium chloride) to cause flocculation - the process") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "of aggregating dispersed suspended particles from a solution into larger clumps for further filtration.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(7, 4, 7, false) + .addCasingInfoRangeColored( + "Slick Sterile Flocculation Casing", + EnumChatFormatting.GRAY, + MIN_CASING, + 65, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 16, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Reinforced Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 30, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Tinted Industrial Glass", + EnumChatFormatting.GRAY, + 6, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Adamantium Frame Box", + EnumChatFormatting.GRAY, + 12, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Filter Machine Casing", + EnumChatFormatting.GRAY, + 9, + EnumChatFormatting.GOLD, + false) + .addController("Front center") + .addOutputBus(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addInputHatch( + EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "-" + EnumChatFormatting.GOLD + "2", + 1) + .addOutputHatch( + EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "-" + EnumChatFormatting.GOLD + "2", + 1) + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + // Reset amount of fluid consumed in this cycle. + this.inputFluidConsumed = 0; + } + + @Override + public void endCycle() { + super.endCycle(); + // Output waste water proportional to amount of boost levels. We do this even when the recipe fails, so you can + // always fully recycle. + // NOTE: If this process ever PRODUCES excess chlorine, there is a recipe bug. + int levels = calculateBoostLevels(); + long amount = levels * WASTE_WATER_PER_LEVEL; + this.addFluidOutputs(new FluidStack[] { OUTPUT_WASTE.getFluid(amount) }); + // Make sure to reset consumed fluid (again) + this.inputFluidConsumed = 0; + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + + // Consume all input fluid periodically, only when running + if (aTick % CONSUME_INTERVAL == 0 && mMaxProgresstime > 0) { + // Iterate over all fluids stored + List<FluidStack> fluids = this.getStoredFluids(); + for (FluidStack fluid : fluids) { + // If this FluidStack is the input chemical, consume it all + if (fluid.getFluid() + .equals(INPUT_CHEMICAL.mFluid)) { + this.inputFluidConsumed += fluid.amount; + if (!this.depleteInput(fluid)) { + stopMachine(ShutDownReasonRegistry.outOfFluid(fluid)); + } + } + } + } + } + + private int calculateBoostLevels() { + return (int) Math.floor((float) this.inputFluidConsumed / (float) INPUT_CHEMICAL_PER_LEVEL); + } + + @Override + public float calculateFinalSuccessChance() { + // Amount of times the required amount of input fluid was fully inserted + int levels = calculateBoostLevels(); + // Target amount of fluid needed to reach this amount of boost levels + long targetAmount = levels * INPUT_CHEMICAL_PER_LEVEL; + // Amount of excess fluid inserted. + long overflow = inputFluidConsumed - targetAmount; + // Base boost chance, before applying overflow penalty + float boost = SUCCESS_PER_LEVEL * levels; + // If there was any overflow, apply an exponential penalty multiplier based on percentage overflow + if (overflow > 0) { + float overflowPct = (float) overflow / INPUT_CHEMICAL_PER_LEVEL; + float penaltyMultiplier = (float) Math.pow(2.0f, overflowPct * -10.0); + return Math.max(0.0f, (this.currentRecipeChance + boost) * penaltyMultiplier); + } else { + return Math.min(100.0f, this.currentRecipeChance + boost); + } + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 3; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_ZPM; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationFlocculationRecipes; + } + + @Override + public String[] getInfoData() { + ArrayList<String> infoData = new ArrayList<>(Arrays.asList(super.getInfoData())); + infoData.add( + INPUT_CHEMICAL.mLocalizedName + " consumed this cycle: " + + EnumChatFormatting.RED + + inputFluidConsumed + + "L"); + return infoData.toArray(new String[] {}); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + aNBT.setLong("mInputFluidConsumed", inputFluidConsumed); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + this.inputFluidConsumed = aNBT.getLong("mInputFluidConsumed"); + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.GT_MACHINES_COAGULATION_LOOP.resourceLocation; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitOzonation.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitOzonation.java new file mode 100644 index 0000000000..f569273445 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitOzonation.java @@ -0,0 +1,299 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockAnyMeta; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputBus; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; + +public class GT_MetaTileEntity_PurificationUnitOzonation + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitOzonation> + implements ISurvivalConstructable { + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final String STRUCTURE_PIECE_MAIN_SURVIVAL = "main_survival"; + + private static final String[][] structure = new String[][] { + // spotless:off + { " ", " ", " A ", " A ", " AAA ", " AAA ", " A A ", " A A ", " A A ", " A~A " }, + { " A ", " A ", " A A ", " A A ", "BBBBA A", "BDDBA A", "BBBBA D A", "E A D A", "E A D A", "E AAAAA" }, + { " AAA ", " A A ", " A A", " A A", "BDDBA A", "O BA A", "BBBBA A", " C A A", " CCA A", " AAAAA" }, + { " A ", " A ", " A A ", " A A ", "BBBBA A", "BDDBA A", "BBBBA A", "E A A", "E A A", "E AAAAA" }, + { " ", " ", " A ", " A ", " AAA ", " AAA ", " AAA ", " AAA ", " AAA ", " AAA " } }; + // spotless:on + + private static final int MAIN_CASING_INDEX = getTextureIndex(GregTech_API.sBlockCasings9, 10); + + private static final int OFFSET_X = 6; + private static final int OFFSET_Y = 9; + private static final int OFFSET_Z = 0; + + /** + * If the player inserts more ozone gas than this amount, the multi will explode. + */ + public static final int MAX_OZONE_GAS_FOR_EXPLOSION = 1000 * (int) Math.pow(2, 10); + + private int casingCount = 0; + private static final int MIN_CASING = 96; + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitOzonation> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitOzonation>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + // Inert Filtration Casing + .addElement( + 'A', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitOzonation>buildHatchAdder() + .atLeastList(ImmutableList.of(InputHatch, OutputHatch, OutputBus)) + .casingIndex(getTextureIndex(GregTech_API.sBlockCasings9, 10)) + .dot(1) + .build()), + onElementPass(t -> t.casingCount++, ofBlock(GregTech_API.sBlockCasings9, 10)))) + // High Pressure Resistant Casing (possibly placeholder name) + .addElement('B', ofBlock(GregTech_API.sBlockCasings9, 9)) + // PTFE pipe casing + .addElement('C', ofBlock(GregTech_API.sBlockCasings8, 1)) + // Any tinted industrial glass + .addElement('D', ofBlockAnyMeta(GregTech_API.sBlockTintedGlass)) + .addElement('E', ofFrame(Materials.TungstenSteel)) + // Ozone input hatch + .addElement( + 'O', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitOzonation>buildHatchAdder() + .atLeast(InputHatch) + .casingIndex(getTextureIndex(GregTech_API.sBlockCasings9, 9)) + .dot(2) + .buildAndChain(ofBlock(GregTech_API.sBlockCasings9, 9)))) + .build(); + + public GT_MetaTileEntity_PurificationUnitOzonation(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + protected GT_MetaTileEntity_PurificationUnitOzonation(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitOzonation(this.mName); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection aFacing, + int colorIndex, boolean aActive, boolean redstoneLevel) { + if (side == aFacing) { + if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(MAIN_CASING_INDEX) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece(STRUCTURE_PIECE_MAIN, stackSize, hintsOnly, OFFSET_X, OFFSET_Y, OFFSET_Z); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + OFFSET_X, + OFFSET_Y, + OFFSET_Z, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitOzonation> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Ozonation Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "Will explode if the input hatch contains more than " + EnumChatFormatting.RED + + MAX_OZONE_GAS_FOR_EXPLOSION + + "L " + + EnumChatFormatting.WHITE + + "Ozone Gas.") + .addInfo( + "Receives a " + EnumChatFormatting.RED + + "20%" + + EnumChatFormatting.GRAY + + " bonus to success chance for every doubling of " + + EnumChatFormatting.WHITE + + "Ozone Gas.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The second step in water purification is ozonation, which involves injecting large quantities of small") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "bubbles of highly reactive ozone gas into the water. This removes trace element contaminants like") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "sulfur, iron and manganese, creating insoluble oxide compounds which are then filtered out.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(9, 10, 5, false) + .addCasingInfoRangeColored( + "Inert Filtration Casing", + EnumChatFormatting.GRAY, + MIN_CASING, + 102, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Reactive Gas Containment Casing", + EnumChatFormatting.GRAY, + 27, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Any Tinted Industrial Glass", + EnumChatFormatting.GRAY, + 9, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Tungstensteel Frame Box", + EnumChatFormatting.GRAY, + 6, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored("PTFE Pipe Casing", EnumChatFormatting.GRAY, 3, EnumChatFormatting.GOLD, false) + .addOutputBus(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addInputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addOutputHatch(EnumChatFormatting.GOLD + "1" + EnumChatFormatting.GRAY + "+", 1) + .addOtherStructurePart("Input Hatch (Ozone)", EnumChatFormatting.GOLD + "1", 2) + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationOzonationRecipes; + } + + @NotNull + @Override + public CheckRecipeResult checkProcessing() { + // First do recipe checking logic + CheckRecipeResult result = super.checkProcessing(); + if (!result.wasSuccessful()) return result; + // Look for ozone, blow up if more than max allowed + for (FluidStack fluid : this.storedFluids) { + if (fluid.isFluidEqual(Materials.Ozone.getGas(1L))) { + if (fluid.amount > MAX_OZONE_GAS_FOR_EXPLOSION) { + // TODO: Fix crash in hatch + // this.explodeMultiblock(); + } + } + } + return result; + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.GT_MACHINES_OZONATION_LOOP.resourceLocation; + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 2; + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + casingCount = 0; + if (!checkPiece(STRUCTURE_PIECE_MAIN, OFFSET_X, OFFSET_Y, OFFSET_Z)) return false; + if (casingCount < MIN_CASING) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_LuV; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitParticleExtractor.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitParticleExtractor.java new file mode 100644 index 0000000000..54c5ad9c09 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitParticleExtractor.java @@ -0,0 +1,484 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.api.enums.GT_HatchElement.InputBus; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputBus; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_PROCESSING_ARRAY_GLOW; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.common.util.ForgeDirection; + +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.ItemList; +import gregtech.api.enums.Materials; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.common.items.GT_MetaGenerated_Item_03; +import gregtech.common.items.ID_MetaItem_03; + +public class GT_MetaTileEntity_PurificationUnitParticleExtractor + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitParticleExtractor> + implements ISurvivalConstructable { + + public static long BARYONIC_MATTER_OUTPUT = 2000L; + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final int STRUCTURE_X_OFFSET = 8; + private static final int STRUCTURE_Y_OFFSET = 8; + private static final int STRUCTURE_Z_OFFSET = 0; + + static final String[][] structure = new String[][] { + // spotless:off + { " ", " ", " ", " ", " ", " ", " AAAAA ", " AAAAA ", " AA~AA ", " AAAAA ", " AAAAA ", " ", " ", " ", " ", " ", " " }, + { " ", " E ", " E ", " E ", " E ", " E ", " AAAAA ", " AAAAA ", " EEEEEAAAAAEEEEE ", " AAAAA ", " AAAAA ", " E ", " E ", " E ", " E ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " CCCCC ", " CDDDC ", " E CDBDC E ", " CDDDC ", " CCCCC ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " "," E ", " " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " ", " E ", " " }, + { " AAAAA ", " AAAAA ", " CCCCC ", " ", " ", " ", "AAC AAAAA CAA", "AAC ADDDA CAA", "AAC ADBDA CAA", "AAC ADDDA CAA", "AAC AAAAA CAA", " ", " ", " ", " CCCCC ", " AAAAA ", " AAAAA " }, + { " AAAAA ", " AAAAA ", " CDDDC ", " DDD ", " DDD ", " DDD ", "AAC ADDDA CAA", "AADDDDD DDDDDAA", "AADDDDD B DDDDDAA", "AADDDDD DDDDDAA", "AAC ADDDA CAA", " DDD ", " DDD ", " DDD ", " CDDDC ", " AAAAA ", " AAAAA " }, + { " AAAAA ", " EEEEEAAAAAEEEEE ", " E CDBDC E ", " E DBD E ", " E DBD E ", " E DBD E ", "AAC ADBDA CAA", "AADDDDD B DDDDDAA", "AABBBBBBBBBBBBBAA", "AADDDDD B DDDDDAA", "AAC ADBDA CAA", " E DBD E ", " E DBD E ", " E DBD E ", " E CDBDC E ", " EEEEEAAAAAEEEEE ", " AAAAA " }, + { " AAAAA ", " AAAAA ", " CDDDC ", " DDD ", " DDD ", " DDD ", "AAC ADDDA CAA", "AADDDDD DDDDDAA", "AADDDDD B DDDDDAA", "AADDDDD DDDDDAA", "AAC ADDDA CAA", " DDD ", " DDD ", " DDD ", " CDDDC ", " AAAAA ", " AAAAA " }, + { " AAAAA ", " AAAAA ", " CCCCC ", " ", " ", " ", "AAC AAAAA CAA", "AAC ADDDA CAA", "AAC ADBDA CAA", "AAC ADDDA CAA", "AAC AAAAA CAA", " ", " ", " ", " CCCCC ", " AAAAA ", " AAAAA " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " ", " DDD ", " E DBD E ", " DDD ", " ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " ", " ", " ", " ", " CCCCC ", " CDDDC ", " E CDBDC E ", " CDDDC ", " CCCCC ", " ", " ", " ", " ", " E ", " " }, + { " ", " E ", " E ", " E ", " E ", " E ", " AAAAA ", " AAAAA ", " EEEEEAAAAAEEEEE ", " AAAAA ", " AAAAA ", " E ", " E ", " E ", " E ", " E ", " " }, + { " ", " ", " ", " ", " ", " ", " AAAAA ", " AAAAA ", " AAAAA ", " AAAAA ", " AAAAA ", " ", " ", " ", " ", " ", " " } }; + // spotless:on + + // Dimensionally transcendent casing (placeholder) + private static final int CASING_INDEX_MAIN = getTextureIndex(GregTech_API.sBlockCasings10, 2); + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitParticleExtractor> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitParticleExtractor>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + // Quark Exclusion Casing + .addElement( + 'A', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitParticleExtractor>buildHatchAdder() + .atLeastList(Arrays.asList(InputBus, OutputBus, InputHatch, OutputHatch)) + .dot(1) + .casingIndex(CASING_INDEX_MAIN) + .build()), + ofBlock(GregTech_API.sBlockCasings10, 2))) + // Particle Beam Guidance Pipe Casing + .addElement('B', ofBlock(GregTech_API.sBlockCasings9, 14)) + // Femtometer-Calibrated Particle Beam Casing + .addElement('C', ofBlock(GregTech_API.sBlockCasings9, 15)) + // Non-Photonic Matter Exclusion Glass + .addElement('D', ofBlock(GregTech_API.sBlockGlass1, 3)) + .addElement('E', ofFrame(Materials.Bedrockium)) + .build(); + + private static class CatalystCombination { + + public ItemStack firstCatalyst; + public ItemStack secondCatalyst; + + public static ItemList[] CATALYST_ITEMS = new ItemList[] { ItemList.Quark_Creation_Catalyst_Up, + ItemList.Quark_Creation_Catalyst_Down, ItemList.Quark_Creation_Catalyst_Bottom, + ItemList.Quark_Creation_Catalyst_Top, ItemList.Quark_Creation_Catalyst_Strange, + ItemList.Quark_Creation_Catalyst_Charm }; + + public CatalystCombination(ItemStack first, ItemStack second) { + firstCatalyst = first; + secondCatalyst = second; + } + + public boolean matches(ItemStack a, ItemStack b) { + return (a.isItemEqual(firstCatalyst) && b.isItemEqual(secondCatalyst)) + || (b.isItemEqual(firstCatalyst) && a.isItemEqual(secondCatalyst)); + } + + public NBTTagCompound saveToNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + NBTTagCompound first = new NBTTagCompound(); + NBTTagCompound second = new NBTTagCompound(); + firstCatalyst.writeToNBT(first); + secondCatalyst.writeToNBT(second); + nbt.setTag("first", first); + nbt.setTag("second", second); + return nbt; + } + + public static CatalystCombination readFromNBT(NBTTagCompound nbt) { + NBTTagCompound first = nbt.getCompoundTag("first"); + NBTTagCompound second = nbt.getCompoundTag("second"); + return new CatalystCombination( + ItemStack.loadItemStackFromNBT(first), + ItemStack.loadItemStackFromNBT(second)); + } + } + + private static CatalystCombination generateNewCombination() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + // Generate two unique indices into the list + int firstIndex = random.nextInt(0, CatalystCombination.CATALYST_ITEMS.length); + int secondIndex = random.nextInt(0, CatalystCombination.CATALYST_ITEMS.length); + while (secondIndex == firstIndex) { + secondIndex = random.nextInt(0, CatalystCombination.CATALYST_ITEMS.length); + } + + return new CatalystCombination( + CatalystCombination.CATALYST_ITEMS[firstIndex].get(1), + CatalystCombination.CATALYST_ITEMS[secondIndex].get(1)); + } + + private CatalystCombination currentCombination = null; + + private ArrayList<ItemStack> insertedCatalysts = new ArrayList<>(); + + private int correctIndexA = -1, correctIndexB = -1; + + public GT_MetaTileEntity_PurificationUnitParticleExtractor(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitParticleExtractor(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitParticleExtractor(mName); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection side, ForgeDirection facing, + int colorIndex, boolean active, boolean redstoneLevel) { + if (side == facing) { + if (active) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_PROCESSING_ARRAY_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitParticleExtractor> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Absolute Baryonic Perfection Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "Insert " + EnumChatFormatting.WHITE + + "Quark Releasing Catalysts " + + EnumChatFormatting.GRAY + + "into the input bus while running.") + .addInfo( + "Every recipe cycle, a different combination of " + EnumChatFormatting.RED + + "2" + + EnumChatFormatting.GRAY + + " different " + + EnumChatFormatting.WHITE + + "Quark Releasing Catalysts") + .addInfo("will correctly identify the lone quark and succeed the recipe.") + .addSeparator() + .addInfo( + "Every " + EnumChatFormatting.RED + + "20" + + EnumChatFormatting.GRAY + + " ticks, consumes ALL catalysts in the input bus.") + .addInfo( + "If the two most recently inserted catalysts were the correct combination, immediately outputs " + + EnumChatFormatting.WHITE + + "Stabilised Baryonic Matter") + .addInfo("At the end of the recipe, all incorrectly inserted catalysts are returned in the output bus.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The final stage of water purification goes beyond subatomic particles and identifies the smallest") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "possible imperfections within the baryons themselves. By correctly identifying which pairs of quark") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "flavors are required, the unit will activate the catalysts, stabilizing the errant particles.") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "This ultimately creates both Stabilised Baryonic Matter and, most importantly, absolutely perfectly purified water.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(17, 17, 17, false) + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationParticleExtractionRecipes; + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + this.insertedCatalysts.clear(); + this.currentCombination = generateNewCombination(); + correctIndexA = -1; + correctIndexB = -1; + } + + private boolean isCatalyst(ItemStack stack) { + if (stack.getItem() instanceof GT_MetaGenerated_Item_03) { + int meta = stack.getItemDamage() - 32000; // why, greg. + return meta >= ID_MetaItem_03.Quark_Creation_Catalyst_Up.ID + && meta <= ID_MetaItem_03.Quark_Creation_Catalyst_Top.ID; + } + return false; + } + + @Override + public void endCycle() { + super.endCycle(); + // Output incorrect indices unchanged, the spent ones will follow if recipe was successful from the actual + // recipe outputs + for (int i = 0; i < insertedCatalysts.size(); ++i) { + if (i == correctIndexA || i == correctIndexB) continue; + + addOutput(insertedCatalysts.get(i)); + } + } + + @Override + public float calculateFinalSuccessChance() { + // Only succeed if correct combination was inserted + if (correctIndexA >= 0) return 100.0f; + else return 0.0f; + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + // Every 20 ticks, add all catalysts from the input bus to the internal inventory. + if (mMaxProgresstime > 0 && aTick % 20 == 0) { + ArrayList<ItemStack> storedInputs = getStoredInputs(); + // For each stack in the input, check if it is a valid catalyst item and if so consume it + for (ItemStack stack : storedInputs) { + if (isCatalyst(stack)) { + this.insertedCatalysts.add(stack.copy()); + this.depleteInput(stack); + } + } + + // Only do this check if we didn't find a correct combination yet + if (correctIndexA >= 0) return; + + // After draining all catalyst inputs, find the 2 most recently inserted items + if (insertedCatalysts.isEmpty()) return; + int firstIndex = insertedCatalysts.size() - 1; + ItemStack first = insertedCatalysts.get(firstIndex); + // Since correct combinations are always different catalysts, we can require that there is a second item in + // the history + if (first.stackSize == 1 && insertedCatalysts.size() >= 2) { + int secondIndex = insertedCatalysts.size() - 2; + ItemStack second = insertedCatalysts.get(secondIndex); + // Now check if this combination matches the current correct one. + if (currentCombination.matches(first, second)) { + // It does, we can save that the indices are correct. This means we need to + // - stop checking if any future insertions are the correct combination (but still consume them) + // - output baryonic matter + correctIndexA = firstIndex; + correctIndexB = secondIndex; + addOutput(Materials.StableBaryonicMatter.getFluid(BARYONIC_MATTER_OUTPUT)); + } + } + } + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + if (this.currentCombination != null) { + aNBT.setTag("currentCombination", this.currentCombination.saveToNBT()); + } + NBTTagCompound insertedNBT = new NBTTagCompound(); + for (int i = 0; i < insertedCatalysts.size(); ++i) { + ItemStack inserted = insertedCatalysts.get(i); + NBTTagCompound itemNBT = new NBTTagCompound(); + inserted.writeToNBT(itemNBT); + insertedNBT.setTag(Integer.toString(i), itemNBT); + } + aNBT.setTag("insertedItems", insertedNBT); + aNBT.setInteger("correctIndexA", correctIndexA); + aNBT.setInteger("correctIndexB", correctIndexB); + super.saveNBTData(aNBT); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + if (aNBT.hasKey("currentCombination")) { + currentCombination = CatalystCombination.readFromNBT(aNBT.getCompoundTag("currentCombination")); + } + if (aNBT.hasKey("insertedItems")) { + NBTTagCompound insertedList = aNBT.getCompoundTag("insertedItems"); + // Initialize empty list with correct size + this.insertedCatalysts = new ArrayList<>( + Collections.nCopies( + insertedList.func_150296_c() + .size(), + null)); + for (String key : insertedList.func_150296_c()) { + NBTTagCompound itemCompound = insertedList.getCompoundTag(key); + int index = Integer.parseInt(key); + this.insertedCatalysts.set(index, ItemStack.loadItemStackFromNBT(itemCompound)); + } + } + if (aNBT.hasKey("correctIndexA")) { + correctIndexA = aNBT.getInteger("correctIndexA"); + } + if (aNBT.hasKey("correctIndexB")) { + correctIndexB = aNBT.getInteger("correctIndexB"); + } + super.loadNBTData(aNBT); + } + + private String getCorrectlyDecodedString() { + if (correctIndexA >= 0) { + return EnumChatFormatting.GREEN + "Yes"; + } + return EnumChatFormatting.RED + "No"; + } + + public EnumChatFormatting getQuarkColor(ItemStack stack) { + int meta = stack.getItemDamage() - 32000; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Up.ID) return EnumChatFormatting.RED; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Down.ID) return EnumChatFormatting.YELLOW; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Strange.ID) return EnumChatFormatting.DARK_PURPLE; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Charm.ID) return EnumChatFormatting.LIGHT_PURPLE; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Bottom.ID) return EnumChatFormatting.GREEN; + if (meta == ID_MetaItem_03.Quark_Creation_Catalyst_Top.ID) return EnumChatFormatting.BLUE; + return EnumChatFormatting.GRAY; + } + + public String[] getInfoData() { + ArrayList<String> info = new ArrayList<>(Arrays.asList(super.getInfoData())); + info.add("Catalyst insertion history for this recipe cycle (most recent first): "); + for (int i = insertedCatalysts.size() - 1; i >= 0; --i) { + ItemStack stack = insertedCatalysts.get(i); + String name = stack.getDisplayName(); + String[] split = name.split("-"); + info.add( + EnumChatFormatting.YELLOW + "" + + (i + 1) + + ": " + + getQuarkColor(stack) + + split[0] + + EnumChatFormatting.GRAY + + "-" + + split[1]); + } + info.add("Quark Combination correctly identified: " + getCorrectlyDecodedString()); + return info.toArray(new String[] {}); + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 8; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_UEV; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPhAdjustment.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPhAdjustment.java new file mode 100644 index 0000000000..5c65e105dd --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPhAdjustment.java @@ -0,0 +1,609 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.api.enums.GT_HatchElement.InputBus; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Mods.GoodGenerator; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.util.GT_RecipeBuilder.SECONDS; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.IGT_HatchAdder; + +public class GT_MetaTileEntity_PurificationUnitPhAdjustment + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitPhAdjustment> + implements ISurvivalConstructable { + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final int STRUCTURE_X_OFFSET = 7; + private static final int STRUCTURE_Y_OFFSET = 4; + private static final int STRUCTURE_Z_OFFSET = 1; + + private static final String[][] structure = new String[][] { + // spotless:off + { "E E E E", "EAAAE EAAAE", "EAGAE EAHAE", "EAGAE EAHAE", "EAGAE EAHAE", "EAAAE EAAAE" }, + { " AAA AAA ", "A A A A", "A A A A", "A A A A", "A ABB~BBA A", "AAAAA AAAAA" }, + { " AXA AYA ", "A A A A", "G A A H", "G ABBBBBA H", "G H", "AAAAABRBRBAAAAA" }, + { " AAA AAA ", "A A A A", "A A A A", "A A A A", "A AIIIIIA A", "AAAAA AAAAA" }, + { "E E E E", "EAAAE EAAAE", "EAGAE EAHAE", "EAGAE EAHAE", "EAGAE EAHAE", "EAAAE EAAAE" } }; + // spotless:on + + private static final int CASING_INDEX_MIDDLE = getTextureIndex(GregTech_API.sBlockCasings9, 7); + private static final int CASING_INDEX_TOWER = getTextureIndex(GregTech_API.sBlockCasings9, 8); + + /** + * The current pH value of the water inside the multiblock + */ + private float currentpHValue = 0.0f; + + /** + * The multiblock will try to consume catalyst every CONSUME_INTERVAL ticks. + */ + private static final int CONSUME_INTERVAL = 1 * SECONDS; + + /** + * Maximum deviation the initial pH value can have away from the neutral value. + */ + private static final float INITIAL_PH_DEVIATION = 2.5f; + + /** + * pH value of entirely pH neutral water. + */ + private static final float PH_NEUTRAL_VALUE = 7.0f; + + /** + * Maximum deviation from the neutral value that is allowed for the recipe to succeed. + */ + private static final float PH_MAX_DEVIATION = 0.05f; + + /** + * Change in pH value for each piece of alkaline dust supplied. + */ + public static final float PH_PER_ALKALINE_DUST = 0.01f; + + /** + * Change in pH value for every 10L of acid supplied. + */ + public static final float PH_PER_10_ACID_LITER = -0.01f; + + /** + * Alkaline catalyst material + */ + public static final Materials ALKALINE_MATERIAL = Materials.SodiumHydroxide; + + /** + * Acidic catalyst material + */ + public static final Materials ACIDIC_MATERIAL = Materials.HydrochloricAcid; + + /** + * The input hatch for the acidic material + */ + private GT_MetaTileEntity_Hatch_Input acidInputHatch; + /** + * The input bus for the alkaline material + */ + private GT_MetaTileEntity_Hatch_InputBus alkalineInputBus; + + /** + * List of all placed sensor hatches in the multi, so we can update them with the proper pH value when it changes. + */ + private final ArrayList<GT_MetaTileEntity_pHSensor> sensorHatches = new ArrayList<>(); + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitPhAdjustment> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitPhAdjustment>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + // Extreme Corrosion Resistant Casing + .addElement('A', ofBlock(GregTech_API.sBlockCasings9, 8)) + // Naquadah Reinforced Water Plant Casing + .addElement('B', ofBlock(GregTech_API.sBlockCasings9, 7)) + .addElement('E', ofFrame(Materials.NaquadahAlloy)) + // pH Resistant Glass + .addElement('G', ofBlock(GregTech_API.sBlockGlass1, 0)) + .addElement('H', ofBlock(GregTech_API.sBlockGlass1, 0)) + // Regular I/O hatches + .addElement( + 'I', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPhAdjustment>buildHatchAdder() + .atLeastList(t.getAllowedHatches()) + .dot(1) + .casingIndex(CASING_INDEX_MIDDLE) + .build()), + // Naquadah Reinforced Water Plant Casing + ofBlock(GregTech_API.sBlockCasings9, 7))) + .addElement( + 'R', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPhAdjustment>buildHatchAdder() + .atLeast(SpecialHatchElement.PhSensor) + .dot(2) + .cacheHint(() -> "pH Sensor Hatch") + .casingIndex(CASING_INDEX_MIDDLE) + .build()), + // Naquadah Reinforced Water Plant Casing + ofBlock(GregTech_API.sBlockCasings9, 7))) + // Special I/O hatches + .addElement( + 'X', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPhAdjustment>buildHatchAdder() + .atLeast(InputBus) + .dot(3) + .adder(GT_MetaTileEntity_PurificationUnitPhAdjustment::addAlkalineBusToMachineList) + .cacheHint(() -> "Input Bus (" + ALKALINE_MATERIAL.mLocalizedName + ")") + .casingIndex(CASING_INDEX_TOWER) + .allowOnly(ForgeDirection.UP) + .build())) + .addElement( + 'Y', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPhAdjustment>buildHatchAdder() + .atLeast(InputHatch) + .dot(4) + .adder(GT_MetaTileEntity_PurificationUnitPhAdjustment::addAcidHatchToMachineList) + .cacheHint(() -> "Input Hatch (" + ACIDIC_MATERIAL.mLocalizedName + ")") + .casingIndex(CASING_INDEX_TOWER) + .allowOnly(ForgeDirection.UP) + .build())) + .build(); + + private List<IHatchElement<? super GT_MetaTileEntity_PurificationUnitPhAdjustment>> getAllowedHatches() { + return ImmutableList.of(InputHatch, OutputHatch); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity aBaseMetaTileEntity, ForgeDirection side, ForgeDirection aFacing, + int colorIndex, boolean aActive, boolean redstoneLevel) { + if (side == aFacing) { + if (aActive) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MIDDLE), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MIDDLE), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MIDDLE) }; + } + + public GT_MetaTileEntity_PurificationUnitPhAdjustment(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitPhAdjustment(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitPhAdjustment(this.mName); + } + + @Override + protected void setHatchRecipeMap(GT_MetaTileEntity_Hatch_Input hatch) { + // Do nothing, we don't want to lock hatches to recipe maps since this can cause + // them to reject our catalyst fluids + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitPhAdjustment> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationPhAdjustmentRecipes; + } + + public boolean addAcidHatchToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity == null) return false; + if (aMetaTileEntity instanceof GT_MetaTileEntity_Hatch_Input) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + ((GT_MetaTileEntity_Hatch_Input) aMetaTileEntity).mRecipeMap = null; + acidInputHatch = (GT_MetaTileEntity_Hatch_Input) aMetaTileEntity; + return true; + } + return false; + } + + public boolean addAlkalineBusToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity == null) return false; + if (aMetaTileEntity instanceof GT_MetaTileEntity_Hatch_InputBus) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + ((GT_MetaTileEntity_Hatch_InputBus) aMetaTileEntity).mRecipeMap = null; + alkalineInputBus = (GT_MetaTileEntity_Hatch_InputBus) aMetaTileEntity; + return true; + } + return false; + } + + public boolean addSensorHatchToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity instanceof GT_MetaTileEntity_pHSensor) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + return this.sensorHatches.add((GT_MetaTileEntity_pHSensor) aMetaTileEntity); + } + return false; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the pH Neutralization Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "Initial pH value every cycle varies from " + EnumChatFormatting.RED + + (PH_NEUTRAL_VALUE - INITIAL_PH_DEVIATION) + + EnumChatFormatting.GRAY + + " - " + + EnumChatFormatting.RED + + (PH_NEUTRAL_VALUE + INITIAL_PH_DEVIATION) + + " pH" + + EnumChatFormatting.GRAY + + ".") + .addInfo( + "If the pH value is within " + EnumChatFormatting.RED + + PH_MAX_DEVIATION + + " pH " + + EnumChatFormatting.GRAY + + "of 7.0 pH at the end of the cycle, the recipe always succeeds.") + .addInfo("Otherwise, the recipe always fails.") + .addInfo("Use a pH Sensor Hatch to read the current pH value.") + .addSeparator() + .addInfo( + "Every " + EnumChatFormatting.RED + + CONSUME_INTERVAL + + EnumChatFormatting.GRAY + + " ticks, consumes ALL " + + EnumChatFormatting.WHITE + + ALKALINE_MATERIAL.mLocalizedName + + EnumChatFormatting.GRAY + + " and " + + EnumChatFormatting.WHITE + + ACIDIC_MATERIAL.mLocalizedName + + EnumChatFormatting.GRAY + + " in the special hatches.") + .addInfo( + EnumChatFormatting.RED + "Raises " + + EnumChatFormatting.GRAY + + "the pH value by " + + EnumChatFormatting.RED + + PH_PER_ALKALINE_DUST + + " pH " + + EnumChatFormatting.GRAY + + "per piece of " + + EnumChatFormatting.WHITE + + ALKALINE_MATERIAL.getDust(1) + .getDisplayName() + + EnumChatFormatting.GRAY + + ".") + .addInfo( + EnumChatFormatting.RED + "Lowers " + + EnumChatFormatting.GRAY + + "the pH value by " + + EnumChatFormatting.RED + + -PH_PER_10_ACID_LITER + + " pH " + + EnumChatFormatting.GRAY + + "per " + + EnumChatFormatting.RED + + "10L " + + EnumChatFormatting.GRAY + + "of " + + EnumChatFormatting.WHITE + + ACIDIC_MATERIAL.getFluid(1L) + .getLocalizedName() + + EnumChatFormatting.GRAY + + ".") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The fourth step of water purification is to neutralize the solution and bring its pH to exactly 7, rendering") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "the solution inert with no hydrogen ion activity beyond water’s natural amphiproticity. Acids and bases from soils") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "and geology cause natural alkalinity variations in water which can cause corrosive reactions with sensitive") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "materials. This necessitates the use of the corresponding neutralizing agents to pH balance the water.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(7, 4, 7, false) + .addCasingInfoExactlyColored( + "Stabilized Naquadah Water Plant Casing", + EnumChatFormatting.GRAY, + 15, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Chemical Grade Glass", + EnumChatFormatting.GRAY, + 18, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Naquadah Alloy Frame Box", + EnumChatFormatting.GRAY, + 48, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Inert Neutralization Water Plant Casing", + EnumChatFormatting.GRAY, + 67 * 2, + EnumChatFormatting.GOLD, + false) + .addController("Front center") + .addOtherStructurePart("Input Hatch (Water)", EnumChatFormatting.GOLD + "1+", 1) + .addOtherStructurePart("Output Hatch", EnumChatFormatting.GOLD + "1", 1) + .addOtherStructurePart("pH Sensor Hatch", EnumChatFormatting.GOLD + "2", 2) + .addOtherStructurePart("Input Bus (Sodium Hydroxide)", EnumChatFormatting.GOLD + "1", 3) + .addOtherStructurePart("Input Hatch (Hydrochloric Acid)", EnumChatFormatting.GOLD + "1", 4) + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + // Randomize initial pH value + ThreadLocalRandom random = ThreadLocalRandom.current(); + // Generate random integer in [-RNG_PRECISION, RNG_PRECISION] + final int RNG_PRECISION = 1000; + int rng = random.nextInt(-RNG_PRECISION, RNG_PRECISION); + // Remap to [-1.0, 1.0] and then to [-INITIAL_PH_DEVIATION, INITIAL_PH_DEVIATION] + float deviation = ((float) rng / RNG_PRECISION) * INITIAL_PH_DEVIATION; + // Round to 2 digits + this.currentpHValue = Math.round((PH_NEUTRAL_VALUE + deviation) * 100.0f) / 100.0f; + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + // Eat all acid and alkaline material every second + if (mMaxProgresstime > 0 && aTick % CONSUME_INTERVAL == 0) { + // Important that we drain backwards, since draining stacks can auto-sort the bus + long totalAlkalineDrained = 0; + for (int i = alkalineInputBus.getSizeInventory() - 1; i >= 0; --i) { + ItemStack stack = alkalineInputBus.getStackInSlot(i); + // If this ItemStack is the alkaline material, drain it entirely and record the amount drained + if (stack != null && stack.isItemEqual(ALKALINE_MATERIAL.getDust(1))) { + totalAlkalineDrained += stack.stackSize; + alkalineInputBus.decrStackSize(i, stack.stackSize); + } + } + + // Now do fluid, this is simpler since we only need to bother with one slot + FluidStack stack = acidInputHatch.getDrainableStack(); + int numMultiples = 0; + if (stack != null && stack.isFluidEqual(ACIDIC_MATERIAL.getFluid(1))) { + int acidAvailable = stack.amount; + // We only care about multiples of 10, but we still drain all. + numMultiples = Math.floorDiv(acidAvailable, 10); + acidInputHatch.drain(acidAvailable, true); + } else { + // Little easier egg: Fluoroantimonic acid has a pH value of -31, it's an acid so strong it will + // instantly shatter the glass in the structure. + if (GoodGenerator.isModLoaded()) { + Fluid acid = FluidRegistry.getFluid("fluoroantimonic acid"); + if (stack != null && stack.getFluid() + .equals(acid)) { + // TODO: Actually break the glass and trigger achievement lol + } + } + } + + // Adjust pH with to new value + this.currentpHValue = this.currentpHValue + totalAlkalineDrained * PH_PER_ALKALINE_DUST + + numMultiples * PH_PER_10_ACID_LITER; + + // Clamp pH to sensible values + this.currentpHValue = Math.min(Math.max(this.currentpHValue, 0.0f), 14.0f); + + // Round to 2 decimals + this.currentpHValue = Math.round(this.currentpHValue * 100.0f) / 100.0f; + } + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTimer) { + super.onPostTick(aBaseMetaTileEntity, aTimer); + // Update sensor hatch + for (GT_MetaTileEntity_pHSensor hatch : sensorHatches) { + hatch.updateRedstoneOutput(this.currentpHValue); + } + } + + @Override + public float calculateFinalSuccessChance() { + // Success chance is 100% when inside target range, 0% otherwise + float distance = Math.abs(this.currentpHValue - PH_NEUTRAL_VALUE); + if (distance <= PH_MAX_DEVIATION) { + return 100.0f; + } else { + return 0.0f; + } + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 4; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_ZPM; + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + public String[] getInfoData() { + ArrayList<String> infoData = new ArrayList<>(Arrays.asList(super.getInfoData())); + infoData.add("Current pH Value: " + EnumChatFormatting.YELLOW + currentpHValue + " pH"); + return infoData.toArray(new String[] {}); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + aNBT.setFloat("mCurrentpH", this.currentpHValue); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + this.currentpHValue = aNBT.getFloat("mCurrentpH"); + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.GT_MACHINES_PURIFICATION_PH_LOOP.resourceLocation; + } + + private enum SpecialHatchElement implements IHatchElement<GT_MetaTileEntity_PurificationUnitPhAdjustment> { + + PhSensor(GT_MetaTileEntity_PurificationUnitPhAdjustment::addSensorHatchToMachineList, + GT_MetaTileEntity_pHSensor.class) { + + @Override + public long count( + GT_MetaTileEntity_PurificationUnitPhAdjustment gtMetaTileEntityPurificationUnitPhAdjustment) { + return gtMetaTileEntityPurificationUnitPhAdjustment.sensorHatches.size(); + } + }; + + private final List<Class<? extends IMetaTileEntity>> mteClasses; + private final IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitPhAdjustment> adder; + + @SafeVarargs + SpecialHatchElement(IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitPhAdjustment> adder, + Class<? extends IMetaTileEntity>... mteClasses) { + this.mteClasses = Collections.unmodifiableList(Arrays.asList(mteClasses)); + this.adder = adder; + } + + @Override + public List<? extends Class<? extends IMetaTileEntity>> mteClasses() { + return mteClasses; + } + + public IGT_HatchAdder<? super GT_MetaTileEntity_PurificationUnitPhAdjustment> adder() { + return adder; + } + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPlasmaHeater.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPlasmaHeater.java new file mode 100644 index 0000000000..3fdd408ec1 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitPlasmaHeater.java @@ -0,0 +1,570 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockAnyMeta; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.recipe.RecipeMaps.purificationPlasmaHeatingRecipes; +import static gregtech.api.util.GT_RecipeBuilder.SECONDS; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.ImmutableList; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import cpw.mods.fml.common.registry.GameRegistry; +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.Mods; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_ModHandler; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; + +public class GT_MetaTileEntity_PurificationUnitPlasmaHeater + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitPlasmaHeater> + implements ISurvivalConstructable { + + private static final int CASING_INDEX_HEATER = getTextureIndex(GregTech_API.sBlockCasings9, 11); + private static final int CASING_INDEX_TOWER = getTextureIndex(GregTech_API.sBlockCasings9, 5); + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final int STRUCTURE_X_OFFSET = 2; + private static final int STRUCTURE_Y_OFFSET = 14; + private static final int STRUCTURE_Z_OFFSET = 5; + + /** + * Fluid is consumed every CONSUME_INTERVAL ticks + */ + private static final long CONSUME_INTERVAL = 1 * SECONDS; + + /** + * Current internal temperature of the multiblock + */ + private long currentTemperature = 0; + /** + * Amount of successful heating cycles completed + */ + private int cyclesCompleted = 0; + /** + * Whether this recipe is ruined due to high temperature + */ + private boolean ruinedCycle = false; + + private enum CycleState { + // Was previously at 0K, currently waiting to heat to 10000K + Heating, + // Was previously at 10000K, currently waiting to cool down to 0K + Cooling + } + + private CycleState state = CycleState.Heating; + + // A cycle is 30s at shortest, a purification plant cycle is 120s. 33% chance per heating cycle + // will give you plenty of room for delay and still get to 99% chance. + public static final long SUCCESS_PER_CYCLE = 33; + + // Consumption rates in liters/second + public static final long MAX_PLASMA_PER_SEC = 10; + public static final long MAX_COOLANT_PER_SEC = 100; + // Change in temperature per consumed liter of plasma + public static final long PLASMA_TEMP_PER_LITER = 100; + // Change in temperature per consumed liter of coolant + public static final long COOLANT_TEMP_PER_LITER = -5; + // Temperature at which the batch is ruined + public static final long MAX_TEMP = 12500; + // Point at which the heating point of the cycle is reached + public static final long HEATING_POINT = 10000; + + private static final Materials plasmaMaterial = Materials.Helium; + private static final Materials coolantMaterial = Materials.SuperCoolant; + + private GT_MetaTileEntity_Hatch_Input plasmaInputHatch; + private GT_MetaTileEntity_Hatch_Input coolantInputHatch; + + private static final String[][] structure = new String[][] { + // spotless:off + { " DDDDDDD ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " DDDDD ", " DDDDD ", " DDKDD " }, + { " DD DD ", " DDDDDDD ", " DDDDD ", " ", " ", " ", " ", " ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DD DD ", " DD DD ", " DDDDDDDDD " }, + { " D D ", " DDD DDD ", " DDDDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " D D ", " D D ", " DDDDDDDDDDD " }, + { " D D ", " D DD ", " DD D ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " DDDDDDDDDDDDD " }, + { " D D", " DD DD ", " DD D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " DDDDDDDDDDDDD " }, + { " D D", " D D ", " DD D ", " D D ", " D D ", "GBBBG D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D", "G G D D", "GB~BG DDDDDDDDDDDDDDD" }, + { " D D", " D D ", " DD D ", " D D ", " BBB D D ", "BBBBB D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D", " EEEBBBBD D", "BAAAB DDDDDDDDDDDDDDD" }, + { " D D", " D D ", " DD D ", " D D ", " BBB D D ", "BBBBB D D ", " EFE D D ", " EFE D D ", " EFE D D ", " EFE D D ", " EFE D D ", " EFE D D ", " EFEBBBBD D", " EFE D D", "PAAABBBBDDDDDDDDDDDDDDD" }, + { " D D", " D D ", " DD D ", " D D ", " BBB D D ", "BBBBB D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D ", " EEE D D", " EEEBBBBD D", "BAAAB DDDDDDDDDDDDDDD" }, + { " D D", " D D ", " DD D ", " D D ", " D D ", "GBBBG D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D ", "G G D D", "G G D D", "GBBBG DDDDDDDDDDDDDDD" }, + { " D D", " D D ", " D D ", " D DD ", " D DD ", " D DD ", " D DD ", " D DD ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " DDDDDDDDDDDDD " }, + { " D DD", " D D ", " D D ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " D D ", " D D ", " D D ", " D D ", " D D ", " D D ", " DDDDDDDDDDDDD " }, + { " D D ", " DD DD ", " D D ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DD DD ", " DD DD ", " DD DD ", " DD DD ", " D D ", " D D ", " DDDDDDDDDDD " }, + { " DD DD ", " DDDDDDD ", " DDDDD ", " ", " ", " ", " ", " ", " DDDDD ", " DDDDD ", " DDDDD ", " DDDDD ", " DD DD ", " DD DD ", " DDDDDDDDD " }, + { " DDDDDDD ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " DDDDD ", " DDDDD ", " DDDDD " } }; + // spotless:on + + private int casingCount = 0; + private static final int MIN_CASING = 50; + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitPlasmaHeater> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitPlasmaHeater>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + // Superconducting coil block + .addElement('A', ofBlock(GregTech_API.sBlockCasings1, 15)) + // Plasma Heating Casing + .addElement( + 'B', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPlasmaHeater>buildHatchAdder() + .atLeastList(t.getAllowedHatches()) + .dot(1) + .casingIndex(CASING_INDEX_HEATER) + .build()), + onElementPass(t -> t.casingCount++, ofBlock(GregTech_API.sBlockCasings9, 11)))) + // Reinforced Sterile Water Plant Casing + .addElement('D', ofBlock(GregTech_API.sBlockCasings9, 5)) + // Any Tinted Glass + .addElement('E', ofBlockAnyMeta(GregTech_API.sBlockTintedGlass, 0)) + // Neonite, with fallback to air + .addElement('F', lazy(t -> { + if (Mods.Chisel.isModLoaded()) { + Block neonite = GameRegistry.findBlock(Mods.Chisel.ID, "neonite"); + return ofBlockAnyMeta(neonite, 7); + } else { + return ofBlockAnyMeta(Blocks.air); + } + })) + // Superconductor Base ZPM frame box + .addElement('G', ofFrame(Materials.Tetranaquadahdiindiumhexaplatiumosminid)) + // Coolant input hatch + .addElement( + 'K', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPlasmaHeater>buildHatchAdder() + .atLeast(InputHatch) + .dot(2) + .adder(GT_MetaTileEntity_PurificationUnitPlasmaHeater::addCoolantHatchToMachineList) + .cacheHint(() -> "Input Hatch (Coolant)") + .casingIndex(CASING_INDEX_TOWER) + .buildAndChain(ofBlock(GregTech_API.sBlockCasings9, 5)))) + // Plasma input hatch + .addElement( + 'P', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitPlasmaHeater>buildHatchAdder() + .atLeast(InputHatch) + .dot(3) + .adder(GT_MetaTileEntity_PurificationUnitPlasmaHeater::addPlasmaHatchToMachineList) + .cacheHint(() -> "Input Hatch (Plasma)") + .casingIndex(CASING_INDEX_HEATER) + .buildAndChain(ofBlock(GregTech_API.sBlockCasings9, 11)))) + .build(); + + private List<IHatchElement<? super GT_MetaTileEntity_PurificationUnitPlasmaHeater>> getAllowedHatches() { + return ImmutableList.of(InputHatch, OutputHatch); + } + + public GT_MetaTileEntity_PurificationUnitPlasmaHeater(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitPlasmaHeater(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitPlasmaHeater(this.mName); + } + + @Override + protected void setHatchRecipeMap(GT_MetaTileEntity_Hatch_Input hatch) { + // Do nothing, we don't want to lock hatches to recipe maps since this can cause + // them to reject our catalyst fluids + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection side, ForgeDirection facing, + int colorIndex, boolean active, boolean redstoneLevel) { + if (side == facing) { + if (active) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_HEATER), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_HEATER), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_HEATER) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitPlasmaHeater> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return purificationPlasmaHeatingRecipes; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the Extreme Temperature Fluctuation Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "Complete heating cycles by first heating the water to " + EnumChatFormatting.RED + + HEATING_POINT + + "K" + + EnumChatFormatting.GRAY + + ",") + .addInfo( + "and then cooling it back down to " + EnumChatFormatting.RED + "0K" + EnumChatFormatting.GRAY + ".") + .addInfo( + "Initial temperature is reset to " + EnumChatFormatting.RED + + "0K" + + EnumChatFormatting.GRAY + + " on recipe start.") + .addInfo( + // TODO: Refer to heating cycles in another way to avoid confusion + "Each completed heating cycle boosts success chance by " + EnumChatFormatting.RED + + SUCCESS_PER_CYCLE + + "%.") + .addInfo( + "If the temperature ever reaches " + EnumChatFormatting.RED + + MAX_TEMP + + "K" + + EnumChatFormatting.GRAY + + " the recipe will fail and output steam.") + .addSeparator() + .addInfo( + "Consumes up to " + EnumChatFormatting.RED + + MAX_PLASMA_PER_SEC + + "L/s " + + EnumChatFormatting.WHITE + + plasmaMaterial.getPlasma(1) + .getLocalizedName() + + EnumChatFormatting.GRAY + + " and up to " + + EnumChatFormatting.RED + + MAX_COOLANT_PER_SEC + + "L/s " + + EnumChatFormatting.WHITE + + coolantMaterial.getFluid(1) + .getLocalizedName()) + .addInfo( + EnumChatFormatting.RED + "Raises " + + EnumChatFormatting.GRAY + + "the temperature by " + + EnumChatFormatting.RED + + PLASMA_TEMP_PER_LITER + + "K" + + EnumChatFormatting.GRAY + + " per liter of plasma consumed.") + .addInfo( + EnumChatFormatting.RED + "Lowers " + + EnumChatFormatting.GRAY + + "the temperature by " + + EnumChatFormatting.RED + + -COOLANT_TEMP_PER_LITER + + "K" + + EnumChatFormatting.GRAY + + " per liter of coolant consumed.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "Step five of water purification is to evaporate complex organic polymers and extremophile organisms") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "that might be resistant to simple acids, clarifying agents, and filters. Using an ultra high") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "pressure chamber in combination with extreme temperature fluctuations allows the water to remain") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "supercritical while evaporating any remaining contaminants, ready for filtration.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(23, 15, 15, false) + .addCasingInfoExactlyColored( + "Reinforced Sterile Water Plant Casing", + EnumChatFormatting.GRAY, + 1091, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Heat-Resistant Trinium Plated Casing", + EnumChatFormatting.GRAY, + 54, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Any Tinted Industrial Glass", + EnumChatFormatting.GRAY, + 64, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Superconductor Base ZPM Frame Box", + EnumChatFormatting.GRAY, + 40, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored("Any Neonite", EnumChatFormatting.GRAY, 8, EnumChatFormatting.GOLD, false) + .addCasingInfoExactlyColored( + "Superconducting Coil Block", + EnumChatFormatting.GRAY, + 8, + EnumChatFormatting.GOLD, + false) + .addController("Front center") + .addOtherStructurePart("Input Hatch (Water)", EnumChatFormatting.GOLD + "1+", 1) + .addOtherStructurePart("Output Hatch", EnumChatFormatting.GOLD + "1", 1) + .addOtherStructurePart("Input Hatch (Coolant)", EnumChatFormatting.GOLD + "1", 2) + .addOtherStructurePart("Input Hatch (Plasma)", EnumChatFormatting.GOLD + "1", 3) + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + // Reset internal state + this.cyclesCompleted = 0; + this.currentTemperature = 0; + this.ruinedCycle = false; + this.state = CycleState.Heating; + } + + // Drains up to maxAmount of a fluid if it is the same fluid as given, returns the amount drained + private long drainFluidLimited(GT_MetaTileEntity_Hatch_Input inputHatch, FluidStack fluid, long maxAmount) { + FluidStack hatchStack = inputHatch.getDrainableStack(); + if (hatchStack == null) return 0; + if (hatchStack.isFluidEqual(fluid)) { + long amountToDrain = Math.min(maxAmount, hatchStack.amount); + if (amountToDrain > 0) { + inputHatch.drain((int) amountToDrain, true); + } + return amountToDrain; + } else { + return 0; + } + } + + @Override + public void addRecipeOutputs() { + super.addRecipeOutputs(); + // If the cycle was ruined, output steam + if (this.ruinedCycle) { + FluidStack insertedWater = currentRecipe.mFluidInputs[0]; + // Multiply by 60 since that's the water:steam ratio in GTNH + long steamAmount = insertedWater.amount * 60L; + addOutput(GT_ModHandler.getSteam(steamAmount)); + } + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + if (mMaxProgresstime > 0 && aTick % CONSUME_INTERVAL == 0) { + // Drain plasma and coolant up to limited amount per second + long plasmaDrained = drainFluidLimited(plasmaInputHatch, plasmaMaterial.getPlasma(1L), MAX_PLASMA_PER_SEC); + long coolantDrained = drainFluidLimited( + coolantInputHatch, + coolantMaterial.getFluid(1L), + MAX_COOLANT_PER_SEC); + // Calculate temperature change + long tempChance = plasmaDrained * PLASMA_TEMP_PER_LITER + coolantDrained * COOLANT_TEMP_PER_LITER; + currentTemperature = Math.max(0, currentTemperature + tempChance); + // Check if batch was ruined + if (currentTemperature > MAX_TEMP) { + ruinedCycle = true; + } + // Update cycle state. + switch (state) { + case Heating -> { + // Heating state can change to cooling when temperature exceeds 10000K + if (currentTemperature >= HEATING_POINT) { + state = CycleState.Cooling; + } + } + case Cooling -> { + if (currentTemperature == 0) { + state = CycleState.Heating; + cyclesCompleted += 1; + } + } + } + } + } + + public boolean addCoolantHatchToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity == null) return false; + if (aMetaTileEntity instanceof GT_MetaTileEntity_Hatch_Input) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + ((GT_MetaTileEntity_Hatch_Input) aMetaTileEntity).mRecipeMap = null; + coolantInputHatch = (GT_MetaTileEntity_Hatch_Input) aMetaTileEntity; + return true; + } + return false; + } + + public boolean addPlasmaHatchToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity == null) return false; + if (aMetaTileEntity instanceof GT_MetaTileEntity_Hatch_Input) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + ((GT_MetaTileEntity_Hatch_Input) aMetaTileEntity).mRecipeMap = null; + plasmaInputHatch = (GT_MetaTileEntity_Hatch_Input) aMetaTileEntity; + return true; + } + return false; + } + + @Override + public float calculateFinalSuccessChance() { + if (ruinedCycle) return 0.0f; + // Success chance directly depends on number of cycles completed. + return cyclesCompleted * SUCCESS_PER_CYCLE + currentRecipeChance; + } + + @Override + public String[] getInfoData() { + ArrayList<String> infoData = new ArrayList<>(Arrays.asList(super.getInfoData())); + infoData.add("Current temperature: " + EnumChatFormatting.YELLOW + currentTemperature + "K"); + infoData.add("Heating cycles completed this run: " + EnumChatFormatting.YELLOW + cyclesCompleted); + return infoData.toArray(new String[] {}); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + aNBT.setLong("mCurrentTemperature", currentTemperature); + aNBT.setInteger("mCyclesCompleted", cyclesCompleted); + aNBT.setBoolean("mRuinedCycle", ruinedCycle); + aNBT.setString("mCycleState", state.toString()); + super.saveNBTData(aNBT); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + currentTemperature = aNBT.getLong("mCurrentTemperature"); + cyclesCompleted = aNBT.getInteger("mCyclesCompleted"); + ruinedCycle = aNBT.getBoolean("mRuinedCycle"); + state = CycleState.valueOf(aNBT.getString("mCycleState")); + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 5; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_UV; + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + casingCount = 0; + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + if (casingCount < MIN_CASING) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.GT_MACHINES_PURIFICATION_PLASMA_LOOP.resourceLocation; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitUVTreatment.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitUVTreatment.java new file mode 100644 index 0000000000..444b3cad4d --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_PurificationUnitUVTreatment.java @@ -0,0 +1,524 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.api.enums.GT_HatchElement.InputHatch; +import static gregtech.api.enums.GT_HatchElement.OutputHatch; +import static gregtech.api.enums.GT_Values.AuthorNotAPenguin; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW; +import static gregtech.api.util.GT_RecipeBuilder.SECONDS; +import static gregtech.api.util.GT_StructureUtility.ofFrame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.ForgeDirection; + +import org.jetbrains.annotations.NotNull; + +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.Textures; +import gregtech.api.enums.TierEU; +import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_StructureUtility; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.IGT_HatchAdder; + +public class GT_MetaTileEntity_PurificationUnitUVTreatment + extends GT_MetaTileEntity_PurificationUnitBase<GT_MetaTileEntity_PurificationUnitUVTreatment> + implements ISurvivalConstructable { + + private static final int CASING_INDEX_MAIN = getTextureIndex(GregTech_API.sBlockCasings9, 12); + + private static final String STRUCTURE_PIECE_MAIN = "main"; + private static final int STRUCTURE_X_OFFSET = 6; + private static final int STRUCTURE_Y_OFFSET = 8; + private static final int STRUCTURE_Z_OFFSET = 0; + + private GT_MetaTileEntity_Hatch_InputBus lensInputBus; + private GT_MetaTileEntity_LensIndicator lensIndicator; + + private UVTreatmentLensCycle lensCycle = null; + + /** + * Bonus chance to success for each lens swap + */ + public static final float SUCCESS_PER_LENS = 10.0f; + + /** + * Maximum amount of ticks between two lens swaps + */ + public static final int MAX_TIME_BETWEEN_SWAPS = GT_MetaTileEntity_PurificationPlant.CYCLE_TIME_TICKS / 8; + /** + * Minimum amount of time between two lens swaps + */ + public static final int MIN_TIME_BETWEEN_SWAPS = MAX_TIME_BETWEEN_SWAPS / 4; + + private int numSwapsPerformed = 0; + private int timeUntilNextSwap = 0; + + private boolean removedTooEarly = false; + + private static final String[][] structure = new String[][] { + // spotless:off + { " ", " DDD ", " ", " ", " ", " ", " ", " DDD ", " H~H " }, + { " AAA ", " DDAAADD ", " BBB ", " BBB ", " BBB ", " BBB ", " BBB ", " DDBBBDD ", " AAAAAAA " }, + { " AAAAAAA ", " DDAACCCAADD ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " DDBB BBDD ", " AAAAAAAAAAA " }, + { " AAAAAAAAAAA ", "DAACCCCCCCAAD", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", "DBB BBD", "HAAAAAAAAAAAH" }, + { " AAAAALAAAAA ", "DACCCCCCCCCAD", " B B ", " B B ", " B B ", " B B ", " B B ", "DB BD", "HAAAAAAAAAAAH" }, + { " AAAAAAAAAAA ", "DAACCCCCCCAAD", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", "DBB BBD", "HAAAAAAAAAAAH" }, + { " AAAAAAA ", " DDAACCCAADD ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " BB BB ", " DDBB BBDD ", " AAAAAAAAAAA " }, + { " AIA ", " DDAAADD ", " BBB ", " BBB ", " BBB ", " BBB ", " BBB ", " DDBBBDD ", " AAAAAAA " }, + { " ", " DDD ", " ", " ", " ", " ", " ", " DDD ", " HHH " } }; + // spotless:on + + private static final IStructureDefinition<GT_MetaTileEntity_PurificationUnitUVTreatment> STRUCTURE_DEFINITION = StructureDefinition + .<GT_MetaTileEntity_PurificationUnitUVTreatment>builder() + .addShape(STRUCTURE_PIECE_MAIN, structure) + // Naquadria-Reinforced Water Plant Casing + .addElement('A', ofBlock(GregTech_API.sBlockCasings9, 12)) + // Neutronium-Coated UV-Resistant Glass + .addElement('B', ofBlock(GregTech_API.sBlockGlass1, 1)) + // UV Backlight sterilizer casing + .addElement('C', ofBlock(GregTech_API.sBlockCasings9, 13)) + .addElement('D', ofFrame(Materials.StellarAlloy)) + // Lens housing bus + .addElement( + 'L', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitUVTreatment>buildHatchAdder() + .atLeast(SpecialHatchElement.LensHousing) + .dot(2) + .cacheHint(() -> "Lens Housing") + .casingIndex(CASING_INDEX_MAIN) + .build())) + // Lens indicator hatch + .addElement( + 'I', + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitUVTreatment>buildHatchAdder() + .atLeast(SpecialHatchElement.LensIndicator) + .dot(3) + .cacheHint(() -> "Lens Indicator") + .casingIndex(CASING_INDEX_MAIN) + .build())) + // Input or output hatch + .addElement( + 'H', + ofChain( + lazy( + t -> GT_StructureUtility.<GT_MetaTileEntity_PurificationUnitUVTreatment>buildHatchAdder() + .atLeastList(Arrays.asList(InputHatch, OutputHatch)) + .dot(1) + .cacheHint(() -> "Input Hatch, Output Hatch") + .casingIndex(CASING_INDEX_MAIN) + .build()), + // Naquadria-reinforced Water Plant Casing + ofBlock(GregTech_API.sBlockCasings9, 12))) + .build(); + + public GT_MetaTileEntity_PurificationUnitUVTreatment(int aID, String aName, String aNameRegional) { + super(aID, aName, aNameRegional); + } + + public GT_MetaTileEntity_PurificationUnitUVTreatment(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_PurificationUnitUVTreatment(this.mName); + } + + @Override + public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection side, ForgeDirection facing, + int colorIndex, boolean active, boolean redstoneLevel) { + if (side == facing) { + if (active) return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_ACTIVE_GLOW) + .extFacing() + .glow() + .build() }; + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR) + .extFacing() + .build(), + TextureFactory.builder() + .addIcon(OVERLAY_FRONT_LARGE_CHEMICAL_REACTOR_GLOW) + .extFacing() + .glow() + .build() }; + } + return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX_MAIN) }; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + buildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + hintsOnly, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + return survivialBuildPiece( + STRUCTURE_PIECE_MAIN, + stackSize, + STRUCTURE_X_OFFSET, + STRUCTURE_Y_OFFSET, + STRUCTURE_Z_OFFSET, + elementBudget, + env, + true); + } + + @Override + public IStructureDefinition<GT_MetaTileEntity_PurificationUnitUVTreatment> getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + + @Override + protected GT_Multiblock_Tooltip_Builder createTooltip() { + GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + tt.addMachineType("Purification Unit"); + tt.addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.BOLD + + "Water Tier: " + + EnumChatFormatting.WHITE + + GT_Utility.formatNumbers(getWaterTier()) + + EnumChatFormatting.RESET) + .addInfo("Controller block for the High Energy Laser Purification Unit.") + .addInfo("Must be linked to a Purification Plant to work.") + .addSeparator() + .addInfo( + "During operation, swap the lens in the " + EnumChatFormatting.WHITE + + "Lens Housing" + + EnumChatFormatting.GRAY + + ".") + .addInfo( + "The multiblock will output a signal through the " + EnumChatFormatting.WHITE + "Lens Indicator Hatch") + .addInfo("when the current lens must be swapped.") + .addInfo( + "Lens swaps will be requested in random intervals of " + EnumChatFormatting.RED + + (MIN_TIME_BETWEEN_SWAPS / SECONDS) + + " to " + + (MAX_TIME_BETWEEN_SWAPS / SECONDS) + + "s" + + EnumChatFormatting.GRAY + + ".") + .addSeparator() + .addInfo( + "Success chance is boosted by " + EnumChatFormatting.RED + + SUCCESS_PER_LENS + + "% " + + EnumChatFormatting.GRAY + + "for each successful swap performed.") + .addInfo("Removing a lens too early will fail the recipe.") + .addInfo("Find the order of lenses in the recipe in NEI,") + .addInfo("or use a portable scanner to view the currently requested lens.") + .addSeparator() + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "The sixth step of water purification involves identifying any remaining negatively charged ions within") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "the water which may cause electrical faults in future wafer manufacturing. Bombarding the water with varying") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "wavelengths of photon beams will impart energy into outer-shell electrons, causing them to detach from the") + .addInfo( + EnumChatFormatting.AQUA + "" + + EnumChatFormatting.ITALIC + + "atoms themselves and pass through the walls of the tank, ensuring the water is perfectly electrically polar.") + .addInfo(AuthorNotAPenguin) + .beginStructureBlock(13, 9, 9, true) + .addCasingInfoRangeColored( + "Naquadria-Reinforced Water Plant Casing", + EnumChatFormatting.GRAY, + 147, + 155, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Electron-Permeable Neutronium Coated Glass", + EnumChatFormatting.GRAY, + 144, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "High Energy Ultraviolet Emitter Casing", + EnumChatFormatting.GRAY, + 24, + EnumChatFormatting.GOLD, + false) + .addCasingInfoExactlyColored( + "Stellar Alloy Frame Box", + EnumChatFormatting.GRAY, + 56, + EnumChatFormatting.GOLD, + false) + .addController("Front center") + .addOtherStructurePart("Input Hatch, Output Hatch", EnumChatFormatting.GOLD + "1+", 1) + .addOtherStructurePart("Lens Housing", EnumChatFormatting.GOLD + "1", 2) + .addOtherStructurePart("Lens Indicator", EnumChatFormatting.GOLD + "1", 3) + .addStructureInfo("Use the StructureLib Hologram Projector to build the structure.") + .toolTipFinisher("GregTech"); + return tt; + } + + @Override + public RecipeMap<?> getRecipeMap() { + return RecipeMaps.purificationUVTreatmentRecipes; + } + + @NotNull + @Override + public CheckRecipeResult checkProcessing() { + CheckRecipeResult result = super.checkProcessing(); + if (result.wasSuccessful()) { + // Note that this cast is fine, look at GT_PurifiedWaterRecipes.java + this.lensCycle = new UVTreatmentLensCycle((List<ItemStack>) this.currentRecipe.mSpecialItems); + } + return result; + } + + private int generateNextSwapTime() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + return random.nextInt(MIN_TIME_BETWEEN_SWAPS, MAX_TIME_BETWEEN_SWAPS); + } + + @Override + public void startCycle(int cycleTime, int progressTime) { + super.startCycle(cycleTime, progressTime); + // Reset internal state + this.timeUntilNextSwap = 0; + this.numSwapsPerformed = 0; + this.lensCycle.reset(); + this.removedTooEarly = false; + } + + private ItemStack getCurrentlyInsertedLens() { + return this.lensInputBus.getStackInSlot(0); + } + + @Override + protected void runMachine(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.runMachine(aBaseMetaTileEntity, aTick); + + // Do no processing if no recipe is running + if (mMaxProgresstime <= 0) return; + + // This can happen because the lens cycle isn't saved to NBT correctly yet, FIXME + if (this.lensCycle == null) { + // FIXME: Properly save current recipe in NBT instead of exiting early + return; + } + + ItemStack currentLens = getCurrentlyInsertedLens(); + + // If we are currently counting down to a next swap, do so + if (timeUntilNextSwap > 0) { + timeUntilNextSwap -= 1; + // Set the indicator to not output a signal for now + lensIndicator.updateRedstoneOutput(false); + + // If we are counting down to the next swap, and there is no correct lens in the bus, we removed a lens + // too early + if (currentLens == null || !currentLens.isItemEqual(lensCycle.current())) { + removedTooEarly = true; + } + + // If the time until the next swap became zero, move on to the next requested lens + if (timeUntilNextSwap == 0) { + boolean advanced = lensCycle.advance(); + if (!advanced) { + // cycle didn't advance, we arrived at the end. This mainly means we want to stop the cycle + // The easiest way to do this is by setting the time until next swap larger than the recipe time + timeUntilNextSwap = mMaxProgresstime + 1; + } + } + } + + // Time until next swap is zero, this means we are waiting for the user to output a lens. + else if (timeUntilNextSwap == 0) { + // Set the indicator to output a signal + lensIndicator.updateRedstoneOutput(true); + + // If we now have a matching lens, we can accept it and move on to the next swap + if (currentLens != null && currentLens.isItemEqual(lensCycle.current())) { + numSwapsPerformed += 1; + timeUntilNextSwap = generateNextSwapTime(); + } + } + } + + @Override + public boolean isCorrectMachinePart(ItemStack aStack) { + return true; + } + + @Override + public int getWaterTier() { + return 6; + } + + @Override + public long getBasePowerUsage() { + return TierEU.RECIPE_UV; + } + + @Override + public float calculateFinalSuccessChance() { + if (removedTooEarly) return 0.0f; + return numSwapsPerformed * SUCCESS_PER_LENS + currentRecipeChance; + } + + @Override + public String[] getInfoData() { + ArrayList<String> infoData = new ArrayList<>(Arrays.asList(super.getInfoData())); + if (this.lensCycle != null) { + infoData.add("Lens swaps performed this run: " + EnumChatFormatting.YELLOW + numSwapsPerformed); + infoData.add( + "Current lens requested: " + EnumChatFormatting.GREEN + + lensCycle.current() + .getDisplayName()); + if (removedTooEarly) { + infoData.add("Removed lens too early. Failing this recipe."); + } + } + return infoData.toArray(new String[] {}); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + aNBT.setInteger("numSwapsPerformed", numSwapsPerformed); + aNBT.setInteger("timeUntilNextSwap", timeUntilNextSwap); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + numSwapsPerformed = aNBT.getInteger("numSwapsPerformed"); + timeUntilNextSwap = aNBT.getInteger("timeUntilNextSwap"); + } + + public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { + if (!checkPiece(STRUCTURE_PIECE_MAIN, STRUCTURE_X_OFFSET, STRUCTURE_Y_OFFSET, STRUCTURE_Z_OFFSET)) return false; + return super.checkMachine(aBaseMetaTileEntity, aStack); + } + + public boolean addLensHousingToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity instanceof GT_MetaTileEntity_LensHousing) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + this.lensInputBus = (GT_MetaTileEntity_LensHousing) aMetaTileEntity; + return true; + } + return false; + } + + public boolean addLensIndicatorToMachineList(IGregTechTileEntity aTileEntity, int aBaseCasingIndex) { + if (aTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aTileEntity.getMetaTileEntity(); + if (aMetaTileEntity instanceof GT_MetaTileEntity_LensIndicator) { + ((GT_MetaTileEntity_Hatch) aMetaTileEntity).updateTexture(aBaseCasingIndex); + this.lensIndicator = (GT_MetaTileEntity_LensIndicator) aMetaTileEntity; + lensIndicator.updateRedstoneOutput(false); + return true; + } + return false; + } + + private enum SpecialHatchElement implements IHatchElement<GT_MetaTileEntity_PurificationUnitUVTreatment> { + + LensHousing(GT_MetaTileEntity_PurificationUnitUVTreatment::addLensHousingToMachineList, + GT_MetaTileEntity_LensHousing.class) { + + @Override + public long count( + GT_MetaTileEntity_PurificationUnitUVTreatment gtMetaTileEntityPurificationUnitUVTreatment) { + if (gtMetaTileEntityPurificationUnitUVTreatment.lensInputBus == null) return 0; + else return 1; + } + }, + + LensIndicator(GT_MetaTileEntity_PurificationUnitUVTreatment::addLensIndicatorToMachineList, + GT_MetaTileEntity_LensHousing.class) { + + @Override + public long count( + GT_MetaTileEntity_PurificationUnitUVTreatment gtMetaTileEntityPurificationUnitUVTreatment) { + if (gtMetaTileEntityPurificationUnitUVTreatment.lensIndicator == null) return 0; + else return 1; + } + }; + + private final List<Class<? extends IMetaTileEntity>> mteClasses; + private final IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitUVTreatment> adder; + + @SafeVarargs + SpecialHatchElement(IGT_HatchAdder<GT_MetaTileEntity_PurificationUnitUVTreatment> adder, + Class<? extends IMetaTileEntity>... mteClasses) { + this.mteClasses = Collections.unmodifiableList(Arrays.asList(mteClasses)); + this.adder = adder; + } + + @Override + public List<? extends Class<? extends IMetaTileEntity>> mteClasses() { + return mteClasses; + } + + public IGT_HatchAdder<? super GT_MetaTileEntity_PurificationUnitUVTreatment> adder() { + return adder; + } + } + + @Override + protected ResourceLocation getActivitySoundLoop() { + return SoundResource.IC2_MACHINES_MAGNETIZER_LOOP.resourceLocation; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_pHSensor.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_pHSensor.java new file mode 100644 index 0000000000..6bb4c0952c --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/GT_MetaTileEntity_pHSensor.java @@ -0,0 +1,196 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import net.minecraftforge.common.util.ForgeDirection; + +import com.gtnewhorizons.modularui.api.math.Alignment; +import com.gtnewhorizons.modularui.api.math.Color; +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; +import com.gtnewhorizons.modularui.common.widget.TextWidget; +import com.gtnewhorizons.modularui.common.widget.textfield.NumericWidget; + +import gregtech.api.enums.Textures; +import gregtech.api.gui.modularui.GT_UIInfos; +import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.IIconContainer; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; +import gregtech.api.render.TextureFactory; +import gregtech.api.util.GT_Utility; +import gregtech.common.gui.modularui.widget.CoverCycleButtonWidget; + +public class GT_MetaTileEntity_pHSensor extends GT_MetaTileEntity_Hatch { + + // This implementation was largely copied from the neutron sensor hatch + + protected float threshold = 0; + protected boolean inverted = false; + private boolean isOn = false; + + private static final IIconContainer textureFont = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR; + private static final IIconContainer textureFont_Glow = Textures.BlockIcons.OVERLAY_HATCH_PH_SENSOR_GLOW; + + public GT_MetaTileEntity_pHSensor(int aID, String aName, String aNameRegional, int aTier) { + super(aID, aName, aNameRegional, aTier, 0, "Detects pH value."); + } + + public GT_MetaTileEntity_pHSensor(String aName, int aTier, String[] aDescription, ITexture[][][] aTextures) { + super(aName, aTier, 0, aDescription, aTextures); + } + + @Override + public boolean isValidSlot(int aIndex) { + return false; + } + + @Override + public boolean isSimpleMachine() { + return true; + } + + @Override + public boolean isFacingValid(ForgeDirection facing) { + return true; + } + + @Override + public boolean isAccessAllowed(EntityPlayer aPlayer) { + return true; + } + + @Override + public boolean allowGeneralRedstoneOutput() { + return true; + } + + @Override + public boolean allowPullStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection Side, + ItemStack aStack) { + return false; + } + + @Override + public boolean allowPutStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, ForgeDirection side, + ItemStack aStack) { + return false; + } + + @Override + public void initDefaultModes(NBTTagCompound aNBT) { + getBaseMetaTileEntity().setActive(true); + } + + @Override + public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer, ForgeDirection side, + float aX, float aY, float aZ) { + GT_UIInfos.openGTTileEntityUI(aBaseMetaTileEntity, aPlayer); + return true; + } + + @Override + public boolean useModularUI() { + return true; + } + + @Override + public String[] getDescription() { + return new String[] { "Can be installed in the pH Neutralization Purification Unit.", + "Outputs Redstone Signal according to the current pH value.", + "Right click to open the GUI and change settings." }; + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + threshold = aNBT.getFloat("mThreshold"); + inverted = aNBT.getBoolean("mInverted"); + super.loadNBTData(aNBT); + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + aNBT.setFloat("mThreshold", threshold); + aNBT.setBoolean("mInverted", inverted); + super.saveNBTData(aNBT); + } + + /** + * Updates redstone output strength based on the pH of the multiblock. + */ + public void updateRedstoneOutput(float pH) { + isOn = (pH > threshold) ^ inverted; + } + + @Override + public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + if (isOn) { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setStrongOutputRedstoneSignal(side, (byte) 15); + } + } else { + for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { + aBaseMetaTileEntity.setStrongOutputRedstoneSignal(side, (byte) 0); + } + } + super.onPostTick(aBaseMetaTileEntity, aTick); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new GT_MetaTileEntity_pHSensor(mName, mTier, mDescriptionArray, mTextures); + } + + @Override + public ITexture[] getTexturesActive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont), TextureFactory.builder() + .addIcon(textureFont_Glow) + .glow() + .build() }; + } + + @Override + public ITexture[] getTexturesInactive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(textureFont) }; + } + + public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { + final String INVERTED = GT_Utility.trans("INVERTED", "Inverted"); + final String NORMAL = GT_Utility.trans("NORMAL", "Normal"); + + builder.widget( + new CoverCycleButtonWidget().setToggle(() -> inverted, (val) -> inverted = val) + .setTextureGetter( + (state) -> state == 1 ? GT_UITextures.OVERLAY_BUTTON_REDSTONE_ON + : GT_UITextures.OVERLAY_BUTTON_REDSTONE_OFF) + .addTooltip(0, NORMAL) + .addTooltip(1, INVERTED) + .setPos(10, 8)) + .widget( + new TextWidget().setStringSupplier(() -> inverted ? INVERTED : NORMAL) + .setDefaultColor(COLOR_TEXT_GRAY.get()) + .setTextAlignment(Alignment.CenterLeft) + .setPos(28, 12)) + .widget( + new NumericWidget().setBounds(0, 14.0) + .setGetter(() -> (double) threshold) + .setSetter((value) -> threshold = (float) value) + .setScrollValues(0.1, 0.01, 1.0) + .setMaximumFractionDigits(2) + .setTextColor(Color.WHITE.dark(1)) + .setTextAlignment(Alignment.CenterLeft) + .setFocusOnGuiOpen(true) + .setBackground(GT_UITextures.BACKGROUND_TEXT_FIELD.withOffset(-1, -1, 2, 2)) + .setPos(10, 28) + .setSize(77, 12)) + .widget( + new TextWidget(StatCollector.translateToLocal("GT5U.gui.text.ph_sensor")) + .setDefaultColor(COLOR_TEXT_GRAY.get()) + .setTextAlignment(Alignment.CenterLeft) + .setPos(90, 30)); + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/LinkedPurificationUnit.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/LinkedPurificationUnit.java new file mode 100644 index 0000000000..104cbe300f --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/LinkedPurificationUnit.java @@ -0,0 +1,125 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import static gregtech.GT_Mod.gregtechproxy; + +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.world.World; +import net.minecraftforge.common.DimensionManager; + +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.util.GT_Util; + +/** + * Small wrapper around a GT_MetaTileEntity_PurificationUnitBase, to be stored in the main purification plant + * controller. May be useful for storing additional data in the controller that the individual units do not need + * to know about. + */ +public class LinkedPurificationUnit { + + /** + * Whether this unit is active in the current cycle. We need to keep track of this so units cannot come online + * in the middle of a cycle and suddenly start processing. + */ + private boolean mIsActive = false; + + private final GT_MetaTileEntity_PurificationUnitBase<?> mMetaTileEntity; + + public LinkedPurificationUnit(GT_MetaTileEntity_PurificationUnitBase<?> unit) { + this.mMetaTileEntity = unit; + } + + /** + * Construct new link data from a NBT compound. This is intended to sync the linked units to the client. + * + * @param nbtData NBT data obtained from writeLinkDataToNBT() + */ + public LinkedPurificationUnit(NBTTagCompound nbtData) { + this.mIsActive = nbtData.getBoolean("active"); + NBTTagCompound linkData = nbtData.getCompoundTag("linkData"); + World world = null; + if (!gregtechproxy.isClientSide()) { + world = DimensionManager.getWorld(nbtData.getInteger("worldID")); + } else { + world = Minecraft.getMinecraft().thePlayer.worldObj; + } + + // Load coordinates from link data + int x = linkData.getInteger("x"); + int y = linkData.getInteger("y"); + int z = linkData.getInteger("z"); + + // Find a TileEntity at this location + TileEntity te = GT_Util.getTileEntity(world, x, y, z, true); + if (te == null) { + // This is a bug, throw a fatal error. + throw new NullPointerException("Unit disappeared during server sync. This is a bug."); + } + + // Cast TileEntity to proper GT TileEntity + this.mMetaTileEntity = (GT_MetaTileEntity_PurificationUnitBase<?>) ((IGregTechTileEntity) te) + .getMetaTileEntity(); + } + + public GT_MetaTileEntity_PurificationUnitBase<?> metaTileEntity() { + return mMetaTileEntity; + } + + /** + * Whether this unit is considered as active in the current cycle + * + * @return true if this unit is active in the current cycle + */ + public boolean isActive() { + return mIsActive; + } + + public void setActive(boolean active) { + this.mIsActive = active; + } + + public String getStatusString() { + if (this.isActive()) { + return EnumChatFormatting.GREEN + "Active"; + } + + PurificationUnitStatus status = this.mMetaTileEntity.status(); + switch (status) { + case ONLINE -> { + return EnumChatFormatting.GREEN + "Online"; + } + case DISABLED -> { + return EnumChatFormatting.YELLOW + "Disabled"; + } + case INCOMPLETE_STRUCTURE -> { + return EnumChatFormatting.RED + "Incomplete Structure"; + } + } + + // If this happens, this is a bug and there is a switch case missing. + return null; + } + + /** + * Save link data to a NBT tag, so it can be reconstructed at the client side + */ + public NBTTagCompound writeLinkDataToNBT() { + NBTTagCompound tag = new NBTTagCompound(); + tag.setBoolean("active,", this.mIsActive); + + NBTTagCompound linkData = new NBTTagCompound(); + IGregTechTileEntity mte = this.mMetaTileEntity.getBaseMetaTileEntity(); + linkData.setInteger("x", mte.getXCoord()); + linkData.setInteger("y", mte.getYCoord()); + linkData.setInteger("z", mte.getZCoord()); + tag.setTag("linkData", linkData); + + tag.setInteger( + "worldID", + this.mMetaTileEntity.getBaseMetaTileEntity() + .getWorld().provider.dimensionId); + return tag; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationPlantStructureString.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationPlantStructureString.java new file mode 100644 index 0000000000..7c40f9be53 --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationPlantStructureString.java @@ -0,0 +1,15 @@ +package gregtech.common.tileentities.machines.multi.purification; + +// Extracted to another class to not fill the main source file with bloat +public class PurificationPlantStructureString { + + public static final String[][] STRUCTURE_STRING = new String[][] { + { " ", " ", " ", " GBG ", " GHG ", " GHG ", " G~G ", " GHG ", "AAAAAAA" }, + { " ", " ", " ", " GBG ", "BBBBBBB", "BCCCCCB", "BCCCCCB", "BCCCCCB", "AAAAAAA" }, + { " G G ", " G G ", " G G ", " GGBGG ", "BCCCCCB", "C C", "C C", "C C", "AAAAAAA" }, + { " BBB ", " BBB ", " BBB ", " BBB ", "BCCCCCB", "C C", "D C", "C C", "AAAAAAA" }, + { " BFB ", " BFB ", " BFB ", " BFB ", "BCCCCCB", "C C", "D C", "C C", "AAAAAAA" }, + { " BBB ", " BBB ", " BBB ", " BBB ", "BCCCCCB", "C C", "D ", "C ", "AAAAAAA" }, + { " G G ", " G G ", " G G ", " G G ", "BCCCCCB", "C C", "C C", "C C", "AAAAAAA" }, + { " ", " ", " ", " ", "BBBBBBB", "BCCCCCB", "BCDDDCB", "BCCCCCB", "AAAAAAA" } }; +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationUnitStatus.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationUnitStatus.java new file mode 100644 index 0000000000..403961e28d --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurificationUnitStatus.java @@ -0,0 +1,14 @@ +package gregtech.common.tileentities.machines.multi.purification; + +/** + * Represents the status of a linked purification unit. This is updated by the main purification plant + * controller by checking the linked machines. + */ +public enum PurificationUnitStatus { + // The purification unit is online and ready to work + ONLINE, + // The purification unit is correctly formed, but switched off. + DISABLED, + // The purification unit has failed its structure check + INCOMPLETE_STRUCTURE, +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurifiedWaterHelpers.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurifiedWaterHelpers.java new file mode 100644 index 0000000000..ec25cc394e --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/PurifiedWaterHelpers.java @@ -0,0 +1,35 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import net.minecraftforge.fluids.FluidStack; + +import gregtech.api.enums.Materials; + +public class PurifiedWaterHelpers { + + public static Materials getPurifiedWaterTier(int tier) { + return switch (tier) { + case 1 -> Materials.Grade1PurifiedWater; + case 2 -> Materials.Grade2PurifiedWater; + case 3 -> Materials.Grade3PurifiedWater; + case 4 -> Materials.Grade4PurifiedWater; + case 5 -> Materials.Grade5PurifiedWater; + case 6 -> Materials.Grade6PurifiedWater; + case 7 -> Materials.Grade7PurifiedWater; + case 8 -> Materials.Grade8PurifiedWater; + default -> throw new IllegalStateException("Unexpected value: " + tier); + }; + } + + public static int getWaterTier(FluidStack fluid) { + if (fluid == null) return 0; + else if (fluid.isFluidEqual(Materials.Grade1PurifiedWater.getFluid(1000L))) return 1; + else if (fluid.isFluidEqual(Materials.Grade2PurifiedWater.getFluid(1000L))) return 2; + else if (fluid.isFluidEqual(Materials.Grade3PurifiedWater.getFluid(1000L))) return 3; + else if (fluid.isFluidEqual(Materials.Grade4PurifiedWater.getFluid(1000L))) return 4; + else if (fluid.isFluidEqual(Materials.Grade5PurifiedWater.getFluid(1000L))) return 5; + else if (fluid.isFluidEqual(Materials.Grade6PurifiedWater.getFluid(1000L))) return 6; + else if (fluid.isFluidEqual(Materials.Grade7PurifiedWater.getFluid(1000L))) return 7; + else if (fluid.isFluidEqual(Materials.Grade8PurifiedWater.getFluid(1000L))) return 8; + else return 0; + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/purification/UVTreatmentLensCycle.java b/src/main/java/gregtech/common/tileentities/machines/multi/purification/UVTreatmentLensCycle.java new file mode 100644 index 0000000000..b834cdd0fe --- /dev/null +++ b/src/main/java/gregtech/common/tileentities/machines/multi/purification/UVTreatmentLensCycle.java @@ -0,0 +1,39 @@ +package gregtech.common.tileentities.machines.multi.purification; + +import java.util.List; + +import net.minecraft.item.ItemStack; + +public class UVTreatmentLensCycle { + + private final List<ItemStack> lenses; + + private int currentLens = 0; + + public UVTreatmentLensCycle(List<ItemStack> lenses) { + this.lenses = lenses; + if (lenses.isEmpty()) { + throw new IllegalArgumentException("Supplied lens array may not be empty"); + } + } + + public ItemStack current() { + return lenses.get(currentLens); + } + + public boolean advance() { + if (currentLens < lenses.size() - 1) { + currentLens = currentLens + 1; + return true; + } + return false; + } + + public void reset() { + currentLens = 0; + } + + public ItemStack first() { + return lenses.get(0); + } +} |