From 88c9370e4f195b9fcf6e7b95b615dacb1e970c02 Mon Sep 17 00:00:00 2001 From: shartte Date: Tue, 8 Sep 2020 18:10:45 +0200 Subject: [PATCH] Crystal Growth Refactor (#4702) * Refactored crystal in-world purification: - Removed iterative formula - Set the default growth factor to 1 rather than 0.5 to simplify settings - Made the growth-tick-progress-per-tick a configuration setting for 0-6 accelerators - Unfinished seeds now slowly sink when ejected by an annihilation plane or other system (emulates a player throwing it in) - Instead of checking for "isLiquid", we now check for a new tag (appliedenergistics2:crystal_purification_medium) to check if the crystal is submerged in an allowed fluid for growth, defaults to #minecraft:water. * Removed configuration options for changing how accelerators work, replaced with improved fluid tag option with a clamped multiplier [1,10]. --- .../items/IGrowableCrystal.java | 16 +- src/main/java/appeng/core/AEConfig.java | 27 +++ .../appeng/entity/GrowingCrystalEntity.java | 216 ++++++++++-------- .../appeng/items/misc/CrystalSeedItem.java | 29 ++- 4 files changed, 188 insertions(+), 100 deletions(-) diff --git a/src/api/java/appeng/api/implementations/items/IGrowableCrystal.java b/src/api/java/appeng/api/implementations/items/IGrowableCrystal.java index a76c2b0e7..276da1d75 100644 --- a/src/api/java/appeng/api/implementations/items/IGrowableCrystal.java +++ b/src/api/java/appeng/api/implementations/items/IGrowableCrystal.java @@ -23,13 +23,23 @@ package appeng.api.implementations.items; -import net.minecraft.block.Block; -import net.minecraft.block.material.Material; +import javax.annotation.Nullable; + +import net.minecraft.block.BlockState; import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; public interface IGrowableCrystal { ItemStack triggerGrowth(ItemStack is); - float getMultiplier(Block blk, Material mat); + /** + * Gets the crystal growth multiplier based on the material it is submerged in. + * + * @param state The blockstate of the block the crystal is currently in. + * @return The multiplier for the growth tick progress. Zero if the crystal + * cannot grow in the current material. + */ + float getMultiplier(BlockState state, @Nullable World world, @Nullable BlockPos pos); } diff --git a/src/main/java/appeng/core/AEConfig.java b/src/main/java/appeng/core/AEConfig.java index b3aa4e93c..db65ae3f9 100644 --- a/src/main/java/appeng/core/AEConfig.java +++ b/src/main/java/appeng/core/AEConfig.java @@ -31,6 +31,8 @@ import java.util.Set; import java.util.function.DoubleSupplier; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import com.google.common.base.Strings; import org.apache.commons.lang3.tuple.Pair; @@ -393,6 +395,15 @@ public final class AEConfig { return this.meteoriteDimensionWhitelist; } + @Nullable + public String getImprovedFluidTag() { + return Strings.emptyToNull(COMMON.improvedFluidTag.get()); + } + + public float getImprovedFluidMultiplier() { + return COMMON.improvedFluidMultiplier.get().floatValue(); + } + // Setters keep visibility as low as possible. private static class ClientConfig { @@ -507,6 +518,11 @@ public final class AEConfig { public final ConfigValue condenserMatterBallsPower; public final ConfigValue condenserSingularityPower; + // In-World Purification + // Settings for improved speed depending on fluid the crystal is in + public final ConfigValue improvedFluidTag; + public final ConfigValue improvedFluidMultiplier; + public final Map> tickRateMin = new HashMap<>(); public final Map> tickRateMax = new HashMap<>(); @@ -617,6 +633,17 @@ public final class AEConfig { tickRateMax.put(tickRate, builder.define(tickRate.name() + "Max", tickRate.getDefaultMax())); } builder.pop(); + + builder.comment("Settings for in-world purification of crystals.").push("inWorldPurification"); + + improvedFluidTag = builder.comment( + "A fluid tag that identifies fluids that improve crystal purification speed. Does not affect purification with water/lava.") + .define("improvedFluidTag", ""); + improvedFluidMultiplier = builder + .comment("The speed multiplier to use when the crystals are submerged in the improved fluid.") + .defineInRange("improvedFluidMultiplier", 2.0, 1.0, 10.0); + + builder.pop(); } } diff --git a/src/main/java/appeng/entity/GrowingCrystalEntity.java b/src/main/java/appeng/entity/GrowingCrystalEntity.java index f2ba87ef5..9bbf04617 100644 --- a/src/main/java/appeng/entity/GrowingCrystalEntity.java +++ b/src/main/java/appeng/entity/GrowingCrystalEntity.java @@ -19,13 +19,14 @@ package appeng.entity; import net.minecraft.block.BlockState; -import net.minecraft.block.material.Material; import net.minecraft.entity.EntityType; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import appeng.api.features.AEFeature; @@ -35,12 +36,27 @@ import appeng.client.EffectType; import appeng.core.AEConfig; import appeng.core.AppEng; import appeng.items.misc.CrystalSeedItem; -import appeng.util.Platform; public final class GrowingCrystalEntity extends AEBaseItemEntity { public static EntityType TYPE; + // Growth tick progress per tick by number of adjacent accelerators + // Expressed as 1/1000th of a growth tick, applied to progress_1000 + // each time this entity ticks. + private static final int[] GROWTH_TICK_PROGRESS = { 1, // no accelerators + 40, // 1 accelerator + 92, // 2 accelerators + 159, // 3 accelerators + 247, // 4 accelerators + 361, // 5 accelerators + 509 // 6 accelerators + }; + + /** + * The accumulated progress towards a single growth tick of the crystal in + * 1/1000th of a growth tick. + */ private int progress_1000 = 0; public GrowingCrystalEntity(EntityType type, World world) { @@ -56,126 +72,140 @@ public final class GrowingCrystalEntity extends AEBaseItemEntity { public void tick() { super.tick(); + final ItemStack is = this.getItem(); + final Item gc = is.getItem(); + + if (!(gc instanceof IGrowableCrystal)) { + return; + } + + applyGrowthTick((IGrowableCrystal) gc, is); + } + + private void applyGrowthTick(IGrowableCrystal cry, ItemStack is) { if (!AEConfig.instance().isFeatureEnabled(AEFeature.IN_WORLD_PURIFICATION)) { return; } - final ItemStack is = this.getItem(); - final Item gc = is.getItem(); + final int x = MathHelper.floor(this.getPosX()); + final int y = MathHelper.floor((this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0D); + final int z = MathHelper.floor(this.getPosZ()); - if (gc instanceof IGrowableCrystal) // if it changes this just stops being an issue... - { - final int j = MathHelper.floor(this.getPosX()); - final int i = MathHelper.floor((this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0D); - final int k = MathHelper.floor(this.getPosZ()); + BlockPos pos = new BlockPos(x, y, z); + final BlockState state = this.world.getBlockState(pos); - final BlockState state = this.world.getBlockState(new BlockPos(j, i, k)); - final Material mat = state.getMaterial(); - final IGrowableCrystal cry = (IGrowableCrystal) is.getItem(); + final float multiplier = cry.getMultiplier(state, world, pos); - final float multiplier = cry.getMultiplier(state.getBlock(), mat); - final int speed = (int) Math.max(1, this.getSpeed(j, i, k) * multiplier); + if (multiplier <= 0) { + // Crystal is in unsuitable material, reset progress and quit + this.progress_1000 = 0; + return; + } - final boolean isClient = Platform.isClient(); + final int progressPerTick = (int) Math.max(1, this.getSpeed(pos) * multiplier); - if (mat.isLiquid()) { - if (isClient) { - this.progress_1000++; - } else { - this.progress_1000 += speed; - } - } else { + if (world.isRemote()) { + // On the client, we reuse the growth-tick-progress + // as a tick-counter for particle effects + int len = getTicksBetweenParticleEffects(progressPerTick); + if (++this.progress_1000 >= len) { this.progress_1000 = 0; + AppEng.proxy.spawnEffect(EffectType.Vibrant, this.world, this.getPosX(), this.getPosY() + 0.2, + this.getPosZ(), null); } + } else { + this.progress_1000 += progressPerTick; - if (isClient) { - int len = 40; + if (this.progress_1000 >= 1000) { + // We need to copy the stack or the change detection will not work and not sync + // this new stack to the client + ItemStack newItem = is.copy(); - if (speed > 2) { - len = 20; - } - - if (speed > 90) { - len = 15; - } - - if (speed > 150) { - len = 10; - } - - if (speed > 240) { - len = 7; - } - - if (speed > 360) { - len = 3; - } - - if (speed > 500) { - len = 1; - } - - if (this.progress_1000 >= len) { - this.progress_1000 = 0; - AppEng.proxy.spawnEffect(EffectType.Vibrant, this.world, this.getPosX(), this.getPosY() + 0.2, - this.getPosZ(), null); - } - } else { - if (this.progress_1000 > 1000) { + // If we did not use a while loop here, the fastest growth for a crystal + // would be limited to a minimum of 30 seconds (based on 600 required growth + // ticks). + // Should a crystal decide to use a high multiplier for a certain material, + // it should be possible to go faster. + do { + newItem = cry.triggerGrowth(newItem); this.progress_1000 -= 1000; - // We need to copy the stack or the change detection will not work and not sync - // this new stack to the client - ItemStack newItem = cry.triggerGrowth(is.copy()); - this.setItem(newItem); - } + // We assume that if the item changes, the process is complete and we can break + } while (this.progress_1000 >= 1000 && newItem.getItem() == is.getItem()); + + this.setItem(newItem); } } } - private int getSpeed(final int x, final int y, final int z) { - final int per = 80; - final float mul = 0.3f; - - int qty = 0; - - if (this.isAccelerated(x + 1, y, z)) { - qty += per + qty * mul; + private static int getTicksBetweenParticleEffects(int progressPerTick) { + if (progressPerTick > 500) { + return 1; // 20 times per second + } else if (progressPerTick > 360) { + return 3; + } else if (progressPerTick > 240) { + return 7; + } else if (progressPerTick > 150) { + return 10; + } else if (progressPerTick > 90) { + return 15; + } else if (progressPerTick > 2) { + return 20; + } else { + return 40; // Every 2 seconds } - - if (this.isAccelerated(x, y + 1, z)) { - qty += per + qty * mul; - } - - if (this.isAccelerated(x, y, z + 1)) { - qty += per + qty * mul; - } - - if (this.isAccelerated(x - 1, y, z)) { - qty += per + qty * mul; - } - - if (this.isAccelerated(x, y - 1, z)) { - qty += per + qty * mul; - } - - if (this.isAccelerated(x, y, z - 1)) { - qty += per + qty * mul; - } - - return qty; } - private boolean isAccelerated(final int x, final int y, final int z) { - final TileEntity te = this.world.getTileEntity(new BlockPos(x, y, z)); + /** + * Gets the extra progress per tick in 1/1000th of a growth tick based on the + * surrounding accelerators. + */ + private int getSpeed(BlockPos pos) { + int acceleratorCount = getAcceleratorCount(pos); + + if (acceleratorCount < 0) { + return GROWTH_TICK_PROGRESS[0]; + } else if (acceleratorCount >= GROWTH_TICK_PROGRESS.length) { + return GROWTH_TICK_PROGRESS[GROWTH_TICK_PROGRESS.length - 1]; + } else { + return GROWTH_TICK_PROGRESS[acceleratorCount]; + } + } + + private int getAcceleratorCount(BlockPos pos) { + int count = 0; + + BlockPos.Mutable testPos = new BlockPos.Mutable(); + for (Direction direction : Direction.values()) { + if (this.isPoweredAccelerator(testPos.func_239622_a_(pos, direction))) { + count++; + } + } + + return count; + } + + private boolean isPoweredAccelerator(BlockPos pos) { + final TileEntity te = this.world.getTileEntity(pos); return te instanceof ICrystalGrowthAccelerator && ((ICrystalGrowthAccelerator) te).isPowered(); } - // Don't let seeds "float" on water surface @Override protected void applyFloatMotion() { ItemStack item = getItem(); + + // Make ungrown seeds sink, and fully grown seeds bouyant allowing for + // automation based around dropping seeds between 5 CGAs, then catchiung + // them on their way up. if (item.getItem() instanceof CrystalSeedItem) { + Vector3d v = this.getMotion(); + + // Apply a much smaller acceleration to make them slowly sink + double yAccel = this.hasNoGravity() ? 0 : -0.002; + + // Apply the x/z slow-down, and the y acceleration + this.setMotion(v.x * 0.99, v.y + yAccel, v.z * 0.99); + return; } super.applyFloatMotion(); diff --git a/src/main/java/appeng/items/misc/CrystalSeedItem.java b/src/main/java/appeng/items/misc/CrystalSeedItem.java index 1d8b6a3c5..8acf74e08 100644 --- a/src/main/java/appeng/items/misc/CrystalSeedItem.java +++ b/src/main/java/appeng/items/misc/CrystalSeedItem.java @@ -24,15 +24,19 @@ import javax.annotation.Nullable; import com.google.common.base.Preconditions; -import net.minecraft.block.Block; -import net.minecraft.block.material.Material; +import net.minecraft.block.BlockState; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.Entity; +import net.minecraft.fluid.Fluid; import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tags.FluidTags; +import net.minecraft.tags.ITag; import net.minecraft.util.IItemProvider; import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; @@ -41,6 +45,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import appeng.api.implementations.items.IGrowableCrystal; +import appeng.core.AEConfig; import appeng.core.localization.ButtonToolTips; import appeng.entity.GrowingCrystalEntity; import appeng.items.AEBaseItem; @@ -94,8 +99,24 @@ public class CrystalSeedItem extends AEBaseItem implements IGrowableCrystal { } @Override - public float getMultiplier(final Block blk, final Material mat) { - return 0.5f; + public float getMultiplier(BlockState state, @Nullable World world, @Nullable BlockPos pos) { + + // Check for the improved fluid tag and return the improved multiplier + String improvedFluidTagName = AEConfig.instance().getImprovedFluidTag(); + if (improvedFluidTagName != null) { + ITag tag = FluidTags.getCollection().get(new ResourceLocation(improvedFluidTagName)); + if (tag != null && state.getFluidState().isTagged(tag)) { + return AEConfig.instance().getImprovedFluidMultiplier(); + } + } + + // Check for the normal supported fluid + if (world != null && world.func_234923_W_() == World.field_234919_h_) { + // In the nether, use Lava as the "normal" fluid + return state.getFluidState().isTagged(FluidTags.LAVA) ? 1 : 0; + } else { + return state.getFluidState().isTagged(FluidTags.WATER) ? 1 : 0; + } } @Override