Skip to content

Commit

Permalink
Feat: Bulk Digestion in Gastric Acid
Browse files Browse the repository at this point in the history
Implement requested changes
Note: Particles (even Vanilla ones) don't render under/through Acid without Fabulous graphics enabled. Likely related to [Elenterius#151](Elenterius#151)
  • Loading branch information
kd8lvt committed Oct 10, 2024
1 parent 2d83925 commit 118499b
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,18 @@ public Particle createParticle(SimpleParticleType type, ClientLevel level, doubl
}
}

@OnlyIn(Dist.CLIENT)
public static class AcidBubbleProvider implements ParticleProvider<SimpleParticleType> {
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
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;
import net.minecraft.core.cauldron.CauldronInteraction;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<DigestingRecipe> 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<DigestingRecipe> foundRecipe = DigesterBlockEntity.RECIPE_TYPE.get().getRecipeForIngredient(itemEntity.level(), itemEntity.getItem());
if (foundRecipe.isEmpty()) return null;
cachedRecipe = foundRecipe.get();
return cachedRecipe;
private static Optional<DigestingRecipe> getDigestionRecipe(ItemEntity itemEntity, ItemStack stack, CompoundTag digestionData) {
Optional<DigestingRecipe> 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.
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public final class ModItemTags {

public static final TagKey<Item> SUGARS = tag("sugars");

public static final TagKey<Item> CANNOT_BE_DIGESTED_IN_ACID = tag("cannot_be_digested_in_acid");

private ModItemTags() {}

private static TagKey<Item> tag(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}

0 comments on commit 118499b

Please sign in to comment.