From b2b39c3fa552bd2f76bd8a97da28d742a03c69a2 Mon Sep 17 00:00:00 2001 From: Glease <4586901+Glease@users.noreply.github.com> Date: Sun, 11 Sep 2022 02:54:11 +0800 Subject: [PATCH] Use argument object for survival autoplace --- addon.gradle | 7 + dependencies.gradle | 3 + .../structurelib/StructureLibAPI.java | 15 +- .../constructable/ConstructableUtility.java | 4 +- .../constructable/ISurvivalConstructable.java | 68 ++- .../alignment/enumerable/Direction.java | 12 +- .../alignment/enumerable/ExtendedFacing.java | 22 + .../structure/AutoPlaceEnvironment.java | 218 ++++++++ .../DefaultSurvivalBuildEnvironment.java | 23 + .../structure/IStructureDefinition.java | 87 ++- .../structure/IStructureElement.java | 30 +- .../structure/IStructureElementChain.java | 24 + .../structure/IStructureElementCheckOnly.java | 6 + .../IStructureElementNoPlacement.java | 6 + .../structure/IStructureNavigate.java | 6 + .../structure/IStructureWalker.java | 23 +- .../structure/ISurvivalBuildEnvironment.java | 35 ++ .../structure/LazyStructureElement.java | 6 + .../structure/StructureDefinition.java | 46 +- .../structure/StructureElement_Bridge.java | 32 ++ .../structure/StructureUtility.java | 514 +++++++++++++----- .../SurvivalBuildStructureWalker.java | 43 +- .../structurelib/util/Vec3Impl.java | 12 + .../enumerable/ExtendedFacingTest.java | 24 + 24 files changed, 1083 insertions(+), 183 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/DefaultSurvivalBuildEnvironment.java create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/ISurvivalBuildEnvironment.java create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/StructureElement_Bridge.java create mode 100644 src/test/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacingTest.java diff --git a/addon.gradle b/addon.gradle index a985e11a..ee3a49ec 100644 --- a/addon.gradle +++ b/addon.gradle @@ -22,3 +22,10 @@ task javadocJar(type: Jar, dependsOn: javadoc) { artifacts { archives javadocJar } + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} diff --git a/dependencies.gradle b/dependencies.gradle index f129e37c..5c7b33e8 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,4 +3,7 @@ dependencies { compile("com.github.GTNewHorizons:GTNHLib:0.0.1:dev") runtime("com.github.GTNewHorizons:NotEnoughItems:2.2.20-GTNH:dev") + + testImplementation(platform('org.junit:junit-bom:5.8.2')) + testImplementation('org.junit.jupiter:junit-jupiter') } diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index 1c28f51d..0164289e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -6,7 +6,7 @@ import com.gtnewhorizon.structurelib.alignment.IAlignment; import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import com.gtnewhorizon.structurelib.net.AlignmentMessage; -import com.gtnewhorizon.structurelib.structure.IItemSource; +import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment; import cpw.mods.fml.common.network.NetworkRegistry; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; @@ -219,9 +219,20 @@ public class StructureLibAPI { * Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and * will be changed, but the general idea will always stay the same. *

- * Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)} + * Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} */ + @Deprecated public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) { + return isBlockTriviallyReplaceable(w, x, y, z, (EntityPlayer) actor); + } + + /** + * Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and + * will be changed, but the general idea will always stay the same. + *

+ * Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} + */ + public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayer actor) { // TODO extend this function a bit Block block = w.getBlock(x, y, z); return block.isAir(w, x, y, z) || block.isReplaceable(w, x, y, z); diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java index 5b2c7527..d4f9e0f7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java @@ -6,6 +6,7 @@ import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.IAlignment; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import com.gtnewhorizon.structurelib.structure.IItemSource; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; import java.util.WeakHashMap; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; @@ -78,8 +79,7 @@ public class ConstructableUtility { .survivalConstruct( aStack, ConfigurationHandler.INSTANCE.getAutoPlaceBudget(), - IItemSource.fromPlayer(playerMP), - playerMP); + ISurvivalBuildEnvironment.create(IItemSource.fromPlayer(playerMP), playerMP)); if (built > 0) { playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.built_stat", built)); } else if (built == -1) { diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java index 2d63e2f0..1268081c 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java @@ -1,17 +1,79 @@ package com.gtnewhorizon.structurelib.alignment.constructable; +import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment; import com.gtnewhorizon.structurelib.structure.IItemSource; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +/** + * An extension to {@link IConstructable}. Tile Entities (or delegates returned by {@link IConstructableProvider#getConstructable()} + * should implement this interface to allow third party tools and StructureLib builtin constructable tools to trigger + * autoplace in survival mode. + *

+ * While not originally planned, a creative player can also use this interface to trigger autoplace by using an + * {@link IItemSource} with every possible items in the game. + */ public interface ISurvivalConstructable extends IConstructable { + /** + * End player might want to disable this hack if it turns out to be too crashy + */ + boolean DISABLE_HACKY_MIGRATION_CODE = Boolean.getBoolean("structurelib.disable_isc_migration_hack"); + /** * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)} + * or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)} * - * @param elementBudget The server configured element budget. You can tune this up a bit if the structure is too big, but - * generally should not be a 4 digits number to not overwhelm the server + * @param elementBudget The server configured element budget. The implementor can choose to tune this up a bit if + * the structure is too big, but generally should not be a 4 digits number to not overwhelm the + * server * @return -1 if done, otherwise number of elements placed this round */ - int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor); + @Deprecated + default int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) { + return survivalConstruct(stackSize, elementBudget, ISurvivalBuildEnvironment.create(source, actor)); + } + + /** + * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} + * or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)} + * + * @param elementBudget The server configured element budget. The implementor can choose to tune this up a bit if + * the structure is too big, but generally should not be a 4 digits number to not overwhelm the + * server + * @return -1 if done, -2 if not supported (e.g. due to an incompatible change in the API of env, otherwise number + * of elements placed this round + */ + default int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) { + EntityPlayer actor = env.getActor(); + if (actor instanceof EntityPlayerMP) + return survivalConstruct(stackSize, elementBudget, env.getSource(), (EntityPlayerMP) actor); + else if (!DISABLE_HACKY_MIGRATION_CODE && __get_player() == null) { + // as far as I know, no implementor would actually do anything to actor beyond passing it down to + // IStructureDefinition, so it's probably fine to proceed like this + GlobalStates.entityPlayer.set(env.getActor()); + try { + return survivalConstruct(stackSize, elementBudget, env.getSource(), null); + } finally { + GlobalStates.entityPlayer.remove(); + } + } + return -2; + } + + + /** + * Internal hack. Do not use. + */ + @Deprecated + static EntityPlayer __get_player() { + return GlobalStates.entityPlayer.get(); + } +} + +class GlobalStates { + public static final ThreadLocal entityPlayer = new ThreadLocal<>(); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Direction.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Direction.java index 4352cf08..eaae4067 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Direction.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Direction.java @@ -1,6 +1,10 @@ package com.gtnewhorizon.structurelib.alignment.enumerable; import com.gtnewhorizon.structurelib.util.Vec3Impl; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import net.minecraftforge.common.util.ForgeDirection; public enum Direction { @@ -14,10 +18,12 @@ public enum Direction { private final ForgeDirection forgeDirection; private final Vec3Impl axisVector; public static final Direction[] VALUES = values(); + private static final Map reverseMap = + Arrays.stream(VALUES).collect(Collectors.toMap(d -> d.axisVector, Function.identity())); Direction(ForgeDirection forgeDirection) { this.forgeDirection = forgeDirection; - axisVector = new Vec3Impl(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ); + axisVector = Vec3Impl.getFromPool(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ); } public ForgeDirection getForgeDirection() { @@ -31,4 +37,8 @@ public enum Direction { public static Vec3Impl getAxisVector(ForgeDirection forgeDirection) { return VALUES[forgeDirection.ordinal()].axisVector; } + + public static Direction getByAxisVector(Vec3Impl vec3) { + return reverseMap.get(vec3); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java index 59156d68..ec76db85 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java @@ -354,6 +354,28 @@ public enum ExtendedFacing { integerAxisSwap.inverseTranslate(point, out); } + /** + * do note the ABC coordinate system is (X, -Y, Z). + */ + public ForgeDirection getWorldDirection(ForgeDirection abcDirection) { + switch (abcDirection) { + case NORTH: + return getRelativeForwardInWorld(); + case SOUTH: + return getRelativeBackInWorld(); + case UP: + return getRelativeDownInWorld(); + case DOWN: + return getRelativeUpInWorld(); + case EAST: + return getRelativeLeftInWorld(); + case WEST: + return getRelativeRightInWorld(); + default: + return ForgeDirection.UNKNOWN; + } + } + /** * Translates world offset to relative front facing offset * diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java b/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java new file mode 100644 index 00000000..2d834b51 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java @@ -0,0 +1,218 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; + +/** + * Represent the environment in which autoplace of a single element took place. + */ +public class AutoPlaceEnvironment { + private IItemSource source; + private final EntityPlayer actor; + private final Consumer chatter; + private final IStructureDefinition definition; + private final String piece; + private final ExtendedFacing facing; + final int[] offsetABC; + private final int[] baseOffsetABC; + + public static AutoPlaceEnvironment fromLegacy( + IItemSource source, EntityPlayerMP actor, Consumer chatter) { + if (source instanceof WrappedIItemSource) { + AutoPlaceEnvironment original = ((WrappedIItemSource) source).container; + // feels like this is extremely likely to cause issues, but this does allow us to recover lost info. + // TODO probably should remove this once all addons migrate over + if (actor != original.actor || chatter != original.chatter) { + AutoPlaceEnvironment newEnv = original; + if (actor != original.actor) newEnv = newEnv.withActor(actor); + if (chatter != original.chatter) newEnv = newEnv.withChatter(chatter); + return newEnv; + } + return original; + } + return new AutoPlaceEnvironment(source, actor, chatter, null, null, null, null, null); + } + + AutoPlaceEnvironment( + EntityPlayer actor, + Consumer chatter, + IStructureDefinition definition, + String piece, + ExtendedFacing facing, + int[] baseOffsetABC) { + this.source = null; + this.actor = actor; + this.chatter = chatter; + this.definition = definition; + this.piece = piece; + this.facing = facing; + this.offsetABC = new int[3]; + this.baseOffsetABC = baseOffsetABC; + } + + AutoPlaceEnvironment( + IItemSource source, + EntityPlayer actor, + Consumer chatter, + IStructureDefinition definition, + String piece, + ExtendedFacing facing, + int[] offsetABC, + int[] baseOffsetABC) { + this.source = definition != null && !(source instanceof WrappedIItemSource) + ? new WrappedIItemSource(this, source) + : source; + this.actor = actor; + this.chatter = chatter; + this.definition = definition; + this.piece = piece; + this.facing = facing; + this.offsetABC = offsetABC; + this.baseOffsetABC = baseOffsetABC; + } + + protected AutoPlaceEnvironment(AutoPlaceEnvironment parent) { + this( + parent.getSource(), + parent.getActor(), + parent.getChatter(), + parent.definition, + parent.piece, + parent.facing, + parent.offsetABC, + parent.baseOffsetABC); + } + + void setSource(IItemSource source) { + this.source = definition != null && !(source instanceof WrappedIItemSource) + ? new WrappedIItemSource(this, source) + : source; + } + + public APILevel getAPILevel() { + return definition == null ? APILevel.Legacy : APILevel.V2; + } + + /** + * From where survival autoplace will drain resources. + */ + public IItemSource getSource() { + return source; + } + + /** + * The initiator of actions. for very critical errors you can also just send the error messages here, bypassing + * any filter that {@link #getChatter()} might have. You might want to use + * {@link com.gtnewhorizon.structurelib.StructureLibAPI#addThrottledChat(Object, EntityPlayer, IChatComponent, short)} + * to help reduce spam. + */ + public EntityPlayer getActor() { + return actor; + } + + /** + * send error messages here. Caller will choose an appropriate way to forward it to player if the other fallbacks + * also fails. + */ + public Consumer getChatter() { + return chatter; + } + + /** + * Test if given location is contained within the current piece. + * @param offsetA offset in A direction relative to current element + * @param offsetB offset in B direction relative to current element + * @param offsetC offset in C direction relative to current element + * @return true if contained, false otherwise. + */ + public boolean isContainedInPiece(int offsetA, int offsetB, int offsetC) { + if (definition == null) return false; + return definition.isContainedInStructure( + piece, + offsetABC[0] + offsetA + baseOffsetABC[0], + offsetABC[1] + offsetB + baseOffsetABC[1], + offsetABC[2] + offsetC + baseOffsetABC[2]); + } + + public ExtendedFacing getFacing() { + return facing; + } + + /** + * Return a new instance with source modified to given value. + * + * @param source new source + * @return new instance + */ + public AutoPlaceEnvironment withSource(IItemSource source) { + return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC); + } + + /** + * Return a new instance with actor modified to given value. + * + * @param actor new actor + * @return new instance + */ + public AutoPlaceEnvironment withActor(EntityPlayerMP actor) { + return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC); + } + + /** + * Return a new instance with chatter modified to given value. + * + * @param chatter new chatter + * @return new instance + */ + public AutoPlaceEnvironment withChatter(Consumer chatter) { + return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC); + } + + private static class WrappedIItemSource implements IItemSource { + final AutoPlaceEnvironment container; + final IItemSource delegate; + + public WrappedIItemSource(AutoPlaceEnvironment container, IItemSource delegate) { + this.container = container; + this.delegate = delegate; + } + + @Override + @Nonnull + public Map take(Predicate predicate, boolean simulate, int count) { + return delegate.take(predicate, simulate, count); + } + + @Override + public ItemStack takeOne(Predicate predicate, boolean simulate) { + return delegate.takeOne(predicate, simulate); + } + + @Override + public boolean takeAll(Predicate predicate, boolean simulate, int count) { + return delegate.takeAll(predicate, simulate, count); + } + + @Override + public boolean takeOne(ItemStack stack, boolean simulate) { + return delegate.takeOne(stack, simulate); + } + + @Override + public boolean takeAll(ItemStack stack, boolean simulate) { + return delegate.takeAll(stack, simulate); + } + } + + public enum APILevel { + Legacy, + V2, + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/DefaultSurvivalBuildEnvironment.java b/src/main/java/com/gtnewhorizon/structurelib/structure/DefaultSurvivalBuildEnvironment.java new file mode 100644 index 00000000..77f571f5 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/DefaultSurvivalBuildEnvironment.java @@ -0,0 +1,23 @@ +package com.gtnewhorizon.structurelib.structure; + +import net.minecraft.entity.player.EntityPlayer; + +class DefaultSurvivalBuildEnvironment implements ISurvivalBuildEnvironment { + private final IItemSource source; + private final EntityPlayer actor; + + public DefaultSurvivalBuildEnvironment(IItemSource source, EntityPlayer actor) { + this.source = source; + this.actor = actor; + } + + @Override + public IItemSource getSource() { + return source; + } + + @Override + public EntityPlayer getActor() { + return actor; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index ed0ac6fd..56a7b6b7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -5,8 +5,10 @@ import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlock import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import java.util.function.Function; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.world.World; @@ -55,6 +57,8 @@ public interface IStructureDefinition { */ IStructureElement[] getStructureFor(String name); + boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC); + /** * Run a structure check. * @param object context object. usually multiblock controller. @@ -258,6 +262,7 @@ public interface IStructureDefinition { * @return number of elements built, or -1 if structure done * @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) */ + @Deprecated default int survivalBuild( T object, ItemStack trigger, @@ -274,8 +279,78 @@ public interface IStructureDefinition { IItemSource source, EntityPlayerMP actor, boolean check) { - SurvivalBuildStructureWalker walker = - new SurvivalBuildStructureWalker<>(object, trigger, source, actor, elementBudget, check); + EntityPlayer realActor; + if (actor == null) { + realActor = ISurvivalConstructable.__get_player(); + if (realActor == null) throw new IllegalArgumentException(); + } else { + realActor = actor; + } + return survivalBuild( + object, + trigger, + piece, + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + elementBudget, + ISurvivalBuildEnvironment.create(source, realActor), + check); + } + + /** + * Cause a survival build. + * + * @param object context object. usually multiblock controller. + * @param trigger The trigger item that contains channel data. + * @param piece the structure piece's string identifier. + * @param world the world object this check takes place in. + * @param extendedFacing the current structure's orientation. + * @param basePositionX X location of the structure + * @param basePositionY Y location of the structure + * @param basePositionZ Z location of the structure + * @param basePositionA see class javadoc + * @param basePositionB see class javadoc + * @param basePositionC see class javadoc + * @param elementBudget build up to this many elements + * @param env build environment. + * @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if anything + * would be placed. use with caution. + * if in doubt, call {@link IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)} + * after this call and set this parameter to false + * @return number of elements built, or -1 if structure done + * @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) + */ + default int survivalBuild( + T object, + ItemStack trigger, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + int elementBudget, + ISurvivalBuildEnvironment env, + boolean check) { + SurvivalBuildStructureWalker walker = new SurvivalBuildStructureWalker<>( + object, + trigger, + elementBudget, + env, + this, + piece, + extendedFacing, + new int[] {basePositionA, basePositionB, basePositionC}, + check); StructureUtility.iterateV2( getStructureFor(piece), world, @@ -341,7 +416,7 @@ public interface IStructureDefinition { basePositionA, basePositionB, basePositionC, - (e, w, x, y, z) -> e.check(object, w, x, y, z), + (e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z), "check"); } else { success = StructureUtility.iterateV2( @@ -354,7 +429,7 @@ public interface IStructureDefinition { basePositionA, basePositionB, basePositionC, - skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)), + skipBlockUnloaded((e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)), "check force"); } if (StructureLibAPI.isDebugEnabled() && success) { @@ -374,7 +449,7 @@ public interface IStructureDefinition { basePositionA, basePositionB, basePositionC, - ignoreBlockUnloaded((e, w, x, y, z) -> { + ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> { e.spawnHint(object, world, x, y, z, trigger); return true; }), @@ -390,7 +465,7 @@ public interface IStructureDefinition { basePositionA, basePositionB, basePositionC, - ignoreBlockUnloaded((e, w, x, y, z) -> { + ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> { e.placeBlock(object, world, x, y, z, trigger); return true; }), diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index a50fffb7..d6102667 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -6,6 +6,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; import com.gtnewhorizon.structurelib.StructureLibAPI; import java.util.function.Consumer; import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.util.IChatComponent; @@ -24,7 +25,7 @@ public interface IStructureElement { /** * Try place the block by taking resource from given IItemSource. *

- * You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayerMP)} + * You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)} * or its overloads to implement this. * * @param trigger trigger item @@ -33,7 +34,11 @@ public interface IStructureElement { * any filter that chatter might have. * @param chatter send error messages here. Caller will choose an appropriate way to forward it to player if the * other fallbacks also fails. + * @deprecated caller should call the non deprecated overload. implementor for reusable {@link IStructureElement} + * should still implement this. implementor for private implementations can freely ignore this as long as you know + * the caller will not call this overload. */ + @Deprecated default PlaceResult survivalPlaceBlock( T t, World world, @@ -53,6 +58,29 @@ public interface IStructureElement { return PlaceResult.SKIP; } + /** + * Try place the block by taking resource from given IItemSource. + *

+ * You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)} + * or its overloads to implement this. + * + * @param trigger trigger item + * @param env autoplacing environment + */ + default PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + EntityPlayer actor = env.getActor(); + if (actor instanceof EntityPlayerMP) + return survivalPlaceBlock( + t, world, x, y, z, trigger, env.getSource(), (EntityPlayerMP) actor, env.getChatter()); + if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit"); + if (StructureLibAPI.isDebugEnabled()) + LOGGER.info( + "Fallback shim code of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}", + getClass().getName()); + return PlaceResult.SKIP; + } + /** * Forget the messed up class dependency graph for now. this is just so convenient. */ diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java index 82a697af..3cd05b6a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java @@ -74,4 +74,28 @@ public interface IStructureElementChain extends IStructureElement { // TODO need reconsider to ensure this is the right course of action return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT; } + + @Override + default PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + boolean haveSkip = false; + List bufferedNoise = new ArrayList<>(); + for (IStructureElement fallback : fallbacks()) { + PlaceResult result = + fallback.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add)); + switch (result) { + case REJECT: + break; + case SKIP: + haveSkip = true; + break; + default: + return result; + } + } + // dump all that noise back into upstream + bufferedNoise.forEach(env.getChatter()); + // TODO need reconsider to ensure this is the right course of action + return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java index 854f405b..e874389d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java @@ -30,4 +30,10 @@ public interface IStructureElementCheckOnly extends IStructureElement { Consumer chatter) { return PlaceResult.SKIP; } + + @Override + default PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + return PlaceResult.SKIP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java index db861178..0570e862 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java @@ -26,6 +26,12 @@ public interface IStructureElementNoPlacement extends IStructureElement { return PlaceResult.REJECT; } + @Override + default PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + return PlaceResult.REJECT; + } + @Override default IStructureElementNoPlacement noPlacement() { return this; diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java index 00e3a89e..a464b317 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java @@ -39,6 +39,12 @@ interface IStructureNavigate extends IStructureElement { return PlaceResult.SKIP; } + @Override + default PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + return PlaceResult.SKIP; + } + default boolean isNavigating() { return true; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java index 2e3ec862..9284f3a9 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java @@ -2,23 +2,25 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.world.World; -public interface IStructureWalker { - boolean visit(IStructureElement element, World world, int x, int y, int z); +interface IStructureWalker { + boolean visit(IStructureElement element, World world, int x, int y, int z, int a, int b, int c); - default boolean blockNotLoaded(IStructureElement element, World world, int x, int y, int z) { + default boolean blockNotLoaded( + IStructureElement element, World world, int x, int y, int z, int a, int b, int c) { return false; } static IStructureWalker ignoreBlockUnloaded(IStructureWalker walker) { return new IStructureWalker() { @Override - public boolean visit(IStructureElement element, World world, int x, int y, int z) { - return walker.visit(element, world, x, y, z); + public boolean visit(IStructureElement element, World world, int x, int y, int z, int a, int b, int c) { + return walker.visit(element, world, x, y, z, a, b, c); } @Override - public boolean blockNotLoaded(IStructureElement element, World world, int x, int y, int z) { - return walker.visit(element, world, x, y, z); + public boolean blockNotLoaded( + IStructureElement element, World world, int x, int y, int z, int a, int b, int c) { + return walker.visit(element, world, x, y, z, a, b, c); } }; } @@ -26,12 +28,13 @@ public interface IStructureWalker { static IStructureWalker skipBlockUnloaded(IStructureWalker walker) { return new IStructureWalker() { @Override - public boolean visit(IStructureElement element, World world, int x, int y, int z) { - return walker.visit(element, world, x, y, z); + public boolean visit(IStructureElement element, World world, int x, int y, int z, int a, int b, int c) { + return walker.visit(element, world, x, y, z, a, b, c); } @Override - public boolean blockNotLoaded(IStructureElement element, World world, int x, int y, int z) { + public boolean blockNotLoaded( + IStructureElement element, World world, int x, int y, int z, int a, int b, int c) { return true; } }; diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ISurvivalBuildEnvironment.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ISurvivalBuildEnvironment.java new file mode 100644 index 00000000..bda10471 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/ISurvivalBuildEnvironment.java @@ -0,0 +1,35 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/* + * Architecture notes: this should never get any callback from survival build. All structure significant code belongs + * within the IStructureElement. This will make the user easier to maintain the IStructureDefinition. + * Structure Element knows the most about what this structure element should do, not the caller. + */ +public interface ISurvivalBuildEnvironment { + /** + * Get the source of the items. This will be invoked exactly once per structure element build. + *

+ * If the call chain ever got into the old API, this will be called at least once per {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)} call. + */ + IItemSource getSource(); + + /** + * Get the origin of action. This will be invoked at least once per {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)} call. + */ + EntityPlayer getActor(); + + /** + * Create a default implementation with three parameters all being constants. + * + * @param source from where to drain resource + * @param actor source of action + */ + static ISurvivalBuildEnvironment create(IItemSource source, EntityPlayer actor) { + return new DefaultSurvivalBuildEnvironment(source, actor); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java index 6739673e..907f74ac 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java @@ -51,4 +51,10 @@ class LazyStructureElement implements IStructureElementDeferred { Consumer chatter) { return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { + return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index eb0946ca..c82ff789 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -2,7 +2,6 @@ package com.gtnewhorizon.structurelib.structure; import static com.gtnewhorizon.structurelib.structure.StructureUtility.*; -import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.util.Vec3Impl; import java.util.*; import java.util.stream.Collectors; @@ -11,6 +10,7 @@ public class StructureDefinition implements IStructureDefinition { private final Map> elements; private final Map shapes; private final Map[]> structures; + private final Map> occupiedSpaces; public static Builder builder() { return new Builder<>(); @@ -19,10 +19,12 @@ public class StructureDefinition implements IStructureDefinition { private StructureDefinition( Map> elements, Map shapes, - Map[]> structures) { + Map[]> structures, + Map> occupiedSpaces) { this.elements = elements; this.shapes = shapes; this.structures = structures; + this.occupiedSpaces = occupiedSpaces; } public static class Builder { @@ -33,11 +35,13 @@ public class StructureDefinition implements IStructureDefinition { private final Map navigates; private final Map> elements; private final Map shapes; + private final Map> occupiedSpaces; private Builder() { navigates = new HashMap<>(); elements = new HashMap<>(); shapes = new HashMap<>(); + occupiedSpaces = new HashMap<>(); } public Map> getElements() { @@ -80,22 +84,37 @@ public class StructureDefinition implements IStructureDefinition { } builder.setLength(builder.length() - 1); } + Set occupiedSpace = new HashSet<>(); + // these track the global current location + int aa = 0, bb = 0, cc = 0; + // these track the vec3 towards next meaningful element int a = 0, b = 0, c = 0; + // I do know these are pretty bad variable naming, but I have no better idea for (int i = 0; i < builder.length(); i++) { char ch = builder.charAt(i); - if (ch == ' ' || ch == '~') { + if (ch == ' ') { builder.setCharAt(i, A); ch = A; + } else if (ch == '~') { + builder.setCharAt(i, A); + ch = A; + occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); } if (ch == A) { + aa++; a++; } else if (ch == B) { + aa = 0; a = 0; + bb++; b++; } else if (ch == C) { + aa = 0; + bb = 0; a = 0; b = 0; c++; + cc++; } else if (a != 0 || b != 0 || c != 0) { Vec3Impl vec3 = new Vec3Impl(a, b, c); Character navigate = navigates.get(vec3); @@ -105,12 +124,19 @@ public class StructureDefinition implements IStructureDefinition { addElement(navigate, step(vec3)); } builder.setCharAt(i - 1, navigate); + occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); + aa++; a = 0; b = 0; c = 0; + } else { + occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); + aa++; } } + occupiedSpaces.put(name, occupiedSpace); + String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", ""); if (built.contains("+")) { @@ -130,11 +156,8 @@ public class StructureDefinition implements IStructureDefinition { public IStructureDefinition build() { Map[]> structures = compileStructureMap(); - if (StructureLibAPI.isDebugEnabled()) { - return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures); - } else { - return structures::get; - } + return new StructureDefinition<>( + new HashMap<>(elements), new HashMap<>(shapes), structures, occupiedSpaces); } @SuppressWarnings("unchecked") @@ -194,4 +217,11 @@ public class StructureDefinition implements IStructureDefinition { if (elements == null) throw new NoSuchElementException(name); return elements; } + + @Override + public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) { + Set occupiedSpace = occupiedSpaces.get(name); + if (occupiedSpace == null) throw new NoSuchElementException(name); + return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC)); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureElement_Bridge.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureElement_Bridge.java new file mode 100644 index 00000000..afbf58bf --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureElement_Bridge.java @@ -0,0 +1,32 @@ +package com.gtnewhorizon.structurelib.structure; + +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; +import net.minecraft.world.World; + +/** + * Internal bridge class for easing implementation + */ +abstract class StructureElement_Bridge implements IStructureElement { + // seal the deprecated method to prevent accidental overrides + @Deprecated + @Override + public final PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return survivalPlaceBlock(t, world, x, y, z, trigger, AutoPlaceEnvironment.fromLegacy(s, actor, chatter)); + } + + // clear the default implementation. we don't need that. + public abstract PlaceResult survivalPlaceBlock( + T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 7c79f8dd..0992e8a7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -112,7 +112,7 @@ import org.apache.commons.lang3.tuple.Pair; *

Helper Methods

* These don't construct a {@link IStructureElement}, but is helpful to the instantiation or implementation of these. *