diff --git a/build.gradle b/build.gradle index eec495335d1..7fbe01d7d82 100644 --- a/build.gradle +++ b/build.gradle @@ -409,6 +409,7 @@ dependencies { compileOnly("cc.tweaked:cc-tweaked-${previous_minor_minecraft_version}-forge-api:${cc_tweaked_version}") //localRuntime("cc.tweaked:cc-tweaked-${minecraft_version}-forge:${cc_tweaked_version}") compileOnly("curse.maven:female-gender-forge-481655:${wildfire_gender_mod_id}") + compileOnly("curse.maven:framedblocks-441647:${framedblocks_mod_id}") //Dependencies for data generators for mod compat reference datagenMainImplementation("appeng:appliedenergistics2:${ae2_version}") diff --git a/gradle.properties b/gradle.properties index 0dd16e97fe5..68b0dbac295 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,6 +49,7 @@ json_things_version=0.12.1 top_version=1.21_neo-12.0.3-5 wildfire_gender_mod_id=5579007 wthit_version=12.3.0 +framedblocks_mod_id=5629510 #Mod dependency min version ranges diff --git a/src/api/java/mekanism/api/MekanismAPI.java b/src/api/java/mekanism/api/MekanismAPI.java index 4dd8db0fbc3..eb63cae42c4 100644 --- a/src/api/java/mekanism/api/MekanismAPI.java +++ b/src/api/java/mekanism/api/MekanismAPI.java @@ -25,7 +25,7 @@ private MekanismAPI() { /** * The version of the api classes - may not always match the mod's version */ - public static final String API_VERSION = "10.7.0"; + public static final String API_VERSION = "10.7.1"; /** * Mekanism's Mod ID */ diff --git a/src/api/java/mekanism/api/MekanismAPITags.java b/src/api/java/mekanism/api/MekanismAPITags.java index d7c46381108..9977dc1af0e 100644 --- a/src/api/java/mekanism/api/MekanismAPITags.java +++ b/src/api/java/mekanism/api/MekanismAPITags.java @@ -86,6 +86,12 @@ private Chemicals() { * Represents all clean slurries. */ public static final TagKey CLEAN = tag("clean"); + /** + * Chemicals in this tag cannot be inserted into framed blocks + * + * @since 10.7.1 + */ + public static final TagKey FRAMEDBLOCKS_BLACKLISTED = tag("framedblocks_blacklisted"); private static TagKey tag(String name) { return TagKey.create(MekanismAPI.CHEMICAL_REGISTRY_NAME, rl(name)); diff --git a/src/datagen/generated/mekanism/.cache/c10fcd8abbb6a520fc3ac2cf14b627d36958dd55 b/src/datagen/generated/mekanism/.cache/c10fcd8abbb6a520fc3ac2cf14b627d36958dd55 index 6815bf733e3..8709dce3323 100644 --- a/src/datagen/generated/mekanism/.cache/c10fcd8abbb6a520fc3ac2cf14b627d36958dd55 +++ b/src/datagen/generated/mekanism/.cache/c10fcd8abbb6a520fc3ac2cf14b627d36958dd55 @@ -1,5 +1,5 @@ -// 1.21.1 2024-08-20T12:57:24.8675755 Languages: en_us for mod: mekanism +// 1.21.1 2024-08-22T01:36:35.0806327 Languages: en_us for mod: mekanism 7897d55635ae6a20fd1ebc5874196e7b8ba7d726 assets/mekanism/lang/en_au.json 7897d55635ae6a20fd1ebc5874196e7b8ba7d726 assets/mekanism/lang/en_gb.json -8f8d19e7920a6b8c4cf0e3ca073857dffc37c725 assets/mekanism/lang/en_ud.json -da62e93136e15dae97b693358e0b540353701678 assets/mekanism/lang/en_us.json +a9f49da3e6c64b0aa2504689833b449edc12c538 assets/mekanism/lang/en_ud.json +f5236ac92dd9774200d5419f4918582ba7579133 assets/mekanism/lang/en_us.json diff --git a/src/datagen/generated/mekanism/assets/mekanism/lang/en_ud.json b/src/datagen/generated/mekanism/assets/mekanism/lang/en_ud.json index 706b6cbd5d7..d3232f3ffc9 100644 --- a/src/datagen/generated/mekanism/assets/mekanism/lang/en_ud.json +++ b/src/datagen/generated/mekanism/assets/mekanism/lang/en_ud.json @@ -1516,6 +1516,7 @@ "module.mekanism.vein_mining_unit": "ʇᴉu∩ ᵷuᴉuᴉW uᴉǝΛ", "module.mekanism.vision_enhancement": "ʇuǝɯǝɔuɐɥuƎ uoᴉsᴉΛ", "module.mekanism.vision_enhancement_unit": "ʇᴉu∩ ʇuǝɯǝɔuɐɥuƎ uoᴉsᴉΛ", + "msg.mekanism.compat.framedblocks.camo.has_special_handling": "¡sʞɔoꞁq pǝɯɐɹɟ oʇuᴉ pǝʇɹǝsuᴉ ǝq ʇouuɐɔ ᵷuᴉꞁpuɐɥ ꞁɐᴉɔǝds ǝɹᴉnbǝɹ ɥɔᴉɥʍ sꞁɐɔᴉɯǝɥƆ", "multiblock.mekanism.conflict": "ʇɔᴉꞁɟuoƆ", "multiblock.mekanism.formed": "pǝɯɹoℲ", "multiblock.mekanism.formed.chat": "pǝɯɹoℲ ʞɔoꞁqᴉʇꞁnW", @@ -1894,6 +1895,7 @@ "tag.mekanism.chemical.mekanism.clean": "ʎɹɹnꞁS uɐǝꞁƆ", "tag.mekanism.chemical.mekanism.diamond": "puoɯɐᴉᗡ", "tag.mekanism.chemical.mekanism.dirty": "ʎɹɹnꞁS ʎʇɹᴉᗡ", + "tag.mekanism.chemical.mekanism.framedblocks_blacklisted": "pǝʇsᴉꞁʞɔɐꞁᗺ sʞɔoꞁᗺpǝɯɐɹℲ", "tag.mekanism.chemical.mekanism.fungi": "ᴉᵷunℲ", "tag.mekanism.chemical.mekanism.gold": "pꞁo⅁", "tag.mekanism.chemical.mekanism.redstone": "ǝuoʇspǝᴚ", diff --git a/src/datagen/generated/mekanism/assets/mekanism/lang/en_us.json b/src/datagen/generated/mekanism/assets/mekanism/lang/en_us.json index a9ab8b348c6..d46580c1c6c 100644 --- a/src/datagen/generated/mekanism/assets/mekanism/lang/en_us.json +++ b/src/datagen/generated/mekanism/assets/mekanism/lang/en_us.json @@ -1519,6 +1519,7 @@ "module.mekanism.vein_mining_unit": "Vein Mining Unit", "module.mekanism.vision_enhancement": "Vision Enhancement", "module.mekanism.vision_enhancement_unit": "Vision Enhancement Unit", + "msg.mekanism.compat.framedblocks.camo.has_special_handling": "Chemicals which require special handling cannot be inserted into framed blocks!", "multiblock.mekanism.conflict": "Conflict", "multiblock.mekanism.formed": "Formed", "multiblock.mekanism.formed.chat": "Multiblock Formed", @@ -1897,6 +1898,7 @@ "tag.mekanism.chemical.mekanism.clean": "Clean Slurry", "tag.mekanism.chemical.mekanism.diamond": "Diamond", "tag.mekanism.chemical.mekanism.dirty": "Dirty Slurry", + "tag.mekanism.chemical.mekanism.framedblocks_blacklisted": "FramedBlocks Blacklisted", "tag.mekanism.chemical.mekanism.fungi": "Fungi", "tag.mekanism.chemical.mekanism.gold": "Gold", "tag.mekanism.chemical.mekanism.redstone": "Redstone", diff --git a/src/datagen/main/java/mekanism/client/lang/MekanismLangProvider.java b/src/datagen/main/java/mekanism/client/lang/MekanismLangProvider.java index ce46524bae9..86fe24e700e 100644 --- a/src/datagen/main/java/mekanism/client/lang/MekanismLangProvider.java +++ b/src/datagen/main/java/mekanism/client/lang/MekanismLangProvider.java @@ -216,6 +216,8 @@ private void addTags() { addTag(MekanismAPITags.Chemicals.DIRTY, "Dirty Slurry"); addTag(MekanismAPITags.Chemicals.CLEAN, "Clean Slurry"); + + add(MekanismAPITags.Chemicals.FRAMEDBLOCKS_BLACKLISTED, "FramedBlocks Blacklisted"); } private void addItems() { @@ -1722,6 +1724,8 @@ private void addMisc() { add(MekanismModules.MAGNETIC_ATTRACTION_UNIT, "Magnetic Attraction Unit", "Uses powerful magnets to draw distant items towards the player. Install multiple for a greater range."); add(MekanismModules.FROST_WALKER_UNIT, "Frost Walker Unit", "Uses liquid hydrogen to freeze any water the player walks on. Install multiple for a greater range."); add(MekanismModules.SOUL_SURFER_UNIT, "Soul Surfer Unit", "Allows the user to surf effortlessly across the top of souls. Install multiple for a greater speed."); + // FramedBlocks integration + add(MekanismLang.FRAMEDBLOCKS_CAMO_HAS_SPECIAL_HANDLING, "Chemicals which require special handling cannot be inserted into framed blocks!"); } private void addOre(OreType type, String description) { diff --git a/src/main/java/mekanism/common/MekanismLang.java b/src/main/java/mekanism/common/MekanismLang.java index 10d74e78d6f..b7c492de1b1 100644 --- a/src/main/java/mekanism/common/MekanismLang.java +++ b/src/main/java/mekanism/common/MekanismLang.java @@ -841,6 +841,8 @@ public enum MekanismLang implements ILangEntry { MODULE_MAGNETIC_ATTRACTION("module", "magnetic_attraction"), MODULE_MODE_CHANGE("module", "mode_change"), MODULE_VISION_ENHANCEMENT("module", "vision_enhancement"), + //FramedBlocks integration + FRAMEDBLOCKS_CAMO_HAS_SPECIAL_HANDLING("msg", "compat.framedblocks.camo.has_special_handling") ; private final String key; diff --git a/src/main/java/mekanism/common/integration/MekanismHooks.java b/src/main/java/mekanism/common/integration/MekanismHooks.java index a2ea9758c40..8617a5ee490 100644 --- a/src/main/java/mekanism/common/integration/MekanismHooks.java +++ b/src/main/java/mekanism/common/integration/MekanismHooks.java @@ -8,6 +8,7 @@ import mekanism.common.integration.crafttweaker.content.CrTContentUtils; import mekanism.common.integration.curios.CuriosIntegration; import mekanism.common.integration.energy.EnergyCompatUtils; +import mekanism.common.integration.framedblocks.FramedBlocksIntegration; import mekanism.common.integration.jsonthings.JsonThingsIntegration; import mekanism.common.integration.lookingat.theoneprobe.TOPProvider; import mekanism.common.integration.projecte.MekanismNormalizedSimpleStacks; @@ -43,6 +44,7 @@ public final class MekanismHooks { public static final String RECIPE_STAGES_MOD_ID = "recipestages"; public static final String TOP_MOD_ID = "theoneprobe"; public static final String WILDFIRE_GENDER_MOD_ID = "wildfire_gender"; + public static final String FRAMEDBLOCKS_MOD_ID = "framedblocks"; public final boolean CCLoaded; public final boolean CraftTweakerLoaded; @@ -58,6 +60,7 @@ public final class MekanismHooks { public final boolean RecipeStagesLoaded; public final boolean TOPLoaded; public final boolean WildfireGenderModLoaded; + public final boolean FramedBlocksLoaded; public MekanismHooks() { ModList modList = ModList.get(); @@ -77,6 +80,7 @@ public MekanismHooks() { RecipeStagesLoaded = loadedCheck.test(RECIPE_STAGES_MOD_ID); TOPLoaded = loadedCheck.test(TOP_MOD_ID); WildfireGenderModLoaded = loadedCheck.test(WILDFIRE_GENDER_MOD_ID); + FramedBlocksLoaded = loadedCheck.test(FRAMEDBLOCKS_MOD_ID); } public void hookConstructor(final IEventBus bus) { @@ -100,6 +104,9 @@ public void hookConstructor(final IEventBus bus) { if (ProjectELoaded) { MekanismNormalizedSimpleStacks.NSS_SERIALIZERS.register(bus); } + if (FramedBlocksLoaded) { + FramedBlocksIntegration.init(bus); + } } public void hookCapabilityRegistration() { diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoClientHandler.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoClientHandler.java new file mode 100644 index 00000000000..83f0a55f1d1 --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoClientHandler.java @@ -0,0 +1,41 @@ +package mekanism.common.integration.framedblocks; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import mekanism.api.chemical.Chemical; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.neoforged.neoforge.client.ChunkRenderTypeSet; +import net.neoforged.neoforge.client.model.data.ModelData; +import xfacthd.framedblocks.api.camo.CamoClientHandler; +import xfacthd.framedblocks.api.model.util.ModelUtils; + +final class ChemicalCamoClientHandler extends CamoClientHandler { + + static final CamoClientHandler INSTANCE = new ChemicalCamoClientHandler(); + private static final Map CHEMICAL_MODEL_CACHE = new ConcurrentHashMap<>(); + + private ChemicalCamoClientHandler() { } + + @Override + public ChunkRenderTypeSet getRenderTypes(ChemicalCamoContent camo, RandomSource random, ModelData data) { + return ModelUtils.TRANSLUCENT; + } + + @Override + public BakedModel getOrCreateModel(ChemicalCamoContent camo) { + return CHEMICAL_MODEL_CACHE.computeIfAbsent(camo.getChemical(), ChemicalModel::create); + } + + @Override + public Particle makeHitDestroyParticle(ClientLevel level, double x, double y, double z, double sx, double sy, double sz, ChemicalCamoContent camo, BlockPos pos) { + return new ChemicalSpriteParticle(level, x, y, z, sx, sy, sz, camo.getChemical()); + } + + static void clearModelCache() { + CHEMICAL_MODEL_CACHE.clear(); + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainer.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainer.java new file mode 100644 index 00000000000..24790855fec --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainer.java @@ -0,0 +1,50 @@ +package mekanism.common.integration.framedblocks; + +import mekanism.api.chemical.Chemical; +import org.jetbrains.annotations.Nullable; +import xfacthd.framedblocks.api.camo.CamoContainer; +import xfacthd.framedblocks.api.camo.CamoContainerFactory; + +final class ChemicalCamoContainer extends CamoContainer { + + ChemicalCamoContainer(Chemical chemical) { + super(new ChemicalCamoContent(chemical)); + } + + Chemical getChemical() { + return content.getChemical(); + } + + @Override + public boolean canRotateCamo() { + return false; + } + + @Override + @Nullable + public ChemicalCamoContainer rotateCamo() { + return null; + } + + @Override + public int hashCode() { + return content.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != ChemicalCamoContainer.class) return false; + return content.equals(((ChemicalCamoContainer) obj).content); + } + + @Override + public String toString() { + return "ChemicalCamoContainer{" + content + "}"; + } + + @Override + public CamoContainerFactory getFactory() { + return FramedBlocksIntegration.CHEMICAL_FACTORY.get(); + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainerFactory.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainerFactory.java new file mode 100644 index 00000000000..3951d16f5c5 --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContainerFactory.java @@ -0,0 +1,164 @@ +package mekanism.common.integration.framedblocks; + +import com.mojang.serialization.MapCodec; +import mekanism.api.Action; +import mekanism.api.MekanismAPI; +import mekanism.api.MekanismAPITags; +import mekanism.api.chemical.Chemical; +import mekanism.api.chemical.ChemicalStack; +import mekanism.api.chemical.IChemicalHandler; +import mekanism.api.text.TextComponentUtil; +import mekanism.common.MekanismLang; +import mekanism.common.capabilities.Capabilities; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; +import xfacthd.framedblocks.api.camo.CamoContainerFactory; +import xfacthd.framedblocks.api.camo.TriggerRegistrar; +import xfacthd.framedblocks.api.util.CamoMessageVerbosity; +import xfacthd.framedblocks.api.util.ConfigView; + +final class ChemicalCamoContainerFactory extends CamoContainerFactory { + + private static final MapCodec CODEC = Chemical.CODEC + .xmap(ChemicalCamoContainer::new, ChemicalCamoContainer::getChemical) + .fieldOf("chemical"); + private static final StreamCodec STREAM_CODEC = + Chemical.STREAM_CODEC.map(ChemicalCamoContainer::new, ChemicalCamoContainer::getChemical); + private static final Component MSG_HAS_SPECIAL_HANDLING = TextComponentUtil.translate( + MekanismLang.FRAMEDBLOCKS_CAMO_HAS_SPECIAL_HANDLING.getTranslationKey() + ); + + @Override + protected void writeToNetwork(CompoundTag tag, ChemicalCamoContainer camo) { + Chemical chemical = camo.getChemical(); + tag.putInt("chemical", MekanismAPI.CHEMICAL_REGISTRY.getId(chemical)); + } + + @Override + protected ChemicalCamoContainer readFromNetwork(CompoundTag tag) { + Chemical chemical = MekanismAPI.CHEMICAL_REGISTRY.byId(tag.getInt("chemical")); + return new ChemicalCamoContainer(chemical); + } + + @Override + @Nullable + public ChemicalCamoContainer applyCamo(Level level, BlockPos pos, Player player, ItemStack stack) { + IChemicalHandler handler = Capabilities.CHEMICAL.getCapability(stack); + if (handler == null || handler.getChemicalTanks() <= 0) { + return null; + } + + for (int tank = 0; tank < handler.getChemicalTanks(); tank++) { + ChemicalStack chemical = handler.getChemicalInTank(tank); + if (!isValidChemical(chemical.getChemical(), player)) { + continue; + } + + if (!player.isCreative() && ConfigView.Server.INSTANCE.shouldConsumeCamoItem()) { + ChemicalStack extracted = handler.extractChemical(tank, FramedBlocksIntegration.Constants.CHEMICAL_AMOUNT, Action.SIMULATE); + if (extracted.getAmount() != FramedBlocksIntegration.Constants.CHEMICAL_AMOUNT) { + continue; + } + + if (!level.isClientSide()) { + handler.extractChemical(tank, FramedBlocksIntegration.Constants.CHEMICAL_AMOUNT, Action.EXECUTE); + } + } + + return new ChemicalCamoContainer(chemical.getChemical()); + } + return null; + } + + @Override + public boolean removeCamo(Level level, BlockPos pos, Player player, ItemStack stack, ChemicalCamoContainer camo) { + if (stack.isEmpty()) { + return false; + } + + IChemicalHandler handler = Capabilities.CHEMICAL.getCapability(stack); + if (handler == null || handler.getChemicalTanks() <= 0) { + return false; + } + + ChemicalStack chemical = camo.getChemical().getStack(FramedBlocksIntegration.Constants.CHEMICAL_AMOUNT); + if (!isValidForHandler(handler, chemical)) { + return false; + } + if (!player.isCreative() && ConfigView.Server.INSTANCE.shouldConsumeCamoItem()) { + if (!handler.insertChemical(chemical, Action.SIMULATE).isEmpty()) { + return false; + } + if (!level.isClientSide()) { + handler.insertChemical(chemical, Action.EXECUTE); + } + } + return true; + } + + private static boolean isValidForHandler(IChemicalHandler handler, ChemicalStack chemical) { + for (int tank = 0; tank < handler.getChemicalTanks(); tank++) { + if (!handler.isValid(tank, chemical)) { + continue; + } + ChemicalStack inTank = handler.getChemicalInTank(tank); + if (inTank.isEmpty() || inTank.is(chemical.getChemical())) { + return true; + } + } + return false; + } + + @Override + public boolean canTriviallyConvertToItemStack() { + return false; + } + + @Override + public ItemStack dropCamo(ChemicalCamoContainer camo) { + return ItemStack.EMPTY; + } + + @Override + public boolean validateCamo(ChemicalCamoContainer camo) { + return isValidChemical(camo.getChemical(), null); + } + + private static boolean isValidChemical(Chemical chemical, @Nullable Player player) { + if (chemical.isEmptyType()) { + return false; + } + if (chemical.hasAttributesWithValidation()) { + displayValidationMessage(player, MSG_HAS_SPECIAL_HANDLING, CamoMessageVerbosity.DEFAULT); + return false; + } + if (chemical.is(MekanismAPITags.Chemicals.FRAMEDBLOCKS_BLACKLISTED)) { + displayValidationMessage(player, MSG_BLACKLISTED, CamoMessageVerbosity.DEFAULT); + return false; + } + return true; + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } + + @Override + public void registerTriggerItems(TriggerRegistrar registrar) { + registrar.registerApplicationPredicate(Capabilities.CHEMICAL::hasCapability); + registrar.registerRemovalPredicate(Capabilities.CHEMICAL::hasCapability); + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContent.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContent.java new file mode 100644 index 00000000000..f67c4adf801 --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalCamoContent.java @@ -0,0 +1,191 @@ +package mekanism.common.integration.framedblocks; + +import mekanism.api.chemical.Chemical; +import mekanism.common.registration.impl.FluidDeferredRegister; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.MapColor; +import net.neoforged.neoforge.common.util.TriState; +import org.jetbrains.annotations.Nullable; +import xfacthd.framedblocks.api.camo.CamoClientHandler; +import xfacthd.framedblocks.api.camo.CamoContent; + +final class ChemicalCamoContent extends CamoContent { + + private final Chemical chemical; + private final MapColor mapColor; + + ChemicalCamoContent(Chemical chemical) { + this.chemical = chemical; + this.mapColor = FluidDeferredRegister.getClosestColor(chemical.getColorRepresentation()); + } + + Chemical getChemical() { + return chemical; + } + + @Override + public boolean propagatesSkylightDown(BlockGetter level, BlockPos pos) { + return true; + } + + @Override + public float getExplosionResistance(BlockGetter level, BlockPos pos, Explosion explosion) { + return 0; + } + + @Override + public boolean isFlammable(BlockGetter level, BlockPos pos, Direction side) { + return false; + } + + @Override + public int getFlammability(BlockGetter level, BlockPos pos, Direction side) { + return 0; + } + + @Override + public int getFireSpreadSpeed(BlockGetter level, BlockPos pos, Direction side) { + return 0; + } + + @Override + public float getShadeBrightness(BlockGetter level, BlockPos pos, float frameShade) { + return 1F; + } + + @Override + public int getLightEmission() { + // TODO: light level is currently not forwarded from ChemicalConstants to the registered Chemical + return 0; + } + + @Override + public boolean isEmissive() { + return false; + } + + @Override + public SoundType getSoundType() { + return SoundType.WET_GRASS; + } + + @Override + public boolean shouldDisplayFluidOverlay(BlockAndTintGetter level, BlockPos pos, FluidState fluidState) { + return true; + } + + @Override + public float getFriction(LevelReader level, BlockPos pos, @Nullable Entity entity, float frameFriction) { + return frameFriction; + } + + @Override + public TriState canSustainPlant(BlockGetter level, BlockPos pos, Direction side, BlockState plant) { + return TriState.DEFAULT; + } + + @Override + public boolean canEntityDestroy(BlockGetter level, BlockPos pos, Entity entity) { + return true; + } + + @Override + @Nullable + public MapColor getMapColor(BlockGetter level, BlockPos pos) { + return mapColor; + } + + @Override + public int getTintColor(BlockAndTintGetter blockAndTintGetter, BlockPos pos, int tintIdx) { + return chemical.getTint(); + } + + @Override + public Integer getBeaconColorMultiplier(LevelReader levelReader, BlockPos pos, BlockPos beaconPos) { + return chemical.getColorRepresentation(); + } + + @Override + public boolean isSolid(BlockGetter level, BlockPos pos) { + return false; + } + + @Override + public boolean canOcclude() { + return false; + } + + @Override + public BlockState getAsBlockState() { + return Blocks.AIR.defaultBlockState(); + } + + @Override + public BlockState getAppearanceState() { + return Blocks.AIR.defaultBlockState(); + } + + @Override + public boolean isOccludedBy(BlockState adjState, BlockGetter level, BlockPos pos, BlockPos adjPos) { + return adjState.isSolidRender(level, pos); + } + + @Override + public boolean isOccludedBy(CamoContent adjCamo, BlockGetter level, BlockPos pos, BlockPos adjPos) { + return adjCamo.isSolid(level, pos) || equals(adjCamo); + } + + @Override + public boolean occludes(BlockState adjState, BlockGetter level, BlockPos pos, BlockPos adjPos) { + return false; + } + + @Override + public ParticleOptions makeRunningLandingParticles(BlockPos pos) { + return new ChemicalParticleOptions(chemical); + } + + @Override + public String getCamoId() { + return chemical.getRegistryName().toString(); + } + + @Override + public MutableComponent getCamoName() { + return (MutableComponent) chemical.getTextComponent(); + } + + @Override + public CamoClientHandler getClientHandler() { + return ChemicalCamoClientHandler.INSTANCE; + } + + @Override + public int hashCode() { + return chemical.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != ChemicalCamoContent.class) return false; + return chemical == ((ChemicalCamoContent) obj).chemical; + } + + @Override + public String toString() { + return "ChemicalCamoContent{" + chemical.toString() + "}"; + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalModel.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalModel.java new file mode 100644 index 00000000000..e97399d6741 --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalModel.java @@ -0,0 +1,124 @@ +package mekanism.common.integration.framedblocks; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import com.google.common.base.Preconditions; +import com.mojang.math.Transformation; +import mekanism.api.chemical.Chemical; +import mekanism.common.Mekanism; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.ChunkRenderTypeSet; +import net.neoforged.neoforge.client.model.SimpleModelState; +import net.neoforged.neoforge.client.model.data.ModelData; +import org.jetbrains.annotations.Nullable; + +final class ChemicalModel implements BakedModel { + + private static final ModelState SIMPLE_STATE = new SimpleModelState(Transformation.identity()); + static final ModelResourceLocation BARE_MODEL = ModelResourceLocation.standalone(FramedBlocksIntegration.Constants.CHEMICAL_DUMMY_MODEL); + + private final RenderType renderType; + private final ChunkRenderTypeSet renderTypeSet; + private final Map> quads; + private final TextureAtlasSprite particles; + + private ChemicalModel(RenderType renderType, Map> quads, TextureAtlasSprite particles) { + this.renderType = renderType; + this.renderTypeSet = ChunkRenderTypeSet.of(renderType); + this.quads = quads; + this.particles = particles; + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource random) { + return getQuads(state, side, random, ModelData.EMPTY, RenderType.translucent()); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, RenderType layer) { + return (side == null || layer != renderType) ? Collections.emptyList() : quads.get(side); + } + + @Override + public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { + return renderTypeSet; + } + + @Override + public boolean useAmbientOcclusion() { + return false; + } + + @Override + public boolean isGui3d() { + return false; + } + + @Override + public boolean usesBlockLight() { + return false; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return particles; + } + + @Override + public ItemOverrides getOverrides() { + return ItemOverrides.EMPTY; + } + + static ChemicalModel create(Chemical chemical) { + ModelBakery modelBakery = Minecraft.getInstance().getModelManager().getModelBakery(); + UnbakedModel bareModel = modelBakery.getModel(BARE_MODEL.id()); + Preconditions.checkNotNull(bareModel, "Bare chemical model not loaded!"); + + ModelResourceLocation modelName = new ModelResourceLocation( + Mekanism.rl("chemical/" + chemical.getRegistryName().toString().replace(":", "_")), + "mekanism_framedblocks_dynamic_chemical" + ); + TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(chemical.getIcon()); + BakedModel model = bareModel.bake( + modelBakery.new ModelBakerImpl( + (modelLoc, material) -> sprite, + modelName + ), + loc -> sprite, + SIMPLE_STATE + ); + Preconditions.checkNotNull(model, "Failed to bake chemical model for chemical %s", chemical); + + Map> quads = new EnumMap<>(Direction.class); + RandomSource random = RandomSource.create(); + RenderType layer = RenderType.translucent(); + + for (Direction side : Direction.values()) + { + quads.put(side, model.getQuads(Blocks.AIR.defaultBlockState(), side, random, ModelData.EMPTY, layer)); + } + + return new ChemicalModel(layer, quads, sprite); + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalParticleOptions.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalParticleOptions.java new file mode 100644 index 00000000000..f21c43a7ab9 --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalParticleOptions.java @@ -0,0 +1,39 @@ +package mekanism.common.integration.framedblocks; + +import com.mojang.serialization.MapCodec; +import mekanism.api.chemical.Chemical; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; + +record ChemicalParticleOptions(Chemical chemical) implements ParticleOptions { + + static final MapCodec CODEC = Chemical.CODEC + .xmap(ChemicalParticleOptions::new, ChemicalParticleOptions::chemical) + .fieldOf("chemical"); + static final StreamCodec STREAM_CODEC = + Chemical.STREAM_CODEC.map(ChemicalParticleOptions::new, ChemicalParticleOptions::chemical); + + @Override + public ParticleType getType() { + return FramedBlocksIntegration.CHEMICAL_PARTICLE.value(); + } + + static final class Type extends ParticleType { + + Type() { + super(false); + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/ChemicalSpriteParticle.java b/src/main/java/mekanism/common/integration/framedblocks/ChemicalSpriteParticle.java new file mode 100644 index 00000000000..248f1db427e --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/ChemicalSpriteParticle.java @@ -0,0 +1,80 @@ +package mekanism.common.integration.framedblocks; + +import mekanism.api.chemical.Chemical; +import mekanism.client.render.MekanismRenderer; +import mekanism.common.util.WorldUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.TextureSheetParticle; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.core.BlockPos; + +final class ChemicalSpriteParticle extends TextureSheetParticle { + + private final BlockPos pos; + private final float uo; + private final float vo; + private final int brightness; + + ChemicalSpriteParticle(ClientLevel level, double x, double y, double z, double sx, double sy, double sz, Chemical chemical) { + super(level, x, y, z, sx, sy, sz); + this.pos = BlockPos.containing(x, y, z); + this.gravity = 1F; + this.quadSize /= 2F; + this.uo = random.nextFloat() * 3F; + this.vo = random.nextFloat() * 3F; + this.brightness = 0; + + int tint = chemical.getTint(); + this.rCol = .6F * MekanismRenderer.getRed(tint); + this.gCol = .6F * MekanismRenderer.getGreen(tint); + this.bCol = .6F * MekanismRenderer.getBlue(tint); + + setSprite(Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(chemical.getIcon())); + } + + @Override + public ParticleRenderType getRenderType() { + return ParticleRenderType.TERRAIN_SHEET; + } + + @Override + protected float getU0() { + return sprite.getU((uo + 1.0F) / 4.0F); + } + + @Override + protected float getU1() { + return sprite.getU(uo / 4.0F); + } + + @Override + protected float getV0() { + return sprite.getV(vo / 4.0F); + } + + @Override + protected float getV1() { + return sprite.getV((vo + 1.0F) / 4.0F); + } + + @Override + public int getLightColor(float partialTick) { + int light = WorldUtils.isChunkLoaded(level, pos) ? LevelRenderer.getLightColor(level, pos) : 0; + int block = Math.max(brightness, LightTexture.block(light)); + return LightTexture.pack(block, LightTexture.sky(light)); + } + + static final class Provider implements ParticleProvider { + + @Override + public Particle createParticle(ChemicalParticleOptions type, ClientLevel level, double x, double y, double z, double sx, double sy, double sz) { + return new ChemicalSpriteParticle(level, x, y, z, sx, sy, sz, type.chemical()); + } + } +} diff --git a/src/main/java/mekanism/common/integration/framedblocks/FramedBlocksIntegration.java b/src/main/java/mekanism/common/integration/framedblocks/FramedBlocksIntegration.java new file mode 100644 index 00000000000..f0c490d668e --- /dev/null +++ b/src/main/java/mekanism/common/integration/framedblocks/FramedBlocksIntegration.java @@ -0,0 +1,72 @@ +package mekanism.common.integration.framedblocks; + +import mekanism.common.Mekanism; +import mekanism.common.registration.impl.ParticleTypeDeferredRegister; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.client.event.ModelEvent; +import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; +import net.neoforged.neoforge.fluids.FluidType; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import xfacthd.framedblocks.api.camo.CamoContainerFactory; +import xfacthd.framedblocks.api.util.FramedConstants; + +public final class FramedBlocksIntegration { + + private static final DeferredRegister> CAMO_FACTORIES = DeferredRegister.create( + FramedConstants.CAMO_CONTAINER_FACTORY_REGISTRY_KEY, + Mekanism.MODID + ); + private static final ParticleTypeDeferredRegister PARTICLE_TYPES = new ParticleTypeDeferredRegister(Mekanism.MODID); + + static final DeferredHolder, ChemicalCamoContainerFactory> CHEMICAL_FACTORY = + CAMO_FACTORIES.register("chemical", ChemicalCamoContainerFactory::new); + static final DeferredHolder, ChemicalParticleOptions.Type> CHEMICAL_PARTICLE = + PARTICLE_TYPES.register("chemical", ChemicalParticleOptions.Type::new); + + public static void init(IEventBus modBus) { + CAMO_FACTORIES.register(modBus); + PARTICLE_TYPES.register(modBus); + + if (FMLEnvironment.dist.isClient()) { + ClientEvents.init(modBus); + } + } + + public static final class Constants { + + /** + * The amount of a given chemical to consume when applying it to a framed block + */ + public static final int CHEMICAL_AMOUNT = FluidType.BUCKET_VOLUME; + /** + * A dummy model used for generating a baked model from a given chemical's texture + * when applying it as a camo to a framed block + */ + public static final ResourceLocation CHEMICAL_DUMMY_MODEL = Mekanism.rl("chemical/dummy"); + } + + static final class ClientEvents { + + static void init(IEventBus modBus) { + modBus.addListener(ClientEvents::onRegisterAdditionalModels); + modBus.addListener(ClientEvents::onModelLoadingCompleted); + modBus.addListener(ClientEvents::onRegisterParticleProviders); + } + + private static void onRegisterAdditionalModels(ModelEvent.RegisterAdditional event) { + event.register(ChemicalModel.BARE_MODEL); + } + + private static void onModelLoadingCompleted(ModelEvent.BakingCompleted event) { + ChemicalCamoClientHandler.clearModelCache(); + } + + private static void onRegisterParticleProviders(RegisterParticleProvidersEvent event) { + event.registerSpecial(FramedBlocksIntegration.CHEMICAL_PARTICLE.get(), new ChemicalSpriteParticle.Provider()); + } + } +} diff --git a/src/main/java/mekanism/common/registration/impl/FluidDeferredRegister.java b/src/main/java/mekanism/common/registration/impl/FluidDeferredRegister.java index 17469cadbba..234f2e6ff58 100644 --- a/src/main/java/mekanism/common/registration/impl/FluidDeferredRegister.java +++ b/src/main/java/mekanism/common/registration/impl/FluidDeferredRegister.java @@ -137,7 +137,7 @@ public FluidRegistryObject(fluidType, stillFluid, flowingFluid, bucket, block); } - private static MapColor getClosestColor(int tint) { + public static MapColor getClosestColor(int tint) { if (tint == 0xFFFFFFFF) { return MapColor.NONE; } diff --git a/src/main/resources/assets/mekanism/models/chemical/dummy.json b/src/main/resources/assets/mekanism/models/chemical/dummy.json new file mode 100644 index 00000000000..aca1c478053 --- /dev/null +++ b/src/main/resources/assets/mekanism/models/chemical/dummy.json @@ -0,0 +1,21 @@ +{ + "parent": "minecraft:block/block", + "textures": { + "all": "minecraft:block/water_still", + "particle": "#all" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "down": { "texture": "#all", "tintindex": 0, "cullface": "down" }, + "up": { "texture": "#all", "tintindex": 0, "cullface": "up" }, + "north": { "texture": "#all", "tintindex": 0, "cullface": "north" }, + "south": { "texture": "#all", "tintindex": 0, "cullface": "south" }, + "west": { "texture": "#all", "tintindex": 0, "cullface": "west" }, + "east": { "texture": "#all", "tintindex": 0, "cullface": "east" } + } + } + ] +}