From 118499bca28aa142a9901a57308619c90274eb4f Mon Sep 17 00:00:00 2001 From: kd8lvt Date: Wed, 9 Oct 2024 23:08:54 -0400 Subject: [PATCH] Feat: Bulk Digestion in Gastric Acid Implement requested changes Note: Particles (even Vanilla ones) don't render under/through Acid without Fabulous graphics enabled. Likely related to [#151](https://github.com/Elenterius/Biomancy/issues/151) --- .../particles/ModParticleSpriteProvider.java | 1 + .../datagen/tags/ModItemTagsProvider.java | 5 + .../client/particle/ParticleProviders.java | 14 ++ .../client/particle/VanillaDripParticle.java | 37 ++++++ .../biomancy/init/AcidInteractions.java | 123 +++++++++++------- .../init/client/ClientSetupHandler.java | 1 + .../biomancy/init/tags/ModItemTags.java | 2 + .../biomancy/mixin/ItemEntityMixin.java | 17 +-- 8 files changed, 138 insertions(+), 62 deletions(-) diff --git a/src/datagen/java/com/github/elenterius/biomancy/datagen/particles/ModParticleSpriteProvider.java b/src/datagen/java/com/github/elenterius/biomancy/datagen/particles/ModParticleSpriteProvider.java index 002be4c5f..d3fe91567 100644 --- a/src/datagen/java/com/github/elenterius/biomancy/datagen/particles/ModParticleSpriteProvider.java +++ b/src/datagen/java/com/github/elenterius/biomancy/datagen/particles/ModParticleSpriteProvider.java @@ -24,6 +24,7 @@ public void registerParticles() { addParticle(ModParticleTypes.LIGHT_GREEN_GLOW, "minecraft:glow"); addParticle(ModParticleTypes.HOSTILE, "biomancy:hostile"); addParticle(ModParticleTypes.BIOHAZARD, "biomancy:biohazard"); + addParticle(ModParticleTypes.ACID_BUBBLE, "biomancy:acid_bubble"); } } diff --git a/src/datagen/java/com/github/elenterius/biomancy/datagen/tags/ModItemTagsProvider.java b/src/datagen/java/com/github/elenterius/biomancy/datagen/tags/ModItemTagsProvider.java index 0967b01c9..5a7c4e10d 100644 --- a/src/datagen/java/com/github/elenterius/biomancy/datagen/tags/ModItemTagsProvider.java +++ b/src/datagen/java/com/github/elenterius/biomancy/datagen/tags/ModItemTagsProvider.java @@ -108,6 +108,11 @@ private void addBiomancyTags() { .addTag(Tags.Items.ARMORS, Tags.Items.TOOLS) .addTag(Tags.Items.ORES_NETHERITE_SCRAP, Tags.Items.INGOTS_NETHERITE, Tags.Items.STORAGE_BLOCKS_NETHERITE) .addTag(forgeTag("shulker_boxes")); + + createTag(ModItemTags.CANNOT_BE_DIGESTED_IN_ACID) + .add(ModItems.NUTRIENT_PASTE.get()) + .add(ModItems.NUTRIENT_BAR.get()) + .add(ModItems.LIVING_FLESH.get()); } private void addMinecraftTags() { diff --git a/src/main/java/com/github/elenterius/biomancy/client/particle/ParticleProviders.java b/src/main/java/com/github/elenterius/biomancy/client/particle/ParticleProviders.java index 164b28571..14c462545 100644 --- a/src/main/java/com/github/elenterius/biomancy/client/particle/ParticleProviders.java +++ b/src/main/java/com/github/elenterius/biomancy/client/particle/ParticleProviders.java @@ -64,4 +64,18 @@ public Particle createParticle(SimpleParticleType type, ClientLevel level, doubl } } + @OnlyIn(Dist.CLIENT) + public static class AcidBubbleProvider implements ParticleProvider { + protected final SpriteSet sprite; + + public AcidBubbleProvider(SpriteSet sprites) { + sprite = sprites; + } + + public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { + VanillaDripParticle.AcidBubbleParticle particle = new VanillaDripParticle.AcidBubbleParticle(level, x, y, z, xSpeed, ySpeed, zSpeed); + particle.pickSprite(sprite); + return particle; + } + } } diff --git a/src/main/java/com/github/elenterius/biomancy/client/particle/VanillaDripParticle.java b/src/main/java/com/github/elenterius/biomancy/client/particle/VanillaDripParticle.java index cc330d292..d2ed45b5a 100644 --- a/src/main/java/com/github/elenterius/biomancy/client/particle/VanillaDripParticle.java +++ b/src/main/java/com/github/elenterius/biomancy/client/particle/VanillaDripParticle.java @@ -1,5 +1,6 @@ package com.github.elenterius.biomancy.client.particle; +import com.github.elenterius.biomancy.init.ModFluids; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.particle.ParticleRenderType; import net.minecraft.client.particle.TextureSheetParticle; @@ -188,4 +189,40 @@ protected void postMoveUpdate() { } } + @OnlyIn(Dist.CLIENT) + protected static class AcidBubbleParticle extends TextureSheetParticle { //Almost an exact copy of the vanilla BubbleParticle because it can't be extended + AcidBubbleParticle(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { + super(level, x, y, z); + this.setSize(0.02F, 0.02F); + this.quadSize *= this.random.nextFloat() * 0.6F + 0.2F; + this.xd = xSpeed * (double)0.2F + (Math.random() * 2.0D - 1.0D) * (double)0.02F; + this.yd = ySpeed * (double)0.2F + (Math.random() * 2.0D - 1.0D) * (double)0.02F; + this.zd = zSpeed * (double)0.2F + (Math.random() * 2.0D - 1.0D) * (double)0.02F; + this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D)); + } + + public void tick() { + this.xo = this.x; + this.yo = this.y; + this.zo = this.z; + if (this.lifetime-- <= 0) { + this.remove(); + } else { + this.yd += 0.002D; + this.move(this.xd, this.yd, this.zd); + this.xd *= 0.85F; + this.yd *= 0.85F; + this.zd *= 0.85F; + if (!this.level.getFluidState(BlockPos.containing(this.x, this.y, this.z)).is(ModFluids.ACID.get())) { + this.remove(); + } + + } + } + + public ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_OPAQUE; + } + } + } diff --git a/src/main/java/com/github/elenterius/biomancy/init/AcidInteractions.java b/src/main/java/com/github/elenterius/biomancy/init/AcidInteractions.java index 9c69235e5..3178f98e2 100644 --- a/src/main/java/com/github/elenterius/biomancy/init/AcidInteractions.java +++ b/src/main/java/com/github/elenterius/biomancy/init/AcidInteractions.java @@ -3,6 +3,7 @@ import com.github.elenterius.biomancy.block.digester.DigesterBlockEntity; import com.github.elenterius.biomancy.crafting.recipe.DigestingRecipe; import com.github.elenterius.biomancy.init.tags.ModBlockTags; +import com.github.elenterius.biomancy.init.tags.ModItemTags; import com.github.elenterius.biomancy.inventory.BehavioralInventory; import com.github.elenterius.biomancy.util.CombatUtil; import net.minecraft.core.Direction; @@ -10,6 +11,7 @@ import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -32,7 +34,6 @@ import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.SoundActions; -import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.annotations.Nullable; import java.util.Map; @@ -166,59 +167,85 @@ else if (livingEntity.tickCount % 10 == 0 && livingEntity.getRandom().nextFloat( } } - private static final String TIMER_KEY = "biomancy:digestion_timer"; - private static final float EFFICIENCY = 0.8f; - public static void tryDigest(ItemEntity itemEntity, boolean onClient) { - DigestingRecipe recipe = getDigestionRecipe(itemEntity); - if (!digestible(itemEntity,recipe)) return; - CompoundTag data = itemEntity.getPersistentData(); - if (data.contains(TIMER_KEY) && data.getInt(TIMER_KEY) < 10) { - data.putInt(TIMER_KEY, data.getInt(TIMER_KEY) + 1); - } else if (data.contains(TIMER_KEY) && data.getInt(TIMER_KEY) >= 10 && !onClient){ - digestIntoNutrientPasteStacks(itemEntity,recipe); - itemEntity.getPersistentData().remove(TIMER_KEY); - } else { - data.putInt(TIMER_KEY,1); + public static final class InWorldItemDigesting { + //NBT Tag keys + public static final String BASE_DATA_KEY = "biomancy:acid_digestion"; + public static final String TIMER_KEY = "timer"; + public static final String RECIPE_KEY = "recipe"; + //Balancing multiplier applied to the output of any found recipes. + public static final double EFFICIENCY = 0.8; + //How many times tryDigestSubmergedItem should be called on a digestible item before it is processed. + //This is not directly ticks, as submerged items are only actually processed every 10 ticks. + public static final int DELAY = 10; + + public static void tryDigestSubmergedItem(ItemEntity itemEntity) { + if (!digestible(itemEntity)) return; + ItemStack stack = itemEntity.getItem(); + CompoundTag entityData = itemEntity.getPersistentData(); + CompoundTag digestionData = getOrCreateDigestionData(entityData); + if (itemEntity.level().isClientSide && digestionData.getInt(TIMER_KEY) > 0) { + Vec3 pos = itemEntity.position(); + RandomSource random = itemEntity.level().getRandom(); + itemEntity.level().addParticle(ModParticleTypes.ACID_BUBBLE.get(),pos.x,pos.y,pos.z,random.nextGaussian()/100,Math.abs(random.nextGaussian()/50),random.nextGaussian()/100); + itemEntity.level().addParticle(ParticleTypes.SMOKE,pos.x,pos.y,pos.z,random.nextGaussian()/100,Math.abs(random.nextGaussian()/100),random.nextGaussian()/100); + return; + } + + if (itemEntity.getAge() % 10 != 0) return; //Only tick digestion every 10 ticks. + Optional optionalRecipe = getDigestionRecipe(itemEntity, stack, digestionData); + + if (optionalRecipe.isEmpty()) return; + if (optionalRecipe.get().getId() != ResourceLocation.tryParse(digestionData.getString(RECIPE_KEY))) { + digestionData.putString(RECIPE_KEY, optionalRecipe.get().getId().toString()); + } + + int currentDigestionTime = digestionData.getInt(TIMER_KEY); + if (currentDigestionTime < DELAY) { + digestionData.putInt(TIMER_KEY, currentDigestionTime + 1); + entityData.put(BASE_DATA_KEY,digestionData); + } else { + digestIntoNutrientPasteStacks(itemEntity, optionalRecipe.get()); + itemEntity.getPersistentData().remove(BASE_DATA_KEY); + } } - } - private static void digestIntoNutrientPasteStacks(ItemEntity itemEntity, DigestingRecipe recipe) { - BehavioralInventory tempInventory = BehavioralInventory.createServerContents(1,player->false,()->{}); - tempInventory.insertItemStack(itemEntity.getItem()); - ItemStack resultStack = recipe.assemble(tempInventory,itemEntity.level().registryAccess()); - int totalToOutput = (int)Math.floor(resultStack.getCount()*itemEntity.getItem().getCount()*EFFICIENCY); - if (totalToOutput > 64) totalToOutput = splitIntoStacks(itemEntity,totalToOutput); - itemEntity.setItem(new ItemStack(ModItems.NUTRIENT_PASTE.get(), totalToOutput)); - itemEntity.playSound(SoundEvents.PLAYER_BURP); - } + public static CompoundTag getOrCreateDigestionData(CompoundTag entityData) { + if (entityData.contains(BASE_DATA_KEY)) return entityData.getCompound(BASE_DATA_KEY); + return new CompoundTag(); + } - private static int splitIntoStacks(ItemEntity itemEntity, int numToSplit) { - Level level = itemEntity.level(); - while (numToSplit > 64) { - DefaultDispenseItemBehavior.spawnItem(level,new ItemStack(ModItems.NUTRIENT_PASTE.get(),64),1, Direction.UP, itemEntity.position()); - numToSplit -= 64; + public static void digestIntoNutrientPasteStacks(ItemEntity itemEntity, DigestingRecipe recipe) { + BehavioralInventory tempInventory = BehavioralInventory.createServerContents(1, player -> false, () -> {}); + tempInventory.insertItemStack(itemEntity.getItem()); + ItemStack resultStack = recipe.assemble(tempInventory, itemEntity.level().registryAccess()); + int totalToOutput = (int) Math.floor(resultStack.getCount() * itemEntity.getItem().getCount() * EFFICIENCY); + if (totalToOutput > 64) totalToOutput = splitIntoStacks(itemEntity, totalToOutput); + itemEntity.setItem(new ItemStack(ModItems.NUTRIENT_PASTE.get(), totalToOutput)); + itemEntity.playSound(SoundEvents.PLAYER_BURP); } - return numToSplit; - } + public static int splitIntoStacks(ItemEntity itemEntity, int numToSplit) { + Level level = itemEntity.level(); + while (numToSplit > 64) { + DefaultDispenseItemBehavior.spawnItem(level, new ItemStack(ModItems.NUTRIENT_PASTE.get(), 64), 1, Direction.UP, itemEntity.position()); + numToSplit -= 64; + } + return numToSplit; + } - @SuppressWarnings("RedundantIfStatement") - private static boolean digestible(ItemEntity itemEntity, @Nullable DigestingRecipe recipe) { - if (recipe == null) return false; - if (!itemEntity.isInFluidType(ModFluids.ACID_TYPE.get()) && !itemEntity.level().getBlockState(itemEntity.blockPosition()).is(ModBlocks.ACID_CAULDRON.get())) return false; - //Inside method to prevent missing registry object errors during init - Item[] blacklistedItems = {ModItems.NUTRIENT_PASTE.get(),ModItems.NUTRIENT_BAR.get(),ModItems.LIVING_FLESH.get()}; - if (ArrayUtils.contains(blacklistedItems,itemEntity.getItem().getItem())) return false; - return true; - } + @SuppressWarnings("RedundantIfStatement") //Let me write readable code please, thanks + public static boolean digestible(ItemEntity itemEntity) { + if (!itemEntity.isInFluidType(ModFluids.ACID_TYPE.get()) && !itemEntity.level().getBlockState(itemEntity.blockPosition()).is(ModBlocks.ACID_CAULDRON.get())) return false; + if (itemEntity.getItem().is(ModItemTags.CANNOT_BE_DIGESTED_IN_ACID)) return false; + return true; + } - private static DigestingRecipe cachedRecipe = null; - private static @Nullable DigestingRecipe getDigestionRecipe(ItemEntity itemEntity) { - if (cachedRecipe != null && cachedRecipe.getIngredient().test(itemEntity.getItem())) return cachedRecipe; - Optional foundRecipe = DigesterBlockEntity.RECIPE_TYPE.get().getRecipeForIngredient(itemEntity.level(), itemEntity.getItem()); - if (foundRecipe.isEmpty()) return null; - cachedRecipe = foundRecipe.get(); - return cachedRecipe; + private static Optional getDigestionRecipe(ItemEntity itemEntity, ItemStack stack, CompoundTag digestionData) { + Optional recipe = Optional.empty(); + ResourceLocation lastRecipeId = ResourceLocation.tryParse(digestionData.getString(RECIPE_KEY)); + if (lastRecipeId != null) recipe = DigesterBlockEntity.RECIPE_TYPE.get().getRecipeById(itemEntity.level(), lastRecipeId); + if (recipe.isEmpty()) recipe = DigesterBlockEntity.RECIPE_TYPE.get().getRecipeForIngredient(itemEntity.level(), stack); + return recipe; //If it's still empty, there's no recipe that matches the item stack. + } } - } diff --git a/src/main/java/com/github/elenterius/biomancy/init/client/ClientSetupHandler.java b/src/main/java/com/github/elenterius/biomancy/init/client/ClientSetupHandler.java index 5afebb12e..a8dea9893 100644 --- a/src/main/java/com/github/elenterius/biomancy/init/client/ClientSetupHandler.java +++ b/src/main/java/com/github/elenterius/biomancy/init/client/ClientSetupHandler.java @@ -149,6 +149,7 @@ public static void registerParticles(final RegisterParticleProvidersEvent event) event.registerSpriteSet(ModParticleTypes.LIGHT_GREEN_GLOW.get(), sprites -> new CustomGlowParticle.TwoColorProvider(sprites, 0x53ff53, 0x64e986)); event.registerSpriteSet(ModParticleTypes.HOSTILE.get(), CustomGlowParticle.GenericProvider::new); event.registerSpriteSet(ModParticleTypes.BIOHAZARD.get(), sprites -> new CustomGlowParticle.TwoColorProvider(sprites, 0xab274f, 0x7e2a43)); + event.registerSpriteSet(ModParticleTypes.ACID_BUBBLE.get(), ParticleProviders.AcidBubbleProvider::new); } @SubscribeEvent diff --git a/src/main/java/com/github/elenterius/biomancy/init/tags/ModItemTags.java b/src/main/java/com/github/elenterius/biomancy/init/tags/ModItemTags.java index 9afd06e5d..0a1be5cd5 100644 --- a/src/main/java/com/github/elenterius/biomancy/init/tags/ModItemTags.java +++ b/src/main/java/com/github/elenterius/biomancy/init/tags/ModItemTags.java @@ -19,6 +19,8 @@ public final class ModItemTags { public static final TagKey SUGARS = tag("sugars"); + public static final TagKey CANNOT_BE_DIGESTED_IN_ACID = tag("cannot_be_digested_in_acid"); + private ModItemTags() {} private static TagKey tag(String name) { diff --git a/src/main/java/com/github/elenterius/biomancy/mixin/ItemEntityMixin.java b/src/main/java/com/github/elenterius/biomancy/mixin/ItemEntityMixin.java index 8b4a261cb..b7437583d 100644 --- a/src/main/java/com/github/elenterius/biomancy/mixin/ItemEntityMixin.java +++ b/src/main/java/com/github/elenterius/biomancy/mixin/ItemEntityMixin.java @@ -1,10 +1,7 @@ package com.github.elenterius.biomancy.mixin; import com.github.elenterius.biomancy.init.AcidInteractions; -import com.github.elenterius.biomancy.init.ModParticleTypes; -import net.minecraft.util.RandomSource; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -14,16 +11,8 @@ public abstract class ItemEntityMixin { @Inject(at=@At("TAIL"),method={"tick()V"}) private void onTick(CallbackInfo ci) { - ItemEntity self = (ItemEntity)((Object)this); //I hate casting like this on so many levels - if (self.isRemoved()) return; - boolean onClient = self.level().isClientSide(); - - if (onClient) { - Vec3 pos = self.position(); - RandomSource random = self.level().getRandom(); - self.level().addParticle(ModParticleTypes.ACID_BUBBLE.get(), pos.x + random.nextGaussian(), pos.y, pos.z + random.nextGaussian(), random.nextGaussian(), 0.1, random.nextGaussian()); - } - if (self.getAge() % 10 != 0) return; //Only fire once every 10 ticks - AcidInteractions.tryDigest(self,onClient); + ItemEntity itemEntity = (ItemEntity)((Object)this); //I hate casting like this on so many levels -Kd + if (itemEntity.isRemoved()) return; + AcidInteractions.InWorldItemDigesting.tryDigestSubmergedItem(itemEntity); } } \ No newline at end of file