mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 04:55:01 +00:00
Compare commits
15
Commits
master
...
1.2.0-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2574f61ef4 | ||
|
|
339d6da99c | ||
|
|
c16d2aa5e1 | ||
|
|
6f8ac8e33e | ||
|
|
5d5a731af3 | ||
|
|
d9d237ed3a | ||
|
|
3b9a77b9fd | ||
|
|
5d0f63e2e9 | ||
|
|
f43aaecfb6 | ||
|
|
857150e18d | ||
|
|
ce8b0b011a | ||
|
|
f5b2f41ad6 | ||
|
|
9700499da2 | ||
|
|
003825915d | ||
|
|
b2b39c3fa5 |
No files matched your search
@@ -6,6 +6,7 @@ javadoc {
|
||||
include "com/gtnewhorizon/structurelib/structure/**"
|
||||
include "com/gtnewhorizon/structurelib/util/**"
|
||||
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
|
||||
include "com/gtnewhorizon/structurelib/StructureEvent.java"
|
||||
|
||||
options {
|
||||
overview "src/main/javadoc/overview.html"
|
||||
@@ -22,3 +23,10 @@ task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureElement;
|
||||
import cpw.mods.fml.common.eventhandler.Event;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
/**
|
||||
* Subscribers are <b>required</b> to not keep reference to these events, nor to depend on values of these events after
|
||||
* the method call returns.
|
||||
* <p>
|
||||
* All of these events will be fired on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}, if they are
|
||||
* enabled by {@link StructureLibAPI#enableInstrument(Object)}.
|
||||
* <p>
|
||||
* All public stuff in this class and its enclosed classes are considered part of the public API.
|
||||
* Backwards compatibility is maintained to the maximum extend possible.
|
||||
*/
|
||||
public abstract class StructureEvent extends Event {
|
||||
/*
|
||||
* In case the event is passed to another thread for processing.
|
||||
*/
|
||||
private final Object identifier = StructureLibAPI.instrument.get();
|
||||
|
||||
StructureEvent() {}
|
||||
|
||||
/**
|
||||
* The instrument identifier. Subscribers should filter by these identifiers to prevent listening to a structure
|
||||
* event for other people.
|
||||
*/
|
||||
public Object getInstrumentIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired <b>just before</b> any structure element is visited.
|
||||
*/
|
||||
public static final class StructureElementVisitedEvent extends StructureEvent {
|
||||
private final World world;
|
||||
private final int x, y, z;
|
||||
private final int a, b, c;
|
||||
private final IStructureElement<?> element;
|
||||
|
||||
/**
|
||||
* Fire this event on {@link MinecraftForge#EVENT_BUS} by populating it with given arguments.
|
||||
* Do nothing if instrumenting isn't enabled for current thread.
|
||||
* <p>
|
||||
* Any {@link Exception} thrown by event listeners will be logged and ignored. {@link Error} will be propagated
|
||||
* as is.
|
||||
*/
|
||||
public static void fireEvent(
|
||||
World world, int x, int y, int z, int a, int b, int c, IStructureElement<?> element) {
|
||||
if (StructureLibAPI.isInstrumentEnabled()) {
|
||||
try {
|
||||
MinecraftForge.EVENT_BUS.post(new StructureElementVisitedEvent(world, x, y, z, a, b, c, element));
|
||||
} catch (Exception e) {
|
||||
// logging is done by event bus, we just ignore the consequence here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StructureElementVisitedEvent(
|
||||
World world, int x, int y, int z, int a, int b, int c, IStructureElement<?> element) {
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* A position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* B position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* C position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element being visited.
|
||||
*/
|
||||
public IStructureElement<?> getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StructureElementVisitedEvent{" + "a="
|
||||
+ a + ", b="
|
||||
+ b + ", c="
|
||||
+ c + ", element="
|
||||
+ element + ", world="
|
||||
+ getWorld() + ", x="
|
||||
+ getX() + ", y="
|
||||
+ getY() + ", z="
|
||||
+ getZ() + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event.
|
||||
*/
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ 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 com.gtnewhorizon.structurelib.structure.IStructureDefinition;
|
||||
import cpw.mods.fml.common.network.NetworkRegistry;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -38,6 +39,54 @@ public class StructureLibAPI {
|
||||
public static final int HINT_BLOCK_META_AIR = 13;
|
||||
public static final int HINT_BLOCK_META_NOT_AIR = 14;
|
||||
public static final int HINT_BLOCK_META_ERROR = 15;
|
||||
static final ThreadLocal<Object> instrument = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Start instrumenting <b>for the current thread only</b>. When instrumentation is enabled,
|
||||
* {@link IStructureDefinition}'s builtin iteration methods will send {@link StructureEvent} and its subclasses when
|
||||
* appropriate. Otherwise, these events will not be sent at all, regardless of whether a listener for these events
|
||||
* are registered.
|
||||
* <p>
|
||||
* Identifiers are required to be value-comparable, i.e. overrides {@link Object#equals(Object)}.
|
||||
* Suggested identifier includes
|
||||
* <ul>
|
||||
* <li>Mod Instance or {@link cpw.mods.fml.common.ModContainer}</li>
|
||||
* <li>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable} and its friends, if you are triggering structure check via this interface.</li>
|
||||
* <li>{@link net.minecraft.util.ResourceLocation}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* It's an API abuse for normal {@link IStructureDefinition} users to depend on this feature. They have full control
|
||||
* and full knowledge of the {@link IStructureDefinition} they are using. Using events to instrument the structure
|
||||
* check/autoplace is completely unnecessary and does nothing but add overhead.
|
||||
* <p>
|
||||
* Instruments are required to call {@link #disableInstrument()} whenever it is done with its business, as there can
|
||||
* be only one instrument for any given thread.
|
||||
*
|
||||
* @param identifier an identifier sent along the actual event.
|
||||
* @throws IllegalStateException if instrument has already been enabled.
|
||||
*/
|
||||
public static void enableInstrument(Object identifier) {
|
||||
if (isInstrumentEnabled()) throw new IllegalStateException();
|
||||
instrument.set(instrument);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop instrumenting.
|
||||
*
|
||||
* @throws IllegalStateException if instrumenting hasn't been enabled.
|
||||
*/
|
||||
public static void disableInstrument() {
|
||||
if (!isInstrumentEnabled()) throw new IllegalStateException();
|
||||
instrument.set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if instrumenting is enabled.
|
||||
* @return true if enabled, false otherwise
|
||||
*/
|
||||
public static boolean isInstrumentEnabled() {
|
||||
return instrument.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
|
||||
@@ -59,12 +108,13 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using given textures with a tint.
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param icons 6 texture. in forge direction order.
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
|
||||
*/
|
||||
public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
|
||||
proxy.hintParticleTinted(w, x, y, z, icons, RGBa);
|
||||
@@ -72,13 +122,14 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using textures from given block with a tint.
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param block block to take texture from
|
||||
* @param meta the meta of block to take texture from
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
|
||||
* @param meta the meta of block to take texture from
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
|
||||
*/
|
||||
public static void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
|
||||
proxy.hintParticleTinted(w, x, y, z, block, meta, RGBa);
|
||||
@@ -86,10 +137,11 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using given textures.
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param icons 6 texture. in forge direction order.
|
||||
*/
|
||||
public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
|
||||
@@ -98,12 +150,13 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using textures from given block.
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param block block to take texture from
|
||||
* @param meta the meta of block to take texture from
|
||||
* @param meta the meta of block to take texture from
|
||||
*/
|
||||
public static void hintParticle(World w, int x, int y, int z, Block block, int meta) {
|
||||
proxy.hintParticle(w, x, y, z, block, meta);
|
||||
@@ -111,7 +164,7 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Update the tint of given hint particle. Do nothing if particle not found.
|
||||
*
|
||||
* <p>
|
||||
* Can be called on either client side or server side.
|
||||
* Server side will schedule a network message to update it for this player only.
|
||||
* Will do nothing if no hint particle is found at given location.
|
||||
@@ -219,9 +272,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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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);
|
||||
@@ -229,10 +293,11 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Send chat to player, but throttled.
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
|
||||
* to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
*
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
|
||||
* to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
|
||||
* we purposefully chose to not use a bigger data type to limit how long this interval can be
|
||||
*/
|
||||
@@ -243,12 +308,13 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Send chat to player, but throttled.
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
|
||||
* to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
|
||||
* we purposefully chose to not use a bigger data type to limit how long this interval can be
|
||||
*
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
|
||||
* to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
|
||||
* we purposefully chose to not use a bigger data type to limit how long this interval can be
|
||||
* @param forceUpdateLastSend if true, always update the last send time, even if the message isn't actually sent.
|
||||
*/
|
||||
public static void addThrottledChat(
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+8
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -10,6 +11,13 @@ import net.minecraft.item.ItemStack;
|
||||
public interface IConstructable {
|
||||
void construct(ItemStack stackSize, boolean hintsOnly);
|
||||
|
||||
/**
|
||||
* Get the structure definition used for this constructable. Can be null if this constructable is not backed by one.
|
||||
*/
|
||||
default IStructureDefinition<?> getStructureDefinition() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
String[] getStructureDescription(ItemStack stackSize);
|
||||
}
|
||||
+64
-3
@@ -1,17 +1,78 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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> entityPlayer = new ThreadLocal<>();
|
||||
}
|
||||
@@ -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<Vec3Impl, Direction> 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
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<IChatComponent> 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, EntityPlayer actor, Consumer<IChatComponent> 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<IChatComponent> 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<IChatComponent> 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
|
||||
? actor instanceof EntityPlayerMP ? APILevel.Legacy : APILevel.LegacyRelaxed
|
||||
: 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<IChatComponent> getChatter() {
|
||||
return chatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given location is contained within the current piece.
|
||||
*
|
||||
* @param offsetA offset in A direction <b>relative to current element</b>
|
||||
* @param offsetB offset in B direction <b>relative to current element</b>
|
||||
* @param offsetC offset in C direction <b>relative to current element</b>
|
||||
* @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(EntityPlayer 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<IChatComponent> 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<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count) {
|
||||
return delegate.take(predicate, simulate, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
|
||||
return delegate.takeOne(predicate, simulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takeAll(Predicate<ItemStack> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the various API level an {@link AutoPlaceEnvironment} has implemented.
|
||||
* <p>
|
||||
* Enum constants are defined in chronological order.
|
||||
*/
|
||||
public enum APILevel {
|
||||
/**
|
||||
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}.
|
||||
* {@link #getActor()} is guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
|
||||
*/
|
||||
Legacy,
|
||||
/**
|
||||
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}.
|
||||
* {@link #getActor()} is <b>NOT</b> guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
|
||||
*/
|
||||
LegacyRelaxed,
|
||||
/**
|
||||
* Implements everything so far we have defined.
|
||||
*/
|
||||
V2,
|
||||
}
|
||||
}
|
||||
+23
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,15 @@ import net.minecraft.item.ItemStack;
|
||||
*/
|
||||
public interface IItemSource {
|
||||
/**
|
||||
* We are probably not going to need to get a lot of items from this method,
|
||||
* Retrieve some amount of items from this item source matching given filter.
|
||||
* <p>
|
||||
* We probably don't need to get a lot of items from this method,
|
||||
* so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @param count how many items to extract.
|
||||
* @return A nonnull map reflecting the result of extraction. Note this map is NBT and metadata aware.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count);
|
||||
@@ -25,9 +32,13 @@ public interface IItemSource {
|
||||
/**
|
||||
* Take exactly one item that matches the predicate.
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return item stack, or null if none matches
|
||||
* @throws IllegalArgumentException if given predicate is null.
|
||||
*/
|
||||
default ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
|
||||
if (predicate == null) throw new IllegalArgumentException();
|
||||
Map<ItemStack, Integer> take = take(predicate, simulate, 1);
|
||||
return take.isEmpty() ? null : take.keySet().iterator().next();
|
||||
}
|
||||
@@ -35,7 +46,11 @@ public interface IItemSource {
|
||||
/**
|
||||
* Take exactly count amount of items matching predicate.
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @param count how many items to extract.
|
||||
* @return true if at least count amount of items can be/is taken.
|
||||
* @throws IllegalArgumentException if given predicate is null
|
||||
*/
|
||||
default boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
|
||||
// fast path for 1 item take requests
|
||||
@@ -48,21 +63,36 @@ public interface IItemSource {
|
||||
|
||||
/**
|
||||
* Take exactly one item. ItemStack given must have a stack size of 1.
|
||||
* <p>
|
||||
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}.
|
||||
* Implementations that support item storage indexed by ItemStack can use this to speed up calculation.
|
||||
*
|
||||
* @param stack what item to extract. The NBT tag of this ItemStack is siginifcant and will be
|
||||
* considered during matching. must have a stack size of 1.
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return true if found. false otherwise
|
||||
* @throws IllegalArgumentException if given stack is invalid, or has a stack size other than 1.
|
||||
*/
|
||||
default boolean takeOne(ItemStack stack, boolean simulate) {
|
||||
if (stack == null || stack.getItem() == null || stack.stackSize != 1) throw new IllegalArgumentException();
|
||||
ItemStack took = takeOne(ItemStackPredicate.from(stack, NBTMode.EXACT), simulate);
|
||||
return took == null || took.stackSize <= 0;
|
||||
return took != null && took.stackSize > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take some item. Will not take item if it cannot find enough items to take.
|
||||
* <p>
|
||||
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}.
|
||||
* Implementations that support indexing off ItemStack can use this to speed up calculation.
|
||||
*
|
||||
* @param stack what item and how many to extract. The NBT tag of this ItemStack is siginifcant and will be
|
||||
* considered during matching
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return true if enough item can be/has been extracted. false otherwise
|
||||
* @throws IllegalArgumentException if given stack is invalid
|
||||
*/
|
||||
default boolean takeAll(ItemStack stack, boolean simulate) {
|
||||
if (stack == null || stack.getItem() == null) throw new IllegalArgumentException();
|
||||
// fast path for 1 item take requests
|
||||
if (stack.stackSize == 1) return takeOne(stack, simulate);
|
||||
ItemStackPredicate predicate = ItemStackPredicate.from(stack, NBTMode.EXACT);
|
||||
|
||||
@@ -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<T> {
|
||||
*/
|
||||
IStructureElement<T>[] 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<T> {
|
||||
* @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<T> {
|
||||
IItemSource source,
|
||||
EntityPlayerMP actor,
|
||||
boolean check) {
|
||||
SurvivalBuildStructureWalker<T> 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<T> 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<T> {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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;
|
||||
}),
|
||||
|
||||
@@ -4,15 +4,26 @@ import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* Use StructureUtility to instantiate
|
||||
* Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It represents
|
||||
* what a particular element can be and how this particular element should be autoplaced.
|
||||
*/
|
||||
public interface IStructureElement<T> {
|
||||
boolean check(T t, World world, int x, int y, int z);
|
||||
@@ -24,7 +35,7 @@ public interface IStructureElement<T> {
|
||||
/**
|
||||
* Try place the block by taking resource from given IItemSource.
|
||||
* <p>
|
||||
* 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 +44,11 @@ public interface IStructureElement<T> {
|
||||
* 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 +68,87 @@ public interface IStructureElement<T> {
|
||||
return PlaceResult.SKIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified version of survivalPlaceBlock. Return null if not implemented.
|
||||
* <p>
|
||||
* <b>BY OVERRIDING THIS TO NULL, YOU ACKNOWLEDGE THAT THE {@link #check(Object, World, int, int, int)} OF THIS
|
||||
* CLASS IS SIDE EFFECT FREE AND CAN HAVE ITS CHECK CALLED WITHOUT BREAKING ANYTHING. OTHERWISE OVERRIDE THE
|
||||
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} TO PROVIDE A SIDEEFFECT
|
||||
* FREE {@link PlaceResult#SKIP}.</b>
|
||||
* <p>
|
||||
* It should be noticed that this filter is only advisory and the actual {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
* or {@link #check(Object, World, int, int, int)} are free to reject/accept/place blocks not contained in this list,
|
||||
* e.g. when the element can be placed anywhere in the structure, but no more than 3 overall; when the structure
|
||||
* element cannot know all blocks it will accept at the time of calling; when this is backed by a legacy
|
||||
* {@link IStructureElement} that just didn't implement this API.
|
||||
*
|
||||
* @param trigger trigger item
|
||||
* @param env autoplacing environment
|
||||
* @return null if not implemented, otherwise an instance describing what kind of blocks will be placed by {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
*/
|
||||
@Nullable
|
||||
default BlocksToPlace getBlocksToPlace(
|
||||
T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try place the block by taking resource from given {@link IItemSource}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* The default implementation provided will
|
||||
* <ol>
|
||||
* <li>Get {@link BlocksToPlace} via {@link #getBlocksToPlace(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}. If not null
|
||||
* <ol>
|
||||
* <li>call {@link #check(Object, World, int, int, int)}. If this returns {@code true}, {@link PlaceResult#SKIP} will be returned without further action.</li>
|
||||
* <li>Use the {@link BlocksToPlace} retrieved earlier and passed in {@link IItemSource} to determine an item to place</li>
|
||||
* <li>Hand over control to {@link StructureUtility#survivalPlaceBlock(ItemStack, NBTMode, NBTTagCompound, boolean, World, int, int, int, IItemSource, EntityPlayer, Consumer)}</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* <li>Call legacy {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} if {@code env.getActor()} contains an {@link EntityPlayerMP}</li>
|
||||
* <li>Emit warning under debug mode (or throw exception under panic mode), then return {@link PlaceResult#SKIP}</li>
|
||||
* </ol>
|
||||
* It should be noticed that the default implementation is likely to cause unexpected issues if the
|
||||
* {@link #check(Object, World, int, int, int)} is not side effect free, or is not idempotent.
|
||||
* E.g. the {@link #check(Object, World, int, int, int)} will add current coord to a {@link java.util.List}. As
|
||||
* this method might be invoked at a location that is already accepted, this will cause the machine to have multiple
|
||||
* of this coord in its list.
|
||||
*
|
||||
* @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) {
|
||||
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
IItemSource source = env.getSource();
|
||||
EntityPlayer actor = env.getActor();
|
||||
Consumer<IChatComponent> chatter = env.getChatter();
|
||||
if (e != null) {
|
||||
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
|
||||
if (e.getStacks() == null) {
|
||||
ItemStack taken = source.takeOne(e.getPredicate(), true);
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
taken, NBTMode.EXACT, taken.stackTagCompound, true, world, x, y, z, source, actor, chatter);
|
||||
}
|
||||
for (ItemStack stack : e.getStacks()) {
|
||||
if (!source.takeOne(stack, true)) continue;
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
stack, NBTMode.EXACT, stack.stackTagCompound, true, world, x, y, z, source, actor, chatter);
|
||||
}
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
if (actor instanceof EntityPlayerMP)
|
||||
return survivalPlaceBlock(t, world, x, y, z, trigger, source, (EntityPlayerMP) actor, chatter);
|
||||
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.
|
||||
*/
|
||||
@@ -126,4 +222,82 @@ public interface IStructureElement<T> {
|
||||
*/
|
||||
ACCEPT_STOP;
|
||||
}
|
||||
|
||||
final class BlocksToPlace {
|
||||
public static final BlocksToPlace errored = createEmpty();
|
||||
private final Predicate<ItemStack> predicate;
|
||||
private final Iterable<ItemStack> stacks;
|
||||
|
||||
public static BlocksToPlace createEmpty() {
|
||||
return new BlocksToPlace(s -> false, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(ItemStack... stacks) {
|
||||
return create(Arrays.asList(stacks));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Iterable<ItemStack> stacks) {
|
||||
Predicate<ItemStack> predicate = null;
|
||||
|
||||
for (ItemStack stack : stacks) {
|
||||
ItemStackPredicate p = ItemStackPredicate.from(stack, NBTMode.EXACT);
|
||||
if (predicate == null) predicate = p;
|
||||
else predicate = predicate.or(p);
|
||||
}
|
||||
|
||||
return new BlocksToPlace(predicate, stacks);
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Block block, int meta) {
|
||||
Item itemBlock = Item.getItemFromBlock(block);
|
||||
if (itemBlock instanceof ISpecialItemBlock) {
|
||||
meta = ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta);
|
||||
}
|
||||
return create(itemBlock, meta);
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Item item, int meta) {
|
||||
return new BlocksToPlace(
|
||||
ItemStackPredicate.from(item).setMeta(meta),
|
||||
Collections.singletonList(new ItemStack(item, 1, meta)));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(ItemStack itemStack) {
|
||||
return new BlocksToPlace(ItemStackPredicate.from(itemStack), Collections.singletonList(itemStack));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Predicate<ItemStack> predicate) {
|
||||
return new BlocksToPlace(predicate, null);
|
||||
}
|
||||
|
||||
BlocksToPlace(Predicate<ItemStack> predicate, Iterable<ItemStack> stacks) {
|
||||
this.predicate = predicate;
|
||||
this.stacks = stacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blocks to place filter. This is usually slower as it requires walking through inventories one by one.
|
||||
* <p>
|
||||
* Suitable for use with {@link IItemSource#takeOne(Predicate, boolean)}
|
||||
*
|
||||
* @return a predicate. never null.
|
||||
*/
|
||||
@Nonnull
|
||||
public Predicate<ItemStack> getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ItemStacks that this one is known to accept. Can be null if this info is not available at the time
|
||||
* this instance is constructed.
|
||||
* <p>
|
||||
* Suitable for use with {@link IItemSource#takeOne(ItemStack, boolean)}.
|
||||
* <p>
|
||||
* This should be equivalent to the predicate returned from {@link #getPredicate()}
|
||||
*/
|
||||
@Nullable
|
||||
public Iterable<ItemStack> getStacks() {
|
||||
return stacks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
@@ -44,6 +47,23 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default BlocksToPlace getBlocksToPlace(
|
||||
T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) {
|
||||
Predicate<ItemStack> predicate = null;
|
||||
List<Iterable<ItemStack>> is = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
if (e == null) continue;
|
||||
if (predicate == null) predicate = e.getPredicate();
|
||||
else predicate = predicate.or(e.getPredicate());
|
||||
is.add(e.getStacks());
|
||||
}
|
||||
if (predicate == null) return null;
|
||||
return new BlocksToPlace(predicate, Iterables.concat(is));
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
@@ -74,4 +94,28 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
// 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<IChatComponent> bufferedNoise = new ArrayList<>();
|
||||
for (IStructureElement<T> 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;
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,10 @@ public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
|
||||
Consumer<IChatComponent> 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;
|
||||
}
|
||||
}
|
||||
+6
@@ -26,6 +26,12 @@ public interface IStructureElementNoPlacement<T> extends IStructureElement<T> {
|
||||
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<T> noPlacement() {
|
||||
return this;
|
||||
|
||||
@@ -39,6 +39,12 @@ interface IStructureNavigate<T> extends IStructureElement<T> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,25 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IStructureWalker<T> {
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z);
|
||||
interface IStructureWalker<T> {
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
|
||||
|
||||
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
default boolean blockNotLoaded(
|
||||
IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean visit(IStructureElement<T> 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<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean blockNotLoaded(
|
||||
IStructureElement<T> 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<T> {
|
||||
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean visit(IStructureElement<T> 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<T> element, World world, int x, int y, int z) {
|
||||
public boolean blockNotLoaded(
|
||||
IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
@@ -38,6 +39,13 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
return get(t).spawnHint(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlocksToPlace getBlocksToPlace(
|
||||
T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) {
|
||||
return get(t).getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
@@ -51,4 +59,10 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
Consumer<IChatComponent> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<T> implements IStructureDefinition<T> {
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, IStructureElement<T>[]> structures;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
public static <B> Builder<B> builder() {
|
||||
return new Builder<>();
|
||||
@@ -19,10 +19,12 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private StructureDefinition(
|
||||
Map<Character, IStructureElement<T>> elements,
|
||||
Map<String, String> shapes,
|
||||
Map<String, IStructureElement<T>[]> structures) {
|
||||
Map<String, IStructureElement<T>[]> structures,
|
||||
Map<String, Set<Vec3Impl>> occupiedSpaces) {
|
||||
this.elements = elements;
|
||||
this.shapes = shapes;
|
||||
this.structures = structures;
|
||||
this.occupiedSpaces = occupiedSpaces;
|
||||
}
|
||||
|
||||
public static class Builder<T> {
|
||||
@@ -33,11 +35,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private final Map<Vec3Impl, Character> navigates;
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
private Builder() {
|
||||
navigates = new HashMap<>();
|
||||
elements = new HashMap<>();
|
||||
shapes = new HashMap<>();
|
||||
occupiedSpaces = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<Character, IStructureElement<T>> getElements() {
|
||||
@@ -49,18 +53,22 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds shape
|
||||
* + is anything but air
|
||||
* - is air checks
|
||||
* space bar is skip
|
||||
* ~ is also skip (but marks controller position, optional. implementation wise it is a space...)
|
||||
* rest needs to be defined
|
||||
* Adds shape. Shape is a two-dimensional string array. Each <b>character</b> inside any of these strings will
|
||||
* be later mapped to a particular type of {@link IStructureElement} as indicated by {@link #addElement(Character, IStructureElement)}.
|
||||
* <p>
|
||||
* next char is next block(a)
|
||||
* next string is next line(b)
|
||||
* next string[] is next slice(c)
|
||||
* <p>
|
||||
* char A000-FFFF range is reserved for generated skips
|
||||
* There are a few special reserved characters that are pre-mapped to some common structure elements
|
||||
* <ul>
|
||||
* <li>+ is anything but air</li>
|
||||
* <li>- is air checks</li>
|
||||
* <li>space bar is skip</li>
|
||||
* <li>~ is also skip (but marks controller position, not optional)</li>
|
||||
* <li>char {@code A000-FFFF} range is reserved for generated skips</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
*
|
||||
* @param name unlocalized/code name
|
||||
* @param structurePiece generated or written struct - DO NOT STORE IT ANYWHERE, or at least set them to null afterwards
|
||||
@@ -80,22 +88,37 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
builder.setLength(builder.length() - 1);
|
||||
}
|
||||
Set<Vec3Impl> 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 +128,19 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
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("+")) {
|
||||
@@ -123,18 +153,24 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the unboxed version
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStructureDefinition<T> build() {
|
||||
Map<String, IStructureElement<T>[]> 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 +230,11 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
if (elements == null) throw new NoSuchElementException(name);
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
|
||||
Set<Vec3Impl> occupiedSpace = occupiedSpaces.get(name);
|
||||
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
||||
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
|
||||
}
|
||||
}
|
||||
@@ -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<T> implements IStructureElement<T> {
|
||||
// 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<IChatComponent> 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);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+31
-12
@@ -1,34 +1,53 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
|
||||
private final T object;
|
||||
private final ItemStack trigger;
|
||||
private final IItemSource source;
|
||||
private final EntityPlayerMP actor;
|
||||
final T object;
|
||||
final ItemStack trigger;
|
||||
private final int elementBudget;
|
||||
private final ISurvivalBuildEnvironment params;
|
||||
private final boolean check;
|
||||
private int built = -1;
|
||||
|
||||
private final AutoPlaceEnvironment env;
|
||||
|
||||
public SurvivalBuildStructureWalker(
|
||||
T object, ItemStack trigger, IItemSource source, EntityPlayerMP actor, int elementBudget, boolean check) {
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
int elementBudget,
|
||||
ISurvivalBuildEnvironment params,
|
||||
IStructureDefinition<?> definition,
|
||||
String piece,
|
||||
ExtendedFacing facing,
|
||||
int[] baseOffsetABC,
|
||||
boolean check) {
|
||||
this.object = object;
|
||||
this.trigger = trigger;
|
||||
this.source = source;
|
||||
this.actor = actor;
|
||||
this.elementBudget = elementBudget;
|
||||
this.params = params;
|
||||
this.check = check;
|
||||
|
||||
env = new AutoPlaceEnvironment(
|
||||
params.getActor(),
|
||||
params.getActor()::addChatComponentMessage,
|
||||
definition,
|
||||
piece,
|
||||
facing,
|
||||
baseOffsetABC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
PlaceResult placeResult = element.survivalPlaceBlock(
|
||||
object, world, x, y, z, trigger, source, actor, actor::addChatComponentMessage);
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
env.offsetABC[0] = a;
|
||||
env.offsetABC[1] = b;
|
||||
env.offsetABC[2] = c;
|
||||
env.setSource(params.getSource());
|
||||
PlaceResult placeResult = element.survivalPlaceBlock(object, world, x, y, z, trigger, env);
|
||||
if (placeResult != PlaceResult.SKIP && built == -1) built = 0;
|
||||
switch (placeResult) {
|
||||
case SKIP:
|
||||
@@ -37,7 +56,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
|
||||
if (check) element.check(object, world, x, y, z);
|
||||
return ++built < elementBudget;
|
||||
case REJECT:
|
||||
StructureLibAPI.markHintParticleError(actor, world, x, y, z);
|
||||
StructureLibAPI.markHintParticleError(params.getActor(), world, x, y, z);
|
||||
return false;
|
||||
case ACCEPT_STOP:
|
||||
if (check) element.check(object, world, x, y, z);
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
package com.gtnewhorizon.structurelib.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import net.minecraft.dispenser.IPosition;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
public class Vec3Impl implements Comparable<Vec3Impl> {
|
||||
public static final Vec3Impl NULL_VECTOR = new Vec3Impl(0, 0, 0);
|
||||
private static final Map<Vec3Impl, Vec3Impl> pool = new HashMap<>();
|
||||
private final int val0;
|
||||
private final int val1;
|
||||
private final int val2;
|
||||
|
||||
/**
|
||||
* THIS WILL CAUSE THIS VECTOR TO PERMANENTLY RESIDE IN STATIC MEMORY. DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE
|
||||
* DOING!!!
|
||||
*/
|
||||
public static Vec3Impl getFromPool(int in0, int in1, int in2) {
|
||||
return pool.computeIfAbsent(new Vec3Impl(in0, in1, in2), Function.identity());
|
||||
}
|
||||
|
||||
public Vec3Impl(int in0, int in1, int in2) {
|
||||
this.val0 = in0;
|
||||
this.val1 = in1;
|
||||
|
||||
@@ -97,6 +97,18 @@ StructureLib API documentation.
|
||||
breaking client code.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.StructureEvent}</h2>
|
||||
<p>
|
||||
This class is the base class of all events that will be posted while a IStructureDefinition are visited.
|
||||
These events are disabled by default, and can be enabled by calling {@link com.gtnewhorizon.structurelib.StructureLibAPI#enableInstrument(Object)}.
|
||||
To receive these events, register an event handler to {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}.
|
||||
Handlers should filter the events received by {@link com.gtnewhorizon.structurelib.StructureEvent#getInstrumentIdentifier()}.
|
||||
</p>
|
||||
<p>
|
||||
<b>These events are intended for third party tools to inspect structure details.</b> Normal users should not use
|
||||
these at all.
|
||||
</p>
|
||||
|
||||
<h1>Important Concepts</h1>
|
||||
<h2>Context Object</h2>
|
||||
<p>
|
||||
@@ -231,5 +243,13 @@ public class MyHatchElement<T> implements IStructureElement<T> {
|
||||
|
||||
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
|
||||
|
||||
<h2>Instrumenting StructureLib</h2>
|
||||
<p>
|
||||
StructureLib support third party tools to poke into structure's internals via events. This is done by listening to {@link com.gtnewhorizon.structurelib.StructureEvent}
|
||||
and its subclasses on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS forge main event bus}.
|
||||
</p>
|
||||
<p>
|
||||
Please refer to {@link com.gtnewhorizon.structurelib.StructureEvent} to see what events are available.
|
||||
</p>
|
||||
</BODY>
|
||||
</HTML>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.enumerable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ExtendedFacingTest {
|
||||
|
||||
@Test
|
||||
void ensureGetWorldDirectionDoesNotCrashNorReturnNull() {
|
||||
for (ExtendedFacing ef : ExtendedFacing.VALUES) {
|
||||
for (ForgeDirection src : ForgeDirection.VALID_DIRECTIONS) {
|
||||
assertNotNull(ef.getWorldDirection(src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureGetWorldDirectionIsCorrect() {
|
||||
assertEquals(ForgeDirection.DOWN, ExtendedFacing.EAST_NORMAL_NONE.getWorldDirection(ForgeDirection.UP));
|
||||
assertEquals(ForgeDirection.DOWN, ExtendedFacing.NORTH_NORMAL_NONE.getWorldDirection(ForgeDirection.UP));
|
||||
assertEquals(ForgeDirection.NORTH, ExtendedFacing.NORTH_NORMAL_NONE.getWorldDirection(ForgeDirection.NORTH));
|
||||
assertEquals(ForgeDirection.NORTH, ExtendedFacing.NORTH_CLOCKWISE_NONE.getWorldDirection(ForgeDirection.NORTH));
|
||||
assertEquals(ForgeDirection.DOWN, ExtendedFacing.NORTH_CLOCKWISE_NONE.getWorldDirection(ForgeDirection.EAST));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user