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].
This commit is contained in:
shartte
2020-09-08 18:10:45 +02:00
committed by GitHub
parent f2aae3ef0f
commit 88c9370e4f
4 changed files with 188 additions and 100 deletions
@@ -23,13 +23,23 @@
package appeng.api.implementations.items; package appeng.api.implementations.items;
import net.minecraft.block.Block; import javax.annotation.Nullable;
import net.minecraft.block.material.Material;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface IGrowableCrystal { public interface IGrowableCrystal {
ItemStack triggerGrowth(ItemStack is); 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);
} }
+27
View File
@@ -31,6 +31,8 @@ import java.util.Set;
import java.util.function.DoubleSupplier; import java.util.function.DoubleSupplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -393,6 +395,15 @@ public final class AEConfig {
return this.meteoriteDimensionWhitelist; 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. // Setters keep visibility as low as possible.
private static class ClientConfig { private static class ClientConfig {
@@ -507,6 +518,11 @@ public final class AEConfig {
public final ConfigValue<Integer> condenserMatterBallsPower; public final ConfigValue<Integer> condenserMatterBallsPower;
public final ConfigValue<Integer> condenserSingularityPower; public final ConfigValue<Integer> condenserSingularityPower;
// In-World Purification
// Settings for improved speed depending on fluid the crystal is in
public final ConfigValue<String> improvedFluidTag;
public final ConfigValue<Double> improvedFluidMultiplier;
public final Map<TickRates, ConfigValue<Integer>> tickRateMin = new HashMap<>(); public final Map<TickRates, ConfigValue<Integer>> tickRateMin = new HashMap<>();
public final Map<TickRates, ConfigValue<Integer>> tickRateMax = new HashMap<>(); public final Map<TickRates, ConfigValue<Integer>> tickRateMax = new HashMap<>();
@@ -617,6 +633,17 @@ public final class AEConfig {
tickRateMax.put(tickRate, builder.define(tickRate.name() + "Max", tickRate.getDefaultMax())); tickRateMax.put(tickRate, builder.define(tickRate.name() + "Max", tickRate.getDefaultMax()));
} }
builder.pop(); 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();
} }
} }
@@ -19,13 +19,14 @@
package appeng.entity; package appeng.entity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.material.Material;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import appeng.api.features.AEFeature; import appeng.api.features.AEFeature;
@@ -35,12 +36,27 @@ import appeng.client.EffectType;
import appeng.core.AEConfig; import appeng.core.AEConfig;
import appeng.core.AppEng; import appeng.core.AppEng;
import appeng.items.misc.CrystalSeedItem; import appeng.items.misc.CrystalSeedItem;
import appeng.util.Platform;
public final class GrowingCrystalEntity extends AEBaseItemEntity { public final class GrowingCrystalEntity extends AEBaseItemEntity {
public static EntityType<GrowingCrystalEntity> TYPE; public static EntityType<GrowingCrystalEntity> 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; private int progress_1000 = 0;
public GrowingCrystalEntity(EntityType<? extends GrowingCrystalEntity> type, World world) { public GrowingCrystalEntity(EntityType<? extends GrowingCrystalEntity> type, World world) {
@@ -56,126 +72,140 @@ public final class GrowingCrystalEntity extends AEBaseItemEntity {
public void tick() { public void tick() {
super.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)) { if (!AEConfig.instance().isFeatureEnabled(AEFeature.IN_WORLD_PURIFICATION)) {
return; return;
} }
final ItemStack is = this.getItem(); final int x = MathHelper.floor(this.getPosX());
final Item gc = is.getItem(); 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... BlockPos pos = new BlockPos(x, y, z);
{ final BlockState state = this.world.getBlockState(pos);
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());
final BlockState state = this.world.getBlockState(new BlockPos(j, i, k)); final float multiplier = cry.getMultiplier(state, world, pos);
final Material mat = state.getMaterial();
final IGrowableCrystal cry = (IGrowableCrystal) is.getItem();
final float multiplier = cry.getMultiplier(state.getBlock(), mat); if (multiplier <= 0) {
final int speed = (int) Math.max(1, this.getSpeed(j, i, k) * multiplier); // 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 (world.isRemote()) {
if (isClient) { // On the client, we reuse the growth-tick-progress
this.progress_1000++; // as a tick-counter for particle effects
} else { int len = getTicksBetweenParticleEffects(progressPerTick);
this.progress_1000 += speed; if (++this.progress_1000 >= len) {
}
} else {
this.progress_1000 = 0; 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) { if (this.progress_1000 >= 1000) {
int len = 40; // 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) { // If we did not use a while loop here, the fastest growth for a crystal
len = 20; // 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,
if (speed > 90) { // it should be possible to go faster.
len = 15; do {
} newItem = cry.triggerGrowth(newItem);
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) {
this.progress_1000 -= 1000; this.progress_1000 -= 1000;
// We need to copy the stack or the change detection will not work and not sync // We assume that if the item changes, the process is complete and we can break
// this new stack to the client } while (this.progress_1000 >= 1000 && newItem.getItem() == is.getItem());
ItemStack newItem = cry.triggerGrowth(is.copy());
this.setItem(newItem); this.setItem(newItem);
}
} }
} }
} }
private int getSpeed(final int x, final int y, final int z) { private static int getTicksBetweenParticleEffects(int progressPerTick) {
final int per = 80; if (progressPerTick > 500) {
final float mul = 0.3f; return 1; // 20 times per second
} else if (progressPerTick > 360) {
int qty = 0; return 3;
} else if (progressPerTick > 240) {
if (this.isAccelerated(x + 1, y, z)) { return 7;
qty += per + qty * mul; } 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(); return te instanceof ICrystalGrowthAccelerator && ((ICrystalGrowthAccelerator) te).isPowered();
} }
// Don't let seeds "float" on water surface
@Override @Override
protected void applyFloatMotion() { protected void applyFloatMotion() {
ItemStack item = getItem(); 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) { 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; return;
} }
super.applyFloatMotion(); super.applyFloatMotion();
@@ -24,15 +24,19 @@ import javax.annotation.Nullable;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import net.minecraft.block.Block; import net.minecraft.block.BlockState;
import net.minecraft.block.material.Material;
import net.minecraft.client.util.ITooltipFlag; import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.ITag;
import net.minecraft.util.IItemProvider; import net.minecraft.util.IItemProvider;
import net.minecraft.util.NonNullList; 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.math.MathHelper;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
@@ -41,6 +45,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import appeng.api.implementations.items.IGrowableCrystal; import appeng.api.implementations.items.IGrowableCrystal;
import appeng.core.AEConfig;
import appeng.core.localization.ButtonToolTips; import appeng.core.localization.ButtonToolTips;
import appeng.entity.GrowingCrystalEntity; import appeng.entity.GrowingCrystalEntity;
import appeng.items.AEBaseItem; import appeng.items.AEBaseItem;
@@ -94,8 +99,24 @@ public class CrystalSeedItem extends AEBaseItem implements IGrowableCrystal {
} }
@Override @Override
public float getMultiplier(final Block blk, final Material mat) { public float getMultiplier(BlockState state, @Nullable World world, @Nullable BlockPos pos) {
return 0.5f;
// Check for the improved fluid tag and return the improved multiplier
String improvedFluidTagName = AEConfig.instance().getImprovedFluidTag();
if (improvedFluidTagName != null) {
ITag<Fluid> 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 @Override