Author SHA1 Message Date
layue13andClaude Sonnet 5.5 34f98ad930 perf: hyper node creator rewrites the node only when it no longer holds the wanted aspects
Build / build (push) Skipped
Build / publish (push) Skipped
Build / publish (pull_request) Skipped
Build / build (pull_request) Successful in 43s
Closes TDLM/thaumicadditions#16

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 14:19:39 +08:00
layue13 a0e3d1751f Merge pull request 'perf: creative node registers once, no server stack walk' (#29) from perf/creative-node-registry into main
Build / build (push) Successful in 51s
Build / publish (push) Successful in 45s
2026-11-07 06:18:49 +00:00
layue13andClaude Sonnet 5.5 726fef01f6 perf: creative node registers its location once, server skips the collision stack walk
Build / build (push) Skipped
Build / build (pull_request) Successful in 1m7s
Build / publish (pull_request) Skipped
Build / publish (push) Skipped
Closes TDLM/thaumicadditions#17

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 14:17:27 +08:00
layue13 f523d73954 Merge pull request 'perf: singularity pull box queried once per tick' (#28) from perf/singularity-queries into main
Build / build (push) Successful in 44s
Build / publish (push) Successful in 1m5s
2026-11-07 06:16:34 +00:00
layue13andClaude Sonnet 5.5 51a1e0d83d perf: grounded singularity builds its pull box and queries entities once per tick
Build / build (push) Skipped
Build / publish (push) Skipped
Build / build (pull_request) Successful in 40s
Build / publish (pull_request) Skipped
Closes TDLM/thaumicadditions#21

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 14:15:36 +08:00
layue13 12042a98d8 Merge pull request 'perf: tank and tainted crop lookups hoisted' (#27) from perf/tank-crops-lookups into main
Build / build (push) Successful in 48s
Build / publish (push) Successful in 48s
2026-11-07 06:13:31 +00:00
layue13andClaude Sonnet 5.5 69fc32e3f6 perf: tanks and tainted crops stop re-querying the same positions each tick
Build / build (push) Skipped
Build / publish (push) Skipped
Build / build (pull_request) Successful in 1m25s
Build / publish (pull_request) Skipped
Closes TDLM/thaumicadditions#20, closes TDLM/thaumicadditions#22

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 14:11:48 +08:00
layue13 b8e763aa68 Merge pull request 'guiFactory 指向真实的类' (#26) from fix/guifactory into main
Build / build (push) Successful in 5m39s
Build / publish (push) Successful in 1m40s
2026-11-06 14:47:01 +00:00
12 changed files with 442 additions and 78 deletions

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ repositories {
dependencies {
// Thaumcraft 4 full dev jar (api + common impl; the sources import thaumcraft.common.* and thaumcraft.client.*)
testImplementation("junit:junit:4.13.2")
api("uk.potatolab:Thaumcraft4:4.2.3.5-20261028.155248-g1a600e9")
// cofh.api.energy.IEnergyReceiver is used by TileThaumicGenerator (the CoFH API lives in CoFHLib, not CoFHCore)
compileOnly("uk.potatolab:CoFHLib-1.7.10-Legacy:1.7.10R3.0.3-20261028.145717-gbf0ac89")
@@ -40,6 +40,11 @@ public class BlockCreativeNode extends BlockContainer {
}
public AxisAlignedBB getCollisionBoundingBoxFromPool(World w, int x, int y, int z) {
if(!w.isRemote) {
// rendering never happens on the server, so the stack walk below can only find nothing
return AxisAlignedBB.getBoundingBox(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
}
StackTraceElement[] var5 = Thread.currentThread().getStackTrace();
int var6 = var5.length;
@@ -67,10 +67,18 @@ public class EntitySingularity extends EntityAlumentum {
List l;
List l2;
int i;
// position and radius do not change inside the loop, so the box is built once and the entity lists are
// only queried again after a round that could have added or removed entities (alumentum, block breaking)
AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius);
l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);
boolean entitiesChanged = false;
for(p = 0; p < 128; ++p) {
AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius);
l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);
if(entitiesChanged) {
l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);
entitiesChanged = false;
}
double ry;
double ey;
@@ -103,6 +111,7 @@ public class EntitySingularity extends EntityAlumentum {
var24.posZ = ez;
var24.inGround = true;
this.worldObj.spawnEntityInWorld(var24);
entitiesChanged = true;
}
}
@@ -122,23 +131,6 @@ public class EntitySingularity extends EntityAlumentum {
}
int var22;
for(i = 0; i < 16; ++i) {
for(var22 = 0; var22 < l2.size(); ++var22) {
double rz = ((EntityLivingBase)l2.get(var22)).posX;
double ey1 = ((EntityLivingBase)l2.get(var22)).posY;
double ez1 = ((EntityLivingBase)l2.get(var22)).posZ;
double motionX1 = ((EntityLivingBase)l2.get(var22)).motionX;
double motionY1 = ((EntityLivingBase)l2.get(var22)).motionY;
double motionZ1 = ((EntityLivingBase)l2.get(var22)).motionZ;
motionX1 = this.posX - rz;
motionY1 = this.posY - ey1 + (double)(this.ticksExisted < 400?(float)this.ticksExisted / 400.0F * 4.0F:4.0F);
motionZ1 = this.posZ - ez1;
((EntityLivingBase)l2.get(var22)).motionX = motionX1 / 10.0D;
((EntityLivingBase)l2.get(var22)).motionY = motionY1 / 10.0D;
((EntityLivingBase)l2.get(var22)).motionZ = motionZ1 / 10.0D;
}
}
i = (int)this.posX;
var22 = (int)this.posY;
int var23 = (int)this.posZ;
@@ -153,6 +145,7 @@ public class EntitySingularity extends EntityAlumentum {
}
this.worldObj.setBlockToAir(i, var22, var23);
entitiesChanged = true;
}
}
@@ -14,20 +14,11 @@ public class TileOldReinforcedTank extends TileTankBasic {
}
public void updateEntity() {
if(!(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic) && !(this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 0) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 0);
}
if(!(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 1) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 1);
}
if(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic && !(this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 2) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 2);
}
if(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 3) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 3);
boolean above = this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic;
boolean below = this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic;
int wantedMeta = TileTankBasic.connectionMeta(above, below);
if(this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != wantedMeta) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, wantedMeta);
}
boolean ext = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) instanceof TileOldReinforcedTank && this.vis + this.taint < this.capacity;
@@ -35,6 +35,7 @@ public class TileCreativeNode extends TileVisNode implements INode, IWandable {
public Color targetColor = new Color(16777215);
public Color color = new Color(16777215);
public boolean hasUpdatedOnce;
private boolean locationRegistered;
public void updateEntity() {
@@ -44,12 +45,30 @@ public class TileCreativeNode extends TileVisNode implements INode, IWandable {
if(!this.hasUpdatedOnce) {
this.hasUpdatedOnce = !this.hasUpdatedOnce;
} else {
} else if(!this.locationRegistered) {
this.generateId();
}
}
public void invalidate() {
this.unregisterLocation();
super.invalidate();
}
public void onChunkUnload() {
this.unregisterLocation();
super.onChunkUnload();
}
private void unregisterLocation() {
if(this.id != null && TileNode.locations != null) {
TileNode.locations.remove(this.id);
}
this.locationRegistered = false;
}
public AspectList getAspects() {
return (new AspectList()).add(Aspect.AIR, 134217).add(Aspect.WATER, 134217).add(Aspect.FIRE, 134217).add(Aspect.ORDER, 134217).add(Aspect.ENTROPY, 134217).add(Aspect.EARTH, 134217);
}
@@ -232,6 +251,7 @@ public class TileCreativeNode extends TileVisNode implements INode, IWandable {
t.add(Integer.valueOf(this.yCoord));
t.add(Integer.valueOf(this.zCoord));
TileNode.locations.put(this.id, t);
this.locationRegistered = true;
}
return this.id;
@@ -35,25 +35,17 @@ public class TileHyperNodeCreator extends TileThaumcraft implements IInventory,
}
}
TileNode var5 = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) != null && this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) instanceof TileNode?(TileNode)this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord):null;
net.minecraft.tileentity.TileEntity above = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord);
TileNode var5 = above instanceof TileNode?(TileNode)above:null;
this.node = var5 != null;
if(this.node) {
AspectList list = new AspectList();
int corner;
for(corner = 0; corner < Aspect.getPrimalAspects().size(); ++corner) {
list.add((Aspect)Aspect.getPrimalAspects().get(corner), totalItems);
// the node is only rewritten when it no longer holds the wanted amounts (drained, or the stock changed)
if(!holdsExactly(var5.getAspects(), totalItems, this.primalsOnly)) {
var5.setAspects(wantedAspects(totalItems, this.primalsOnly));
}
if(!this.primalsOnly) {
for(corner = 0; corner < Aspect.getCompoundAspects().size(); ++corner) {
list.add((Aspect)Aspect.getCompoundAspects().get(corner), totalItems);
}
}
var5.setAspects(list);
if(this.worldObj.rand.nextBoolean()) {
corner = this.worldObj.rand.nextInt(8);
int corner = this.worldObj.rand.nextInt(8);
if(corner == 0) {
Thaumcraft.proxy.nodeBolt(this.worldObj, (float)this.xCoord, (float)this.yCoord + 2.0F, (float)this.zCoord, (float)this.xCoord + 0.5F, (float)this.yCoord + 1.5F, (float)this.zCoord + 0.5F);
}
@@ -90,6 +82,52 @@ public class TileHyperNodeCreator extends TileThaumcraft implements IInventory,
}
/** The aspect list a node above gets: every primal (and unless primalsOnly every compound) at {@code total}. */
public static AspectList wantedAspects(int total, boolean primalsOnly) {
AspectList list = new AspectList();
int i;
for(i = 0; i < Aspect.getPrimalAspects().size(); ++i) {
list.add((Aspect)Aspect.getPrimalAspects().get(i), total);
}
if(!primalsOnly) {
for(i = 0; i < Aspect.getCompoundAspects().size(); ++i) {
list.add((Aspect)Aspect.getCompoundAspects().get(i), total);
}
}
return list;
}
/** Whether {@code current} already equals {@link #wantedAspects(int, boolean)} without building it. */
public static boolean holdsExactly(AspectList current, int total, boolean primalsOnly) {
if(current == null) {
return false;
}
int expected = Aspect.getPrimalAspects().size() + (primalsOnly?0:Aspect.getCompoundAspects().size());
return current.size() == expected && allAmounts(current, total, primalsOnly);
}
private static boolean allAmounts(AspectList current, int total, boolean primalsOnly) {
for(int i = 0; i < Aspect.getPrimalAspects().size(); ++i) {
if(current.getAmount((Aspect)Aspect.getPrimalAspects().get(i)) != total) {
return false;
}
}
if(!primalsOnly) {
for(int i = 0; i < Aspect.getCompoundAspects().size(); ++i) {
if(current.getAmount((Aspect)Aspect.getCompoundAspects().get(i)) != total) {
return false;
}
}
}
return true;
}
public static boolean areItemStacksEqual(ItemStack s1, ItemStack s2) {
return s1 == null && s2 == null?true:s1 != null && s2 != null && s1.getItem() == s2.getItem() && s1.getItemDamage() == s2.getItemDamage();
}
@@ -2,11 +2,8 @@ package com.pengu.thaumcraft.additions.tileentity;
import com.pengu.thaumcraft.additions.init.All;
import com.pengu.thaumcraft.additions.utils.Util;
import java.lang.reflect.Method;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.common.util.ForgeDirection;
import thaumcraft.api.TileThaumcraft;
@@ -17,24 +14,29 @@ public class TileTaintedCrops extends TileThaumcraft {
public int tick = 0;
/** Ticks of growth needed per stage; the taint biome grows faster. */
public static int growthThreshold(boolean taintBiome) {
return 4000 - (taintBiome?450:0);
}
/** Growth only counts while the crop is not yet fully grown. */
public static boolean countsGrowth(int meta) {
return meta < 4;
}
public void updateEntity() {
if(!this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord).canSustainPlant(this.worldObj, this.xCoord, this.yCoord - 1, this.zCoord, ForgeDirection.UP, (IPlantable)All.B_taint_crops) && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) != Blocks.grass && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) != Blocks.dirt) {
net.minecraft.block.Block soil = this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord);
if(!soil.canSustainPlant(this.worldObj, this.xCoord, this.yCoord - 1, this.zCoord, ForgeDirection.UP, (IPlantable)All.B_taint_crops) && soil != Blocks.grass && soil != Blocks.dirt) {
// the crop block is gone after this, so there is nothing left to drop
this.worldObj.setBlockToAir(this.xCoord, this.yCoord, this.zCoord);
try {
Method m = this.worldObj.getBlock(this.xCoord, this.yCoord, this.zCoord).getClass().getDeclaredMethod("dropBlockAsItem", new Class[]{World.class, Integer.class, Integer.class, Integer.class, ItemStack.class});
m.setAccessible(true);
m.invoke(this.worldObj.getBlock(this.xCoord, this.yCoord, this.zCoord), new Object[]{this.worldObj, Integer.valueOf(this.xCoord), Integer.valueOf(this.yCoord), Integer.valueOf(this.zCoord), null});
} catch (Throwable var2) {
;
}
} else {
if(this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) < 4) {
int meta = this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord);
if(countsGrowth(meta)) {
++this.tick;
}
if(this.tick >= 4000 - (this.worldObj.getBiomeGenForCoords(this.xCoord, this.zCoord).biomeID == Config.biomeTaintID?450:0)) {
// the lowest possible threshold is 3550, so the biome is only looked up once it matters
if(this.tick >= growthThreshold(true) && this.tick >= growthThreshold(this.worldObj.getBiomeGenForCoords(this.xCoord, this.zCoord).biomeID == Config.biomeTaintID)) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) + 1);
}
@@ -23,21 +23,17 @@ public class TileTankBasic extends TileThaumcraft implements ITubable {
this.capacity = 1005;
}
/** Block metadata for a tank by which neighbours are tanks: 0 none, 1 only below, 2 only above, 3 both. */
public static int connectionMeta(boolean above, boolean below) {
return (above?2:0) | (below?1:0);
}
public void updateEntity() {
if(!(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic) && !(this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 0) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 0);
}
if(!(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 1) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 1);
}
if(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic && !(this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic) && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 2) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 2);
}
if(this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic && this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != 3) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, 3);
boolean above = this.worldObj.getBlock(this.xCoord, this.yCoord + 1, this.zCoord) instanceof BlockTankBasic;
boolean below = this.worldObj.getBlock(this.xCoord, this.yCoord - 1, this.zCoord) instanceof BlockTankBasic;
int wantedMeta = TileTankBasic.connectionMeta(above, below);
if(this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) != wantedMeta) {
Util.setBlockMetadata(this.worldObj, this.xCoord, this.yCoord, this.zCoord, wantedMeta);
}
if(!this.worldObj.isRemote) {
@@ -0,0 +1,159 @@
package com.pengu.thaumcraft.additions.entity;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import net.minecraft.world.WorldProviderSurface;
import net.minecraft.world.WorldServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import thaumcraft.common.CommonProxy;
import thaumcraft.common.Thaumcraft;
/** TDLM/thaumicadditions#21: a grounded singularity queries its pull box once per tick, not 256 times. */
public class EntitySingularityQueryTest {
private CommonProxy originalProxy;
private QueryWorld world;
private EntitySingularity singularity;
private Entity bystander;
private Mob mob;
@SuppressWarnings("unchecked")
private static <T> T allocate(Class<T> type) throws Exception {
Constructor<?> ctor = sun.reflect.ReflectionFactory.getReflectionFactory()
.newConstructorForSerialization(type, Object.class.getDeclaredConstructor());
return (T) ctor.newInstance();
}
@Before
public void setUp() throws Exception {
this.originalProxy = Thaumcraft.proxy;
Thaumcraft.proxy = new CommonProxy();
this.world = allocate(QueryWorld.class);
this.world.rand = new Random(3);
this.world.all = new ArrayList<Entity>();
this.world.living = new ArrayList<Entity>();
Field provider = World.class.getDeclaredField("provider");
provider.setAccessible(true);
provider.set(this.world, new WorldProviderSurface());
this.singularity = allocate(EntitySingularity.class);
this.singularity.worldObj = this.world;
this.singularity.posX = 8;
this.singularity.posY = 64;
this.singularity.posZ = 8;
this.singularity.radius = 10;
Field ground = net.minecraft.entity.projectile.EntityThrowable.class.getDeclaredField("inGround");
ground.setAccessible(true);
ground.setBoolean(this.singularity, true);
this.bystander = allocate(Plain.class);
this.bystander.posX = 12;
this.bystander.posY = 66;
this.bystander.posZ = 5;
this.mob = allocate(Mob.class);
this.mob.posX = 4;
this.mob.posY = 63;
this.mob.posZ = 11;
this.world.all.add(this.bystander);
this.world.all.add(this.mob);
this.world.living.add(this.mob);
}
@After
public void tearDown() {
Thaumcraft.proxy = this.originalProxy;
}
@Test
public void thePullBoxIsQueriedOncePerTickAndEntitiesAreStillPulled() {
this.singularity.onUpdate();
assertEquals("entity queries over the pull box", 2, this.world.pullQueries);
double lift = 1 / 400.0F * 4.0F;
assertEquals((8 - 12) / 10.0D, this.bystander.motionX, 1e-9);
assertEquals((64 - 66 + lift) / 10.0D, this.bystander.motionY, 1e-9);
assertEquals((8 - 5) / 10.0D, this.bystander.motionZ, 1e-9);
assertEquals((8 - 4) / 10.0D, this.mob.motionX, 1e-9);
assertEquals((64 - 63 + lift) / 10.0D, this.mob.motionY, 1e-9);
assertEquals((8 - 11) / 10.0D, this.mob.motionZ, 1e-9);
}
public static class Plain extends Entity {
public Plain() {
super(null);
}
@Override
protected void entityInit() {}
@Override
protected void readEntityFromNBT(net.minecraft.nbt.NBTTagCompound nbt) {}
@Override
protected void writeEntityToNBT(net.minecraft.nbt.NBTTagCompound nbt) {}
}
public static class Mob extends EntityLivingBase {
public Mob() {
super(null);
}
@Override
public ItemStack getHeldItem() {
return null;
}
@Override
public ItemStack getEquipmentInSlot(int slot) {
return null;
}
@Override
public void setCurrentItemOrArmor(int slot, ItemStack stack) {}
@Override
public ItemStack[] getLastActiveItems() {
return new ItemStack[0];
}
}
public static class QueryWorld extends WorldServer {
List<Entity> all;
List<Entity> living;
int pullQueries;
QueryWorld() {
super(null, null, "singularity", 0, null, null);
}
@Override
public List getEntitiesWithinAABB(Class type, AxisAlignedBB box) {
if (box.maxX - box.minX < 3.0D) {
return new ArrayList();
}
this.pullQueries++;
return new ArrayList(type == EntityLivingBase.class ? this.living : this.all);
}
@Override
public Block getBlock(int x, int y, int z) {
return null; // equals Blocks.air, which is unset without a Minecraft bootstrap
}
}
}
@@ -0,0 +1,80 @@
package com.pengu.thaumcraft.additions.tileentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import net.minecraft.world.World;
import net.minecraft.world.WorldProviderSurface;
import net.minecraft.world.WorldServer;
import org.junit.Before;
import org.junit.Test;
import thaumcraft.common.tiles.TileNode;
/** TDLM/thaumicadditions#17: a creative node registers its location once, not every tick, and unregisters on removal. */
public class CreativeNodeRegistryTest {
private TileCreativeNode node;
@SuppressWarnings("unchecked")
private static <T> T allocate(Class<T> type) throws Exception {
Constructor<?> ctor = sun.reflect.ReflectionFactory.getReflectionFactory()
.newConstructorForSerialization(type, Object.class.getDeclaredConstructor());
return (T) ctor.newInstance();
}
@Before
public void setUp() throws Exception {
if (TileNode.locations == null) {
Field f = TileNode.class.getDeclaredField("locations");
f.setAccessible(true);
f.set(null, new HashMap<String, ArrayList>());
}
TileNode.locations.clear();
World world = allocate(FixtureWorld.class);
Field provider = World.class.getDeclaredField("provider");
provider.setAccessible(true);
provider.set(world, new WorldProviderSurface());
this.node = allocate(TileCreativeNode.class);
this.node.setWorldObj(world);
this.node.xCoord = 3;
this.node.yCoord = 70;
this.node.zCoord = 5;
}
@Test
public void locationIsPutOnceAcrossManyTicks() {
this.node.updateEntity(); // first tick only marks the node as updated
this.node.updateEntity();
ArrayList first = TileNode.locations.get("0:3:70:5");
assertNotNull(first);
for (int i = 0; i < 1000; i++) {
this.node.updateEntity();
}
assertSame("no re-registration on later ticks", first, TileNode.locations.get("0:3:70:5"));
assertEquals(1, TileNode.locations.size());
}
@Test
public void unloadingRemovesTheLocationSoItCanBeRegisteredAgain() {
this.node.updateEntity();
this.node.updateEntity();
assertNotNull(TileNode.locations.get("0:3:70:5"));
this.node.onChunkUnload();
assertNull(TileNode.locations.get("0:3:70:5"));
this.node.updateEntity();
assertNotNull(TileNode.locations.get("0:3:70:5"));
}
public static class FixtureWorld extends WorldServer {
FixtureWorld() {
super(null, null, "node", 0, null, null);
}
}
}
@@ -0,0 +1,47 @@
package com.pengu.thaumcraft.additions.tileentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import thaumcraft.api.aspects.Aspect;
import thaumcraft.api.aspects.AspectList;
/** TDLM/thaumicadditions#16: the node is only rewritten when it no longer holds what the creator wants. */
public class HyperNodeCreatorAspectsTest {
@Test
public void aFreshlyBuiltListIsRecognisedAsCurrent() {
for (boolean primalsOnly : new boolean[] { false, true }) {
for (int total : new int[] { 0, 1, 64, 5000 }) {
AspectList list = TileHyperNodeCreator.wantedAspects(total, primalsOnly);
assertTrue("total " + total + " primalsOnly " + primalsOnly,
TileHyperNodeCreator.holdsExactly(list, total, primalsOnly));
}
}
}
@Test
public void wantedListHasTheSameShapeAsBefore() {
AspectList all = TileHyperNodeCreator.wantedAspects(7, false);
assertEquals(Aspect.getPrimalAspects().size() + Aspect.getCompoundAspects().size(), all.size());
assertEquals(7, all.getAmount(Aspect.getPrimalAspects().get(0)));
assertEquals(Aspect.getPrimalAspects().size(), TileHyperNodeCreator.wantedAspects(7, true).size());
}
@Test
public void aDrainedOrChangedNodeIsRewritten() {
AspectList list = TileHyperNodeCreator.wantedAspects(10, false);
list.reduce(Aspect.getPrimalAspects().get(0), 3);
assertFalse(TileHyperNodeCreator.holdsExactly(list, 10, false));
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, false), 11, false));
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, true), 10, false));
assertFalse(TileHyperNodeCreator.holdsExactly(null, 10, false));
}
@Test
public void primalsOnlyDoesNotMatchAnUnrelatedFullList() {
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, false), 10, true));
}
}
@@ -0,0 +1,32 @@
package com.pengu.thaumcraft.additions.tileentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/** TDLM/thaumicadditions#20 and #22: the lookups were hoisted without changing what the tiles decide. */
public class TankAndCropLogicTest {
@Test
public void tankMetadataMatchesTheOriginalFourBranches() {
assertEquals(0, TileTankBasic.connectionMeta(false, false));
assertEquals(1, TileTankBasic.connectionMeta(false, true));
assertEquals(2, TileTankBasic.connectionMeta(true, false));
assertEquals(3, TileTankBasic.connectionMeta(true, true));
}
@Test
public void cropThresholdIsLowerInTheTaintBiome() {
assertEquals(4000, TileTaintedCrops.growthThreshold(false));
assertEquals(3550, TileTaintedCrops.growthThreshold(true));
}
@Test
public void cropOnlyCountsGrowthBelowStageFour() {
assertTrue(TileTaintedCrops.countsGrowth(0));
assertTrue(TileTaintedCrops.countsGrowth(3));
assertFalse(TileTaintedCrops.countsGrowth(4));
}
}