diff --git a/.github/workflows/publish-javadoc.yaml b/.github/workflows/publish-javadoc.yaml new file mode 100644 index 00000000..0196d82f --- /dev/null +++ b/.github/workflows/publish-javadoc.yaml @@ -0,0 +1,64 @@ +name: Deploy Javadoc + +on: + push: + branches: + - master + - feature/doc + tags: + - '*' + +permissions: + contents: write + pages: write + id-token: write + +jobs: + javadoc: + concurrency: javadoc-publish + runs-on: ubuntu-latest + outputs: + result_hash: ${{ steps.deploy.outputs.hash }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-java@v2 + with: + java-version: 8 + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Generate Javadoc + run: ./gradlew --no-daemon spotlessCheck javadoc + + - name: Deploy 🚀 + id: deploy + shell: bash + run: | + mv build/docs $RUNNER_TEMP/docs_output + rm build -rf + git checkout gh-pages + mkdir -p $TARGET_FOLDER + rm -rf $TARGET_FOLDER + mv $RUNNER_TEMP/docs_output $TARGET_FOLDER -v + git add $TARGET_FOLDER + if git diff --cached --exit-code > /dev/null ; then + echo Nothing to deploy + exit 0 + fi + find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' > index.txt + git add index.txt + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git commit -m "Deploy javadoc from ref $TARGET_FOLDER" + git push + echo "::set-output name=hash::$(git rev-parse HEAD)" + env: + TARGET_FOLDER: ${{ github.ref_name }} + COMMIT_AUTHOR: ${{ github.pusher.name }} + COMMIT_EMAIL: ${{ github.pusher.email }} diff --git a/addon.gradle b/addon.gradle new file mode 100644 index 00000000..a985e11a --- /dev/null +++ b/addon.gradle @@ -0,0 +1,24 @@ +javadoc { + inputs.file "src/main/javadoc/overview.html" + + include "com/gtnewhorizon/structurelib/alignment/**" + include "com/gtnewhorizon/structurelib/constructable/**" + include "com/gtnewhorizon/structurelib/structure/**" + include "com/gtnewhorizon/structurelib/util/**" + include "com/gtnewhorizon/structurelib/StructureLibAPI.java" + + options { + overview "src/main/javadoc/overview.html" + links 'https://docs.oracle.com/javase/8/docs/api/' + links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/' + } +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives javadocJar +} diff --git a/docs/channels.md b/docs/channels.md deleted file mode 100644 index f83a6b8f..00000000 --- a/docs/channels.md +++ /dev/null @@ -1,72 +0,0 @@ -# Constructable Tool - -The convention is to use the stack size of tool stack to pass tier info. - -## Channels - -When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious restriction, as -one ItemStack cannot have two stack sizes at the same time. -Here we use the item NBT to bypass this restriction. - -Basically, one hologram can contain multiple channels. -Each channel can be identified with lower case strings and has the value being one positive Java int. -There will be one and only one master channel and 0 or more sub channel. - -When a non-existent sub channel is queried, master channel will be used instead to fill its place. - -Structure element designers will usually not need to consider channels, i.e. proceed as if this concept does not exist, unless it must take input from multiple channels. - -Multi Designers will consider how to map each channel to each tiered component. -He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element on which channel to use. -Conceptually, it will return an ItemStack with the master channel being the value of selected channel. -In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size. - -In any case, it is NOT safe to mutate or query this ItemStack beyond its stack size. -What information this carries (beyond the stack size) is an implementation detail and should not be relied on. -__For the future of mankind please do not rely on this.__ - -### why is this called channel? - -IDK man, just give me a better name and I'd do the renaming. - -### Example: Multiblock Structure Designer - -GT++ Chemical Plant is a multi with 4 distinctly tiered component. - -It will use -* master channel for its outer shell -* channel `"casing"` for machine casings -* channel `"pipe"` for pipe casings -* channel `"coil"` for heating coils - -Its structure code will then look like -```java -.addElement('a', shellStructureElement()) -.addElement('b', withChannel("casing", machineCasingStructureElement())) -.addElement('c', withChannel("pipe", pipeCasingStructureElement())) -.addElement('d', withChannel("coil", coilStructureElement())) -``` - -whereas all these structure elements are designed without considering channels whatsoever - -### Example: Multi channel input Structure Element - -Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both meta id and facing input. - - -It will use -* master channel for its outer shell -* channel `"casing"` for machine casings -* channel `"pipe"` for pipe casings -* channel `"coil"` for heating coils - -Its structure code will then look like -```java -.addElement('a', shellStructureElement()) -.addElement('b', withChannel("casing", machineCasingStructureElement())) -.addElement('c', withChannel("pipe", pipeCasingStructureElement())) -.addElement('d', withChannel("coil", coilStructureElement())) -``` - -whereas all these structure elements are designed without considering channels whatsoever - diff --git a/docs/walkthrough.md b/docs/walkthrough.md deleted file mode 100644 index b3f60a6f..00000000 --- a/docs/walkthrough.md +++ /dev/null @@ -1,62 +0,0 @@ -# StructureLib Developer Guide v0.1-SNAPSHOT - -## `IStructureDefinition` - -`IStructureDefinition` is the structure definition of your multi. You will have one `IStructureDefinition` for each multi. - -### Caching? - -You will construct this definition once per multi, either at class load or at first call to `getStructureDefinition()`. -You can use a `ClassValue>` if you have a template for subclass. -You can also use `StructureUtility.defer()` for simpler cases, but it is slightly less performant as it will allocate new -objects on each structure check, whereas `ClassValue` will allocate once and cache it onwards. - -### Constructing - -IStructureDefinition is constructed using `IStructureDefinition#builder()`. - -Builder has two main methods -1. `addShape` is a 2d string array describing the layout of structure. You can optionally use `StructureUtility#transpose()` -to transpose the array to make the structure look nicer in source code. -2. `addElement`. It takes a char identifier (mapped to a char in any of `addShape()` calls) and a IStructureElement. - -## `IStructureElement` - -`IStructureElement` are how you define which block will be accepted. -They are simple stateless classes. They will rely on the multiblock controller to store any states they need. -If you don't want to store these transient states in multiblock controller, you can use `StructureUtility#withContext()` - -They will usually come from `StructureUtility`. -There is a vast library of `IStructureElement` there to cover basic needs. -For more complex usage, you can define your own subclass, e.g. GregTech 5 Unofficial defines `ofHatchAdder()`, `ofCoil()`, -`ofFrame()` and `buildHatchAdder()` in its `GT_StructureUtility` class. - -Here we will detail a few more complex `IStructureElement` that comes with standard library. - -### `ofBlocksTiered` - -This assumes you have a series of block, e.g. T1 motor, T2 motor, etc. Your multi will accept any of them (__as long as -player does not mix them (*)__), but would like to know which is used. - -(*): Implementation wise, this structure element will only accept a block if its tier is the same as existing tier or -if existing tier is unset (i.e. value of third argument) - -The first argument is tier extractor. -You will need to provide a function to convert block+meta to a tier. -This tier cam be an Enum, an Integer, a String, or basically anything. -It doesn't even have to implement `Comparable`. -The choice is yours. -The extractor can return null and will never be passed a null block or an invalid block meta. - -The second argument is a list of all blocks you know that you will accept. -This list is only for hint particle spawning and survival build. -The hint/autoplace code will choose 1st pair to spawn/place if hologram has [channel data](./channels.md) of 1, -2nd pair if 2, and so on. - -The third argument is the value when the tier has not been set by structure code. -This means getter (5th argument) will usually return this upon first invocation for each round of structure check. - -The fourth argument will notify the multi of a tier change. - -The fifth argument is how multi notify the structure element about current tier. -This is necessary as all structure element are stateless themselves. diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index 19ec3099..3e506eaa 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -2,6 +2,7 @@ package com.gtnewhorizon.structurelib; import static com.gtnewhorizon.structurelib.StructureLib.proxy; +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; @@ -55,25 +56,66 @@ public class StructureLibAPI { proxy.endHinting(w); } + /** + * 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 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. + */ public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { proxy.hintParticleTinted(w, x, y, z, icons, RGBa); } + /** + * 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 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. + */ 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); } + /** + * 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 icons 6 texture. in forge direction order. + */ public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) { proxy.hintParticle(w, x, y, z, icons); } + /** + * 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 block 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); } /** * Update the tint of given hint particle. Do nothing if particle not found. - * @return false if nothing updated. true if updated. + * + * 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. + * + * @return false if nothing updated. true if updated or update instruction sent. */ public static boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) { return proxy.markHintParticleError(player, w, x, y, z); @@ -81,6 +123,7 @@ public class StructureLibAPI { /** * Update the tint of given hint particle. Do nothing if particle not found. + * * @return false if nothing updated. true if updated. */ public static boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] RGBa) { @@ -88,9 +131,11 @@ public class StructureLibAPI { } /** - * Query the ExtendedFacing of this tile entity. + * Query the ExtendedFacing of this tile entity from client side. Can be sent only on client side. * The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} * upon arrival of server reply. + *

+ * The server side will query the {@link ExtendedFacing} of this provider using {@link IAlignment#getExtendedFacing()} * * @throws IllegalArgumentException if is not tile entity or provided a null alignment */ @@ -100,6 +145,8 @@ public class StructureLibAPI { /** * Send the ExtendedFacing of this Tile Entity to all players. Can be called on server side only. + *

+ * The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method. * * @throws IllegalArgumentException if is not tile entity or provided a null alignment */ @@ -109,6 +156,8 @@ public class StructureLibAPI { /** * Send the ExtendedFacing of this Tile Entity to given player. Can be called on server side only. + *

+ * The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method. * * @throws IllegalArgumentException if is not tile entity or provided a null alignment */ @@ -118,6 +167,8 @@ public class StructureLibAPI { /** * Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side only. + *

+ * The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method. * * @throws IllegalArgumentException if is not tile entity or provided a null alignment */ @@ -127,6 +178,8 @@ public class StructureLibAPI { /** * Send the ExtendedFacing of this Tile Entity to all players in that dimension. Can be called on server side only. + *

+ * The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method. * * @throws IllegalArgumentException if is not tile entity or provided a null alignment */ @@ -134,14 +187,25 @@ public class StructureLibAPI { StructureLib.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId); } + /** + * Get the Block for StructureLib supplied hint block. + */ public static Block getBlockHint() { return StructureLib.blockHint; } + /** + * Get the ItemBlock for StructureLib supplied hint block. + */ public static Item getItemBlockHint() { return StructureLib.itemBlockHint; } + /** + * Check if structure debug mode is on. + * + * @return true if debug mode is on + */ public static boolean isDebugEnabled() { return StructureLib.DEBUG_MODE; } @@ -153,11 +217,12 @@ 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)} */ public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) { // TODO extend this function a bit - return w.getBlock(x, y, z).isReplaceable(w, x, y, z); + 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/ChannelDataAccessor.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java index 3dd2a473..9d1ea2d1 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java @@ -3,7 +3,7 @@ package com.gtnewhorizon.structurelib.alignment.constructable; import static com.gtnewhorizon.structurelib.util.MiscUtils.getTagKeys; import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND; -import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; import java.util.Locale; import java.util.Map.Entry; import java.util.stream.Stream; @@ -11,16 +11,38 @@ import net.minecraft.init.Items; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.Constants.NBT; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +/** + * Provide accessor methods for channel data from trigger item. + * + * Channels section on overview + */ public class ChannelDataAccessor { private static final String SECONDARY_HINT_TAG = "channels"; private ChannelDataAccessor() {} + /** + * Return a trigger item that + *

+ * + * If given subchannel does not exist, masterStack may or may not be returned to save performance. + * Code within StructureLib can depend on this behavior, you cannot. + * + * @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call + * @param channel subchannel to use. will use current master channel data instead if this channel does not exist + * Note: all channel identifiers are supposed to be lower case and not empty. + * @return a trigger item with new master channel + */ public static ItemStack withChannel(ItemStack masterStack, String channel) { - if (channel == null || channel.isEmpty()) throw new IllegalArgumentException(); - if (StructureLib.DEBUG_MODE && !channel.toLowerCase(Locale.ROOT).equals(channel)) + if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (StructureLibAPI.isDebugEnabled() + && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); if (!masterStack.hasTagCompound() || !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND) @@ -36,12 +58,27 @@ public class ChannelDataAccessor { return ret; } + /** + * Check if given trigger item contains any subchannel + * @param masterStack trigger stack to check + * @return true if contains any subchannel + */ public static boolean hasSubChannel(ItemStack masterStack) { + if (masterStack == null) throw new IllegalArgumentException(); return masterStack.hasTagCompound() && masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND); } + /** + * Check if given trigger item contains specified subchannel + * @param masterStack trigger stack to check + * @param channel channel identifier. + * Note: all channel identifiers are supposed to be lower case and not empty. + * @return true if contains specified subchannel + */ public static boolean hasSubChannel(ItemStack masterStack, String channel) { - if (StructureLib.DEBUG_MODE && !channel.toLowerCase(Locale.ROOT).equals(channel)) + if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (StructureLibAPI.isDebugEnabled() + && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); return !channel.isEmpty() && masterStack.hasTagCompound() @@ -52,9 +89,17 @@ public class ChannelDataAccessor { .hasKey(channel, NBT.TAG_INT); } + /** + * Get the subchannel data from given trigger item. Will use master channel instead if not present. + * @param masterStack trigger stack to query from + * @param channel channel identifier. + * Note: all channel identifiers are supposed to be lower case and not empty. + * @return channel data + */ public static int getChannelData(ItemStack masterStack, String channel) { - if (channel == null || channel.isEmpty()) throw new IllegalArgumentException(); - if (StructureLib.DEBUG_MODE && !channel.toLowerCase(Locale.ROOT).equals(channel)) + if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (StructureLibAPI.isDebugEnabled() + && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); if (!masterStack.hasTagCompound() || !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND) @@ -65,9 +110,17 @@ public class ChannelDataAccessor { return masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel); } + /** + * Set the subchannel data on given trigger item + * @param masterStack trigger stack to check + * @param channel channel identifier. + * Note: all channel identifiers are supposed to be lower case and not empty. + * @param data subchannel data. should always be a positive value + */ public static void setChannelData(ItemStack masterStack, String channel, int data) { - if (channel == null || channel.isEmpty()) throw new IllegalArgumentException(); - if (StructureLib.DEBUG_MODE && !channel.toLowerCase(Locale.ROOT).equals(channel)) + if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (StructureLibAPI.isDebugEnabled() + && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); if (data <= 0) throw new IllegalArgumentException(); if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound(); @@ -76,9 +129,16 @@ public class ChannelDataAccessor { main.getCompoundTag(SECONDARY_HINT_TAG).setInteger(channel, data); } + /** + * Clear the given subchannel from given trigger item, if it exists + * @param masterStack trigger stack to unset + * @param channel channel identifier. + * Note: all channel identifiers are supposed to be lower case and not empty. + */ public static void unsetChannelData(ItemStack masterStack, String channel) { - if (channel == null || channel.isEmpty()) throw new IllegalArgumentException(); - if (StructureLib.DEBUG_MODE && !channel.toLowerCase(Locale.ROOT).equals(channel)) + if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (StructureLibAPI.isDebugEnabled() + && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound(); NBTTagCompound main = masterStack.stackTagCompound; @@ -89,16 +149,33 @@ public class ChannelDataAccessor { if (main.hasNoTags()) masterStack.stackTagCompound = null; } + /** + * Wipe all subchannel data on given trigger item + * @param masterStack trigger stack to wipe + */ public static void wipeChannelData(ItemStack masterStack) { + if (masterStack == null) throw new IllegalArgumentException(); if (masterStack.stackTagCompound != null) masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG); } + /** + * Iterate over all subchannel data on trigger stack. Does not include master channel!! + * @param masterStack trigger stack to check + * @return A java8 stream of pairs. Key is channel identifier and value is channel data. Pairs do not support + * mutation nor removal. Can still cause {@link java.util.ConcurrentModificationException} if underlying channel + * data is modified while stream is working. + */ public static Stream> iterateChannelData(ItemStack masterStack) { if (!hasSubChannel(masterStack)) return Stream.empty(); NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG); return getTagKeys(tag).stream().map(s -> new ImmutablePair<>(s, tag.getInteger(s))); } + /** + * Get the number of subchannels present on given trigger item. Does not include master channel. + * @param masterStack trigger stack to query from + * @return subchannel count + */ public static int countChannelData(ItemStack masterStack) { if (!hasSubChannel(masterStack)) return 0; NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG); diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/package-info.java new file mode 100644 index 00000000..e7c94677 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains interfaces and utility methods for autobuilding a structure. + */ +package com.gtnewhorizon.structurelib.alignment.constructable; 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 3b54c274..59156d68 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java @@ -335,7 +335,7 @@ public enum ExtendedFacing { /** * Translates relative to front facing offset to world offset * - * @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B) + * @param abcOffset A,B,C offset (facing relative {@code L-->R,U-->D,F-->B}) * @return X, Y, Z offset in world */ public Vec3 getWorldOffset(Vec3 abcOffset) { @@ -358,7 +358,7 @@ public enum ExtendedFacing { * Translates world offset to relative front facing offset * * @param xyzOffset X,Y,Z offset in world - * @return A, B, C offset (facing relative L-->R,U-->D,F-->B) + * @return A, B, C offset (facing relative {@code L-->R,U-->D,F-->B}) */ public Vec3 getOffsetABC(Vec3 xyzOffset) { return integerAxisSwap.translate(xyzOffset); diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java index 9e031726..af026250 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java @@ -1,4 +1,6 @@ /** + * This package provide a framework for rotating a given block, or a multiblock structure. + *

* All public stuff in this package and its subpackages are considered part of the public API. * Backwards compatibility is maintained to the maximum extend possible. */ diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java index 16e7248d..d85d7a06 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java +++ b/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java @@ -45,6 +45,10 @@ public abstract class AlignmentMessage implements IMessage { ByteBufUtils.writeTag(pBuffer, tFXTag); } + /** + * {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)} + * @param provider + */ private AlignmentMessage(IAlignmentProvider provider) { if (!(provider instanceof TileEntity)) throw new IllegalArgumentException("Provider must be a TileEntity"); IAlignment alignment = provider.getAlignment(); diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/ErrorHintParticleMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/ErrorHintParticleMessage.java index cc830207..79f4cea6 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/net/ErrorHintParticleMessage.java +++ b/src/main/java/com/gtnewhorizon/structurelib/net/ErrorHintParticleMessage.java @@ -41,7 +41,7 @@ public class ErrorHintParticleMessage implements IMessage { public IMessage onMessage(ErrorHintParticleMessage msg, MessageContext ctx) { boolean updateResult = StructureLibAPI.markHintParticleError( StructureLib.getCurrentPlayer(), StructureLib.getCurrentPlayer().worldObj, msg.x, msg.y, msg.z); - if (StructureLib.DEBUG_MODE) + if (StructureLibAPI.isDebugEnabled()) LOGGER.debug( "Server instructed to mark hint particle at ({}, {}, {}) error, result {}!", msg.x, diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java index 88fab993..8aa7acd3 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java +++ b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java @@ -64,7 +64,7 @@ public class UpdateHintParticleMessage implements IMessage { new short[] { msg.r, msg.g, msg.b, msg.a, }); - if (StructureLib.DEBUG_MODE) + if (StructureLibAPI.isDebugEnabled()) LOGGER.debug( "Server instructed to update hint particle at ({}, {}, {}), result {}!", msg.x, diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index a9617366..6c4075c8 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -1,15 +1,48 @@ package com.gtnewhorizon.structurelib.structure; -import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded; import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded; import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import java.util.function.Function; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +/** + * This is the structure definition of your multi. You will have one of these for each multi. + * + *

Cache the instances of this class

+ *

+ * You will construct this definition once per multi, either at class load or at first call to your variant of {@code getStructureDefinition()}. + * You can use a {@link ClassValue ClassValue<IStructureDefinition<?>>} if you have a template for subclass. + * You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for simpler cases, but it is + * slightly less performant as it will allocate new objects on each structure check, whereas {@link ClassValue} will + * allocate once and cache it onwards. + *

+ *

Constructing

+ *

+ * {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()} + *

+ * Builder has two main methods: + *

+ *

Position A, B and C

+ * + * Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped. + * + * @param Type of the context object. Usually this will be the multiblock controller. + */ public interface IStructureDefinition { /** * Used internally @@ -22,6 +55,21 @@ public interface IStructureDefinition { */ IStructureElement[] getStructureFor(String name); + /** + * Run a structure check. + * @param object context object. usually multiblock controller. + * @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 forceCheckAllBlocks check all location even if chunk not currently loaded. + * @return true if successful, false otherwise + */ default boolean check( T object, String piece, @@ -50,6 +98,20 @@ public interface IStructureDefinition { forceCheckAllBlocks); } + /** + * Spawn hint particles. Should not be called on server side. + * @param object context object. usually multiblock controller. + * @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 + * @return true if successful, false otherwise + */ default boolean hints( T object, ItemStack trigger, @@ -78,6 +140,23 @@ public interface IStructureDefinition { null); } + /** + * Build the multi in creative mode on a best effort basis. Does not guarantee the structure is correct after a + * successful call. + * + * @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 + * @return true if sucessful, false otherwise + */ default boolean build( T object, ItemStack trigger, @@ -106,6 +185,26 @@ public interface IStructureDefinition { null); } + /** + * Call {@link #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} if {@code hintsOnly}, + * call {@link #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} otherwise. + * + * @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 hintsOnly whether to spawn hints or do creative build. + * @return true if sucessful, false otherwise + * @see #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) + * @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) + */ default boolean buildOrHints( T object, ItemStack trigger, @@ -136,6 +235,19 @@ public interface IStructureDefinition { } /** + * 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 source from where to drain resource * @param actor source of action @@ -179,6 +291,25 @@ public interface IStructureDefinition { return walker.getBuilt(); } + /** + * Low level utility. + * @param object context object. usually multiblock controller. + * @param trigger The trigger item that contains channel data. + * @param elements the structure piece + * @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 hintsOnly whether to spawn hints or do creative build. no effect if {@code checkBlocksIfNotNullForceCheckAllIfTrue} is not null. + * @param checkBlocksIfNotNullForceCheckAllIfTrue creative build or spawn hints if null. Force check not loaded chunk + * if true. Check only loaded chunk is false. + * @return true if iteration completed successfully. + * @param type of context object. + */ static boolean iterate( T object, ItemStack trigger, @@ -226,7 +357,7 @@ public interface IStructureDefinition { skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)), "check force"); } - if (DEBUG_MODE && success) { + if (StructureLibAPI.isDebugEnabled() && success) { StructureLib.LOGGER.info( "Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass"); } @@ -269,6 +400,11 @@ public interface IStructureDefinition { return true; } + /** + * Create a new instance of builder. + * @return + * @param type of context object + */ static StructureDefinition.Builder builder() { return StructureDefinition.builder(); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index 9fdc4a76..a50fffb7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -1,11 +1,11 @@ package com.gtnewhorizon.structurelib.structure; -import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; 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.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.util.IChatComponent; @@ -23,10 +23,16 @@ 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)} + * or its overloads to implement this. * + * @param trigger trigger item * @param s drain resources from this place - * @param actor source of action. - * @param chatter + * @param actor source of action. for very critical errors you can also just send the error messages here, bypassing + * 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. */ default PlaceResult survivalPlaceBlock( T t, @@ -39,7 +45,7 @@ public interface IStructureElement { EntityPlayerMP actor, Consumer chatter) { if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit"); - if (DEBUG_MODE) + if (StructureLibAPI.isDebugEnabled()) LOGGER.error( "Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}", getClass().getName()); @@ -115,7 +121,7 @@ public interface IStructureElement { ACCEPT, /** * Combination of ACCEPT and STOP. - * + *

* Element placed successfully. To proceed further would require stopping the placement and wait for next round. */ ACCEPT_STOP; diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index 38edb59c..e7ffe852 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -1,8 +1,8 @@ package com.gtnewhorizon.structurelib.structure; -import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; 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; @@ -130,7 +130,7 @@ public class StructureDefinition implements IStructureDefinition { public IStructureDefinition build() { Map[]> structures = compileStructureMap(); - if (DEBUG_MODE) { + if (StructureLibAPI.isDebugEnabled()) { return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures); } else { return structures::get; diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 967f3f0a..bceacd0f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -1,6 +1,5 @@ package com.gtnewhorizon.structurelib.structure; -import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; import static java.lang.Integer.MIN_VALUE; import com.gtnewhorizon.structurelib.StructureLib; @@ -34,9 +33,92 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; /** - * Fluent API for structure checking! - *

- * (Just import static this class to have a nice fluent syntax while defining structure definitions) + * A brief index of everything contained + *

Control blocks

+ * A lot of these might seem to make more sense as a default function on {@link IStructureElement}. However, in that + * case javac generic will often fail to infer the type for you, so we have to define them as static methods + *

If block

+ * Provide if block, allowing downstream call only if user specified conditions are meet (i.e. predicate returns true) + *
    + *
  • {@link #onlyIf(Predicate, IStructureElement)} and its overloads
  • + *
+ *

Switch block

+ * Provide switch block, allowing to select a downstream element by using a key or index computed from trigger item + * and/or context object. + *
    + *
  • {@link #partitionBy(Function, IStructureElement[])} and its overloads
  • + *
+ *

Or Chain

+ * Provide a short-circuiting OR chain. + *
    + *
  • {@link #ofChain(IStructureElement[])} and its overloads
  • + *
+ *

Side effect

+ * Provide callbacks on various occasion + *
    + *
  • {@link #onElementPass(Consumer, IStructureElement)}: Call an callback upon element check success
  • + *
  • {@link #onElementFail(Consumer, IStructureElement)}: Call an callback upon element check failure
  • + *
+ * + *

Other Primitives

+ *

Error statement

+ * Unconditionally return false. + *
    + *
  • {@link #error()}
  • + *
+ *

Context Object Change

+ * Switch the context object. + *
    + *
  • {@link #withContext(IStructureElement)}: switch context to the extended context
  • + *
+ *

Channel Change

+ * Change the channel. See Channels section on overview for more information. + *
    + *
  • {@link #withChannel(String, IStructureElement)}: switch channel using {@link ChannelDataAccessor#withChannel(ItemStack, String)} when a trigger item is present.
  • + *
+ *

Spawn hint particle

+ * Just spawn a hint. Do nothing otherwise. Useful when you want to override the hint provided by another structure element, + * or as a fallback to a structure element that is check only. + *
    + *
  • {@link #ofHint(int)}, {@link #ofHintDeferred(Supplier)} and its overloads
  • + *
+ * + *

Actual Element

+ * These elements are elements that can actually do structure check, spawn hint and do auto place. + * Elements listed in above 2 sections are not going to be helpful without elements like these. + * They also serve as a reference implementation for your own custom {@link IStructureElement} implementations. + *

Simple Block Element

+ * These accept one or more different block types in one location. + *
    + *
  • {@link #ofBlock(Block, int, Block, int)}, {@link #ofBlocksFlat(Map, Block, int)}, {@link #ofBlocksMap(Map, Block, int)} + * and their overloads: the most basic form
  • + *
  • {@link #ofBlockHint(Block, int, Block, int)}, {@link #ofBlocksFlatHint(Map, Block, int)}, {@link #ofBlocksMapHint(Map, Block, int)}: + * these are the same as above, but will not do autoplace
  • + *
  • {@link #isAir()}, {@link #notAir()}: They are supplied by default under the identifier {@code '-'} and {@code '+'} + * respectively, but are provided here regardless in case you want to use them as a fallback.
  • + *
+ *

Complex Block Element

+ * In case your logic on determining which block is accepted is complex, use these. + *
    + *
  • {@link #ofBlockAdder(IBlockAdder, Block, int)}, {@link #ofBlockAdderHint(IBlockAdder, Block, int)} and their + * overloads: hands off actual adder logic to an {@link IBlockAdder} you supplied. Save you the boilerplate of querying + * block type and block meta from {@link World}
  • + *
  • {@link #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function)} and its overloads: accept a series of + * blocks that each may have different tier, but only allow one single tier for anywhere this element is used (e.g. coils).
  • + *
  • {@link #ofTileAdder(ITileAdder, Block, int)}, {@link #ofSpecificTileAdder(BiPredicate, Class, Block, int)}: + * Similar to block adder, but for tile entities.
  • + *
+ * + *

Helper Methods

+ * These don't construct a {@link IStructureElement}, but is helpful to the instantiation or implementation of these. + *
    + *
  • {@link #survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayerMP, Consumer)} and its overloads: + * Helper method to cut common boilerplate for implementing {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
  • + *
  • {@link #getPseudoJavaCode(World, ExtendedFacing, int, int, int, int, int, int, Function, int, int, int, boolean)}: + * Generate a structure code template from existing structure.
  • + *
  • {@link #iterate(World, ExtendedFacing, int, int, int, int, int, int, boolean, int, int, int, IBlockPosConsumer, Runnable, Runnable)} + * and its overloads: help iterate over a coordinate space.
  • + *
*/ public class StructureUtility { private static final String NICE_CHARS = @@ -158,7 +240,15 @@ public class StructureUtility { private StructureUtility() {} /** + * This is a helper method for implementing {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} + *

+ * This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at given + * location. * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * This method might yield error messages to the player only if there is likely a programming error or exploit. + * + * @param s drain resources from this place + * @param actor source of action. cannot be null. */ public static PlaceResult survivalPlaceBlock( Block block, int meta, World world, int x, int y, int z, IItemSource s, EntityPlayerMP actor) { @@ -166,7 +256,17 @@ public class StructureUtility { } /** + * This is a helper method for implementing {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} + *

+ * This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at given + * location. * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * This method might yield error messages to the player only if there is likely a programming error or exploit. + * This method might yield error messages to the chatter. + * + * @param s drain resources from this place + * @param actor source of action. cannot be null. + * @param chatter normal error destination. can be null to suppress them. */ public static PlaceResult survivalPlaceBlock( Block block, @@ -201,9 +301,16 @@ public class StructureUtility { } /** + * This is a helper method for implementing {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} + *

+ * This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at given + * location. * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * This method might yield error messages to the player only if there is likely a programming error or exploit. * - * @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock! + * @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock! + * @param s drain resources from this place + * @param actor source of action. cannot be null. */ public static PlaceResult survivalPlaceBlock( ItemStack stack, @@ -220,10 +327,18 @@ public class StructureUtility { } /** + * This is a helper method for implementing {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} + *

+ * This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at given + * location. * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * This method might yield error messages to the player only if there is likely a programming error or exploit. + * This method might yield error messages to the chatter. * * @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock! - * @param chatter pass in null to suppress error output, or just use the other overload + * @param s drain resources from this place + * @param actor source of action. cannot be null. + * @param chatter normal error destination. can be null to suppress them. */ public static PlaceResult survivalPlaceBlock( ItemStack stack, @@ -255,11 +370,21 @@ public class StructureUtility { return PlaceResult.ACCEPT; } + /** + * Return a structure element that only allow air (or air equivalents, like Railcraft hidden blocks). + * You usually don't need to call this yourselves. Use {@code -} in shape to automatically use this. + * Provided nontheless in case you want this as a fallback to something else. + */ @SuppressWarnings("unchecked") public static IStructureElement isAir() { return AIR; } + /** + * Return a structure element that allow anything but air (or air equivalents, like Railcraft hidden blocks). + * You usually don't need to call this yourselves. Use {@code +} in shape to automatically use this. + * Provided nontheless in case you want this as a fallback to something else. + */ @SuppressWarnings("unchecked") public static IStructureElement notAir() { return NOT_AIR; @@ -278,7 +403,9 @@ public class StructureUtility { // region hint only /** + * Spawn a hint with given amount of dots. * Check always returns: true. + * Only useful as a fallback, e.g. {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)} */ public static IStructureElementNoPlacement ofHint(int dots) { int meta = dots - 1; @@ -297,7 +424,9 @@ public class StructureUtility { } /** + * Spawn a hint with given textures. * Check always returns: true. + * Only useful as a fallback, e.g. {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)} */ public static IStructureElementNoPlacement ofHintDeferred(Supplier icons) { return new IStructureElementNoPlacement() { @@ -315,7 +444,9 @@ public class StructureUtility { } /** + * Spawn a hint with given amount of textures and tint. * Check always returns: true. + * Only useful as a fallback, e.g. {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)} */ public static IStructureElementNoPlacement ofHintDeferred(Supplier icons, short[] RGBa) { return new IStructureElementNoPlacement() { @@ -339,6 +470,11 @@ public class StructureUtility { /** * A more primitive variant of {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)} * Main difference is this one is check only. + *

+ * Note that if tierExtractor returns a null, this block will be rejected immediately. This makes null a potent + * candidate for notSet. + *

+ * See documentation on the other overload for more information. * * @see #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function) */ @@ -355,8 +491,21 @@ public class StructureUtility { Block block = world.getBlock(x, y, z); int meta = world.getBlockMetadata(x, y, z); TIER tier = tierExtractor.convert(block, meta); + if (tier == null) return false; TIER current = getter.apply(t); if (Objects.equals(notSet, current)) { + if (Objects.equals(notSet, tier)) { + if (StructureLib.PANIC_MODE) { + throw new AssertionError("tierExtractor should never return notSet: " + notSet); + } else { + StructureLib.LOGGER.error("#########################################"); + StructureLib.LOGGER.error("#########################################"); + StructureLib.LOGGER.error( + "tierExtractor should never return notSet: {}", notSet, new Throwable()); + StructureLib.LOGGER.error("#########################################"); + StructureLib.LOGGER.error("#########################################"); + } + } setter.accept(t, tier); return true; } @@ -365,18 +514,75 @@ public class StructureUtility { } /** - * Element representing a component with different tiers. Player can use more blueprint to get hints denoting more - * advanced components. + * Element representing a component with different tiers. Your multi will accept any of them (as long as + * player does not mix them (*)), but would like to know which is used. Player can use more blueprint to get + * hints denoting more advanced components. *

- * There is yet no TileEntity counter part of this utility. Feel free to submit a PR to add it. + * (*): Implementation wise, this structure element will only accept a block if its tier is the same as existing tier or + * if existing tier is unset (i.e. value of third argument) + *

+ * Above all else, you need to decide what tier you are going to use. For simpler cases, Integer is a good choice. + * You can also use an existing Enum. Tier can never be null though. + * It can also be a String or a {@link net.minecraft.util.ResourceLocation}, or basically anything. + * It doesn't even have to implement {@link Comparable}. + *

+ * This assumes you will reset the backing storage, and on the first occurrence getter would return notSet. + * You can also make getter to return other value to forcefully select a certain tier, but you're probably better + * off using block adders or {@link #ofBlock(Block, int, Block, int)} and its overloads + *

+ * Implementation wise, this allows a block only when we don't yet have a tier, or the block at that location has + * the same tier as we already have. + *

+ * There is yet no TileEntity counterpart of this utility. Feel free to submit a PR to add it. + *

+ * For most typical use cases, you will NOT want to return notSet from your tierExtractor. If you do so, we will + * have this kind of chain of events: + *

    + *
  • at check start, tier is reset to notSet + *
  • at first occurrence, ofBlocksTiered finds your current tier is notSet, and the block's tier is notSet, so it's accepted + *
  • at 2nd, 3rd..., same thing in 2 happens + *
  • at last occurrence, player placed a valid block with a tier not notSet. ofBlocksTiered now sets your current + * tier to this tier, and accept the block + *
  • check got notified structure is sound, and tier is not notSet. it marks the structure as complete and start doing its business + *
  • Player enjoy a (probably) hilariously shaped multi and (probably) reduced build cost. + *
+ *

+ * Example: + *

+ * Assume you have 16 tier, each map to one block's 16 different meta. You will usually want something like this + *

+     * public class Tile extends TileEntity {
+     *     private static final Block B = getTargetBlock();
+     *     private static final IStructureDefinition<Tile> S = IStructureDefinition.<Tile>builder()
+     *            // ... addShape, and other elements
+     *            .addElement('c', ofBlocksTiered((b, m) -> b == B ? m : null,
+     *                                            IntStream.range(0, 16).mapToObj(i -> Pair.of(B, i)).collect(Collectors.toList()),
+     *                                            -1,
+     *                                            (t, m) -> t.tier = m,
+     *                                            t -> t.tier))
+     *            .build();
+     *     private int tier;
+     *
+     *     public boolean check() {
+     *         tier = -1;
+     *         if (!S.check(...))
+     *             return false;
+     *         return isTierAcceptable(tier);
+     *     }
+     * }
+     * 
* - * @param allKnownTiers All known tiers as of calling. Can be empty or null. No hint will be spawned if empty or null. Cannot have null elements. - * First element denotes the most primitive tier. Last element denotes the most advanced tier. - * If not all tiers are available at definition construction time, use {@link #lazy(Supplier)} or its overloads to delay a little bit. - * @param notSet The value returned from {@code getter} when there were no tier info found in T yet. Can be null. - * @param getter a function to retrieve the current tier from T - * @param setter a function to set the current tier into T * @param tierExtractor a function to extract tier info from a block. + * This function can return null and will never be passed a null block or an invalid block meta. + * @param allKnownTiers A list of all known tiers as of calling. Can be empty or null. No hint will be spawned if empty or null. Cannot have null elements. + * First element denotes the most primitive tier. Last element denotes the most advanced tier. + * If not all tiers are available at definition construction time, use {@link #lazy(Supplier)} or its overloads to delay a bit. + * This list is only for hint particle spawning and survival build. + * The hint/autoplace code will choose 1st pair to spawn/place if trigger item has master channel data of 1, + * 2nd pair if 2, and so on. + * @param notSet The value returned from {@code getter} when there were no tier info found in T yet. Can be null. + * @param getter a function to retrieve the current tier from context object + * @param setter a function to set the current tier into context object */ public static IStructureElement ofBlocksTiered( ITierConverter tierExtractor, @@ -445,6 +651,8 @@ public class StructureUtility { /** * Denote a block using unlocalized names. This can be useful to get around mod loading order issues. *

+ * DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME INSTEAD. + *

* While no immediate error will be thrown, client code should ensure said mod is loaded and * said mod is present, otherwise bad things will happen later! */ @@ -455,14 +663,16 @@ public class StructureUtility { /** * Denote a block using unlocalized names. This can be useful to get around mod loading order issues. *

+ * DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME INSTEAD. + *

* While no immediate error will be thrown, client code should ensure said mod is loaded and - * said mod is present, otherwise bad things will happen later! + * said mod is present, otherwise the element will effectively become an {@link #error()}. *

* Will place block or hint using the given meta if wildcard is true. */ public static IStructureElement ofBlockUnlocalizedName( - String modid, String unlocalizedName, int meta, boolean wildcard) { - if (StringUtils.isBlank(unlocalizedName)) throw new IllegalArgumentException(); + String modid, String registryName, int meta, boolean wildcard) { + if (StringUtils.isBlank(registryName)) throw new IllegalArgumentException(); if (meta < 0) throw new IllegalArgumentException(); if (meta > 15) throw new IllegalArgumentException(); if (StringUtils.isBlank(modid)) throw new IllegalArgumentException(); @@ -470,7 +680,7 @@ public class StructureUtility { private Block block; private Block getBlock() { - if (block == null) block = GameRegistry.findBlock(modid, unlocalizedName); + if (block == null) block = GameRegistry.findBlock(modid, registryName); return block; } @@ -515,8 +725,11 @@ public class StructureUtility { * Similiar to the other overload, but allows client code to specify a fallback in case said block was not found later * when the element got called. *

- * This is slightly different to using the other overload and another element in a {@link #ofChain(IStructureElement[])}, - * as that combination would cause crash, where this won't. + * DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME INSTEAD. + *

+ * This is slightly different to using the other overload and another element in a {@link #ofChain(IStructureElement[])}. + * Here fallback will only ever be used if this fails, where ofChain would form an OR relationship even if the mod + * is loaded and the block exists in registry. */ public static IStructureElement ofBlockUnlocalizedName( String modid, String unlocalizedName, int meta, IStructureElement fallback) { @@ -578,7 +791,15 @@ public class StructureUtility { } /** - * Does not allow Block duplicates (with different meta) + * Accept a set of blocks. Less cumbersome to use than {@link #ofBlocksMapHint(Map, Block, int)} when for any accepted + * block type we accept only one meta for each. + *

+ * Does not have autoplace. + * + * @param blocsMap Accepted (block, meta) pairs. + * @param hintBlock hint block to use + * @param hintMeta hint meta to use + * @see #ofBlocksMapHint(Map, Block, int) */ public static IStructureElementNoPlacement ofBlocksFlatHint( Map blocsMap, Block hintBlock, int hintMeta) { @@ -601,7 +822,14 @@ public class StructureUtility { } /** - * Allows block duplicates (with different meta) + * Accept a set of blocks. + *

+ * Does not have autoplace. + * + * @param blocsMap Accepted (block, meta) pairs. + * @param hintBlock hint block to use + * @param hintMeta hint meta to use + * @see #ofBlocksFlatHint(Map, Block, int) */ public static IStructureElementNoPlacement ofBlocksMapHint( Map> blocsMap, Block hintBlock, int hintMeta) { @@ -629,6 +857,9 @@ public class StructureUtility { }; } + /** + * Accept one (block, meta). Spawn hint particles using an alternative block and meta. Not very useful... + */ public static IStructureElementNoPlacement ofBlockHint( Block block, int meta, Block hintBlock, int hintMeta) { if (block == null || hintBlock == null) { @@ -649,10 +880,19 @@ public class StructureUtility { }; } + /** + * Accept one (block, meta). Same as {@link #ofBlock(Block, int)}, except it explicitly turns off creative/survival + * build. + */ public static IStructureElementNoPlacement ofBlockHint(Block block, int meta) { return ofBlockHint(block, meta, block, meta); } + /** + * Add a block using block adder. Spawn hints using given hint block and meta. + *

+ * Useful when your logic is very complex. Does not support autoplace. + */ public static IStructureElementNoPlacement ofBlockAdderHint( IBlockAdder iBlockAdder, Block hintBlock, int hintMeta) { if (iBlockAdder == null || hintBlock == null) { @@ -674,7 +914,13 @@ public class StructureUtility { } /** - * Does not allow Block duplicates (with different meta) + * Accept a set of blocks. Less cumbersome to use than {@link #ofBlocksMap(Map, Block, int)} when for any accepted + * block type we accept only one meta for each. + * + * @param blocsMap Accepted (block, meta) pairs. + * @param defaultBlock default block to place/spawn hint + * @param defaultMeta default meta to place/spawn hint + * @see #ofBlocksMapHint(Map, Block, int) */ public static IStructureElement ofBlocksFlat( Map blocsMap, Block defaultBlock, int defaultMeta) { @@ -757,7 +1003,12 @@ public class StructureUtility { } /** - * Allows block duplicates (with different meta) + * Accept a set of blocks. + * + * @param blocsMap Accepted (block, meta) pairs. + * @param defaultBlock default block to place/spawn hint + * @param defaultMeta default meta to place/spawn hint + * @see #ofBlocksMapHint(Map, Block, int) */ public static IStructureElement ofBlocksMap( Map> blocsMap, Block defaultBlock, int defaultMeta) { @@ -846,6 +1097,14 @@ public class StructureUtility { } } + /** + * Accept a block. Spawn hint/autoplace using another. + * + * @param block accepted block + * @param meta accepted meta + * @param defaultBlock hint block + * @param defaultMeta hint meta + */ public static IStructureElement ofBlock(Block block, int meta, Block defaultBlock, int defaultMeta) { if (block == null || defaultBlock == null) { throw new IllegalArgumentException(); @@ -926,7 +1185,7 @@ public class StructureUtility { } /** - * Same as above but ignores target meta id + * Same as {@link #ofBlock(Block, int, Block, int)} but ignores target meta id */ public static IStructureElement ofBlockAnyMeta(Block block, Block defaultBlock, int defaultMeta) { if (block == null || defaultBlock == null) { @@ -1005,19 +1264,22 @@ public class StructureUtility { } } + /** + * Accept a single block with a fixed meta. Most primitive form of structure. + */ public static IStructureElement ofBlock(Block block, int meta) { return ofBlock(block, meta, block, meta); } /** - * Same as above but ignores target meta id + * Accept a single block, but accept any meta. */ public static IStructureElement ofBlockAnyMeta(Block block) { return ofBlockAnyMeta(block, block, 0); } /** - * Same as above but allows to set hint particle render + * Accept a single block, but accept any meta. Spawn hint/autoplace using given meta. */ public static IStructureElement ofBlockAnyMeta(Block block, int defaultMeta) { return ofBlockAnyMeta(block, block, defaultMeta); @@ -1027,6 +1289,11 @@ public class StructureUtility { // region adders + /** + * Add a block using block adder. Spawn hints/autoplace using given hint block and meta. + *

+ * Useful when your logic is very complex. + */ public static IStructureElement ofBlockAdder( IBlockAdder iBlockAdder, Block defaultBlock, int defaultMeta) { if (iBlockAdder == null || defaultBlock == null) { @@ -1063,6 +1330,8 @@ public class StructureUtility { IItemSource s, EntityPlayerMP actor, Consumer chatter) { + if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta) + return PlaceResult.SKIP; return StructureUtility.survivalPlaceBlock( defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); } @@ -1098,6 +1367,8 @@ public class StructureUtility { IItemSource s, EntityPlayerMP actor, Consumer chatter) { + if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta) + return PlaceResult.SKIP; return StructureUtility.survivalPlaceBlock( defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); } @@ -1109,6 +1380,10 @@ public class StructureUtility { return ofBlockAdder(iBlockAdder, StructureLibAPI.getBlockHint(), dots - 1); } + /** + * Try to add a structure element with a tile entity. Note that tile adder will be called with a null argument at + * locations without tile entity. + */ public static IStructureElementNoPlacement ofTileAdder( ITileAdder iTileAdder, Block hintBlock, int hintMeta) { if (iTileAdder == null || hintBlock == null) { @@ -1130,6 +1405,10 @@ public class StructureUtility { }; } + /** + * Try to add a structure element with a particular type of tile entity. Note that tile adder will not be called at + * locations without a tile entity. + */ public static IStructureElementNoPlacement ofSpecificTileAdder( BiPredicate iTileAdder, Class tileClass, Block hintBlock, int hintMeta) { if (iTileAdder == null || hintBlock == null || tileClass == null) { @@ -1157,6 +1436,12 @@ public class StructureUtility { // region side effects + /** + * Call a callback if downstream element returned true in check. + * + * @param onCheckPass side effect + * @param element downstream + */ public static , T> IStructureElement onElementPass( Consumer onCheckPass, B element) { return new IStructureElement() { @@ -1195,6 +1480,12 @@ public class StructureUtility { }; } + /** + * Call a callback if downstream element returned false in check. + * + * @param onFail side effect + * @param element downstream + */ public static , T> IStructureElement onElementFail( Consumer onFail, B element) { return new IStructureElement() { @@ -1237,6 +1528,8 @@ public class StructureUtility { /** * Enable this structure element only if given predicate returns true. + *

+ * Return SKIP when survival auto place if given predicate returns false. */ public static IStructureElement onlyIf( Predicate predicate, IStructureElement downstream) { @@ -1245,6 +1538,8 @@ public class StructureUtility { /** * Enable this structure element only if given predicate returns true. + * + * @param placeResultWhenDisabled value to return for survival auto place when predicate returns false */ public static IStructureElement onlyIf( Predicate predicate, @@ -1285,6 +1580,20 @@ public class StructureUtility { } /** + * This allows you to compose different {@link IStructureElement} to form a OR chain. + * As with any other OR operator, this one exhibits short-circuiting behavior, i.e. it will not call next structure element + * if previous one succeeded. (*) + *

+ * This allows you e.g. accept both a glass block using {@link #ofBlock(Block, int, Block, int)} and a piece of air + * using {@link #isAir()}. + * It will not attempt to capture any errors though, so next one will not be tried if previous one would crash. + *

+ * (*): For survival auto place, it will + * * REJECT, if all structure element REJECT + * * SKIP, if 1 or more structure element SKIP and the rest structure element (0 or more) REJECT + * * any other result, **immediately** upon any structure element returns these other results. + * This behavior is not 100% fixed and might change later on, but we will send the notice on a best effort basis. + *

* Take care while chaining, as it will try to call every structure element until it returns true. * If none does it will finally return false. */ @@ -1302,8 +1611,11 @@ public class StructureUtility { } /** - * Take care while chaining, as it will try to call every structure element until it returns true. - * If none does it will finally return false. + * This allows you to compose different {@link IStructureElement} to form a OR chain. + *

+ * Practically no difference with the other overload. + * + * @see #ofChain(IStructureElement[]) */ @SuppressWarnings("unchecked") public static IStructureElementChain ofChain(List> elementChain) { @@ -1311,8 +1623,16 @@ public class StructureUtility { } // region context - public static , E extends IStructureElement> - IStructureElement withContext(E elem) { + + /** + * Switch to the extended context object in the downstream element + * + * @param elem downstream element + * @param extended context type + * @param existing context object type + */ + public static > IStructureElement withContext( + IStructureElement elem) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -1351,6 +1671,9 @@ public class StructureUtility { /** * Similar to defer, but caches the first returned element returned and won't call it again. * Initialization is not thread safe. + * These both allow the structure element **constructor** to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use `lazy` if the data you access will remain constant across different context object. */ public static IStructureElementDeferred lazy(Supplier> to) { if (to == null) { @@ -1360,8 +1683,15 @@ public class StructureUtility { } /** + * This will defer the actual instantiation of structure element until the first time structure code is actually called. + *

* Similar to defer, but caches the first returned element returned and won't call it again. * Initialization is not thread safe. + * This will allow the structure element constructor to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use this if the data you access will remain constant. + * + * @param to create structure element from the first context object passed in */ public static IStructureElementDeferred lazy(Function> to) { if (to == null) { @@ -1370,6 +1700,17 @@ public class StructureUtility { return new LazyStructureElement<>(to); } + /** + * This will defer the actual instantiation of structure element until the structure code is actually called. + * This will allow the structure element constructor to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use this if it might change for the same structure definition, e.g. if your structure might switch mode and + * change some parameter, while basically remain the same shape. + * Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary + * performance overhead. + * + * @param to downstream element supplier + */ public static IStructureElementDeferred defer(Supplier> to) { if (to == null) { throw new IllegalArgumentException(); @@ -1406,6 +1747,17 @@ public class StructureUtility { }; } + /** + * This will defer the actual instantiation of structure element until the structure code is actually called. + * This will allow the structure element constructor to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use this if it might change for the same structure definition, e.g. if your structure might switch mode and + * change some parameter, while basically remain the same shape. + * Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary + * performance overhead. + * + * @param to downstream element supplier + */ public static IStructureElementDeferred defer(Function> to) { if (to == null) { throw new IllegalArgumentException(); @@ -1442,8 +1794,37 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. + * + * @param keyExtractor extract a key from the context object + * @param map all possible structure element + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( Function keyExtractor, Map> map) { + return partitionBy(keyExtractor, map); + } + + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key + * not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + * + * @param keyExtractor extract a key from the context object + * @param map all possible structure element + */ + public static IStructureElementDeferred partitionBy( + Function keyExtractor, Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1479,8 +1860,43 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code, with default case support. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play + * nicely in combination with {@link #error()} + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + * + * @param keyExtractor extract a key from the context object + * @param map all possible structure element + * @param defaultElem element to use when keyExtractor returns a value not found in given map + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( Function keyExtractor, Map> map, IStructureElement defaultElem) { + return partitionBy(keyExtractor, map, defaultElem); + } + + /** + * This is the switch block for structure code, with default case support. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play + * nicely in combination with {@link #error()} + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + * + * @param keyExtractor extract a key from the context object + * @param map all possible structure element + * @param defaultElem element to use when keyExtractor returns a value not found in given map + */ + public static IStructureElementDeferred partitionBy( + Function keyExtractor, Map> map, IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1517,9 +1933,40 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + * + * @param keyExtractor extract an index from the context object + * @param array all possible structure element + * @deprecated renamed to partitionBy + */ @SafeVarargs public static IStructureElementDeferred defer( Function keyExtractor, IStructureElement... array) { + return partitionBy(keyExtractor, array); + } + + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + * + * @param keyExtractor extract an index from the context object + * @param array all possible structure element + */ + @SafeVarargs + public static IStructureElementDeferred partitionBy( + Function keyExtractor, IStructureElement... array) { if (keyExtractor == null || array == null) { throw new IllegalArgumentException(); } @@ -1555,12 +2002,56 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + * + * @param keyExtractor extract an index from the context object + * @param array all possible structure element + * @deprecated renamed to partitionBy + */ @SuppressWarnings("unchecked") public static IStructureElementDeferred defer( Function keyExtractor, List> array) { + return partitionBy(keyExtractor, array); + } + + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + * + * @param keyExtractor extract an index from the context object + * @param array all possible structure element + */ + @SuppressWarnings("unchecked") + public static IStructureElementDeferred partitionBy( + Function keyExtractor, List> array) { return defer(keyExtractor, array.toArray(new IStructureElement[0])); } + /** + * This will defer the actual instantiation of structure element until the structure code is actually called. + * This will allow the structure element constructor to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use this if it might change for the same structure definition, e.g. if your structure might switch mode and + * change some parameter, while basically remain the same shape. + * Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary + * performance overhead. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param to downstream element supplier + */ public static IStructureElementDeferred defer(BiFunction> to) { if (to == null) { throw new IllegalArgumentException(); @@ -1597,8 +2088,45 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key + * not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( BiFunction keyExtractor, Map> map) { + return partitionBy(keyExtractor, map); + } + + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key + * not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + */ + public static IStructureElementDeferred partitionBy( + BiFunction keyExtractor, Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1635,10 +2163,51 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code, with default case support. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play + * nicely in combination with {@link #error()} + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + * @param defaultElem element to use when keyExtractor returns a value not found in given map + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( BiFunction keyExtractor, Map> map, IStructureElement defaultElem) { + return partitionBy(keyExtractor, map, defaultElem); + } + + /** + * This is the switch block for structure code, with default case support. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play + * nicely in combination with {@link #error()} + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + * @param defaultElem element to use when keyExtractor returns a value not found in given map + */ + public static IStructureElementDeferred partitionBy( + BiFunction keyExtractor, + Map> map, + IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1678,6 +2247,20 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + */ @SafeVarargs public static IStructureElementDeferred defer( BiFunction keyExtractor, IStructureElement... array) { @@ -1717,12 +2300,41 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + * + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + */ @SuppressWarnings("unchecked") public static IStructureElementDeferred defer( BiFunction keyExtractor, List> array) { return defer(keyExtractor, array.toArray(new IStructureElement[0])); } + /** + * This will defer the actual instantiation of structure element until the first time structure code is actually called. + *

+ * Similar to defer, but caches the first returned element returned and won't call it again. + * Initialization is not thread safe. + * This will allow the structure element constructor to access properties only present on the context object + * (e.g. GT5 multiblock controller), e.g. hatch texture index to use. + * Use this if the data you access will remain constant. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param toCheck override the check function with the returned element + * @param to create structure element from the context object passed in + */ public static IStructureElementDeferred defer( Function> toCheck, BiFunction> to) { if (to == null) { @@ -1760,10 +2372,57 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key + * not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( Function keyExtractorCheck, BiFunction keyExtractor, Map> map) { + return partitionBy(keyExtractorCheck, keyExtractor, map); + } + + /** + * This is the switch block for structure code. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key + * not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + */ + public static IStructureElementDeferred partitionBy( + Function keyExtractorCheck, + BiFunction keyExtractor, + Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1800,11 +2459,57 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. defaultElem will be used if either extractor returned a key not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + * @deprecated renamed to partitionBy + */ + @Deprecated public static IStructureElementDeferred defer( Function keyExtractorCheck, BiFunction keyExtractor, Map> map, IStructureElement defaultElem) { + return partitionBy(keyExtractorCheck, keyExtractor, map, defaultElem); + } + + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute a key from context object, and lookup a map for actual structure element + * to use. defaultElem will be used if either extractor returned a key not found in map. + *

+ * Do pay attention to what properties your map has though. You need to pay attention to how they consider equality + * and how they consider null key/values. Guava ImmutableMap is usually a good enough choice. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param map all possible structure element + */ + public static IStructureElementDeferred partitionBy( + Function keyExtractorCheck, + BiFunction keyExtractor, + Map> map, + IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1843,11 +2548,56 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + * @deprecated renamed to partitionBy + */ @SafeVarargs public static IStructureElementDeferred defer( Function keyExtractorCheck, BiFunction keyExtractor, IStructureElement... array) { + return partitionBy(keyExtractorCheck, keyExtractor, array); + } + + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * Do pay place null values in the array. + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + */ + @SafeVarargs + public static IStructureElementDeferred partitionBy( + Function keyExtractorCheck, + BiFunction keyExtractor, + IStructureElement... array) { if (keyExtractor == null || array == null) { throw new IllegalArgumentException(); } @@ -1884,11 +2634,52 @@ public class StructureUtility { }; } + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + * @deprecated renamed to partitionBy + */ @SuppressWarnings("unchecked") public static IStructureElementDeferred defer( Function keyExtractorCheck, BiFunction keyExtractor, List> array) { + return partitionBy(keyExtractorCheck, keyExtractor, array); + } + + /** + * This is the switch block for structure code, with default case. + *

+ * This allows you to extract or compute an index from context object, and lookup an array for actual structure element + * to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns an index + * not within the array bound + *

+ * This variant also passes the trigger item to the function, allowing it to get more info. + *

+ * This variant will override the check function of the structure element from second function with the one returned + * from the structure element from first function. + * + * @param keyExtractorCheck key extractor of the structure element that will be used for check. + * @param keyExtractor extract a key from the context object and trigger item + * @param array all possible structure element + */ + @SuppressWarnings("unchecked") + public static IStructureElementDeferred partitionBy( + Function keyExtractorCheck, + BiFunction keyExtractor, + List> array) { return defer(keyExtractorCheck, keyExtractor, array.toArray(new IStructureElement[0])); } @@ -2266,7 +3057,7 @@ public class StructureUtility { xyz[1] += basePositionY; xyz[2] += basePositionZ; - if (DEBUG_MODE) + if (StructureLibAPI.isDebugEnabled()) StructureLib.LOGGER.info( "Multi [{}, {}, {}] {} step @ {} {}", basePositionX, @@ -2278,7 +3069,7 @@ public class StructureUtility { if (world.blockExists(xyz[0], xyz[1], xyz[2])) { if (!predicate.visit(element, world, xyz[0], xyz[1], xyz[2])) { - if (DEBUG_MODE) { + if (StructureLibAPI.isDebugEnabled()) { StructureLib.LOGGER.info( "Multi [{}, {}, {}] {} stop @ {} {}", basePositionX, @@ -2291,7 +3082,7 @@ public class StructureUtility { return false; } } else { - if (DEBUG_MODE) { + if (StructureLibAPI.isDebugEnabled()) { StructureLib.LOGGER.info( "Multi [{}, {}, {}] {} !blockExists @ {} {}", basePositionX, diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/adders/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/structure/adders/package-info.java new file mode 100644 index 00000000..1db6e005 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/adders/package-info.java @@ -0,0 +1,4 @@ +/** + * Adder interfaces used in various standard structure elements. + */ +package com.gtnewhorizon.structurelib.structure.adders; diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java index dd43e8c1..aaee3a22 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java @@ -1,4 +1,6 @@ /** + * This package provide support for multiblock structure checking. + *

* All public stuff in this package and its subpackages are considered part of the public API. * Backwards compatibility is maintained to the maximum extend possible. */ diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html new file mode 100644 index 00000000..3c6a9589 --- /dev/null +++ b/src/main/javadoc/overview.html @@ -0,0 +1,235 @@ + + + + API Overview + + +StructureLib API documentation. + +

Key API class outline

+

+ Here we outline a few key API classes/interfaces. +

+ +

{@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}

+

+ This is the structure definition of your multi. + You will have one `IStructureDefinition` for each multi. +

+ +

{@link com.gtnewhorizon.structurelib.structure.IStructureElement}

+

+ These are how you define which block will be accepted. +

+ +

{@link com.gtnewhorizon.structurelib.structure.StructureUtility}

+

+ This is the standard library of {@link com.gtnewhorizon.structurelib.structure.IStructureElement + IStructureElement}s. + The Standard Library contains structure elements that handle basic block element and elements that allow composing + these basic elements. +

+ +

{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}

+

+ This interface should be implemented by tile entities that support creative autoplacing and hint particle emitting. + You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition + IStructureDefinition} + and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#build(Object, ItemStack, String, + World, ExtendedFacing, int, int, int, int, int, int) build()} + or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#hints(Object, ItemStack, String, World, + ExtendedFacing, int, int, int, int, int, int) hints()} method here. +

+ +

{@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}

+

+ This interface is an enhancement over {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable + IConstructable}. + If your tile entities also support survival autoplacing then you should implement this. + You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition + IStructureDefinition} + and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, + String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean) + survivalBuild()}. +

+ +

{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}

+

+ This interface should be implemented on tile entities that will delegate all constructable or survival construable + functionality to some other class. + This is mostly only to support gregtech meta tile entity system. +

+

+ Return null if the delegate does not exist. +

+ +

{@link com.gtnewhorizon.structurelib.util.InventoryUtility}

+

+ This class backs the standard implementations of {@link com.gtnewhorizon.structurelib.structure.IItemSource}. + You might want to register new inventory provider or inventory extractor there to facilitate survival auto place. +

+ +

{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link + com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link + com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}

+

+ These interfaces will be used to implement rotation. + Provide a framework to rotate the block. + You can skip this if you store ExtendedFacing and do rotation in other means. +

+ +

{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}

+

+ This class contains the accessors for channel data held on a trigger item. See below for more + information. +

+ +

{@link com.gtnewhorizon.structurelib.StructureLibAPI}

+

+ This class contains the low level utility used for implementing new {@link + com.gtnewhorizon.structurelib.structure.IStructureElement}. + You will also need methods in this class to while implementing rotation. +

+

+ IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS. + This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered) + without + breaking client code. +

+ +

Important Concepts

+

Context Object

+

+ Context Object are what you pass to {@link + com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, + int, int, int, int, boolean)} and alike as first parameter. It will hold the necessary states for {@link + com.gtnewhorizon.structurelib.structure.IStructureElement}. It's usually, but not necessarily, the multiblock controller. +

+

+ Standard structure element library offers method to switch context objects if needed. +

+ +

Trigger Item

+

+ Trigger Item is the item used to trigger hint spawn, creative build and survival auto place. + The trigger item will be passed to relevant classes as an indicator of structure tier. + The item type and stack tag compound of this item stack are opaque objects that should not be parsed. +

+

+ StructureLib comes with a trigger item implementation. + It does register it to the game registries, but does not come with a recipe. + Usually there is no reason to reimplement this on your own. +

+ +

Channels

+ +

+ Trigger item carries the player's choice of structure tier by its stack size. + When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious + restriction, as one ItemStack cannot have two stack sizes at the same time. + Here we use the channels to bypass this restriction. +

+ +

+ Basically, channels are information stored on the trigger item's NBT tags. + One trigger item can carry multiple channels. + Each channel can be identified with lower case strings and has the value being one positive Java int. + There will be one and only one master channel and 0 or more sub channel. + When a non-existent sub channel is queried, master channel will be used instead to fill its place. +

+ +

+ Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel + to use. +

+ +

+ Structure Element Author will usually not need to consider channels, i.e. proceed as if this concept does not exist, + unless it must take input from multiple channels. +

+ +

+ Structure Definition Author will consider how to map each channel to each tiered component. + He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element + on which channel to use. + Conceptually, it will return an ItemStack with the master channel being the value of selected channel. + In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size. +

+ +

+ It's worth mentioning that the returned new ItemStack will still have all the channel data on it, so a multi-channel + structure element can still query the channel it wants just fine. It is however impossible to recover the original + master channel. +

+ +

why is this called channel?

+ +Just give me a better name and I'd do the renaming. + +

Examples

+ +

For Structure Definition Author

+ +

GT++ Chemical Plant is a multi with 4 distinctly tiered component.

+ +

It will use +

    +
  • master channel for its outer shell
  • +
  • channel `"casing"` for machine casings
  • +
  • channel `"pipe"` for pipe casings
  • +
  • channel `"coil"` for heating coils
  • +
+Its structure code will then look like +
+.addElement('a', shellStructureElement())
+.addElement('b', withChannel("casing", machineCasingStructureElement()))
+.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
+.addElement('d', withChannel("coil", coilStructureElement()))
+
+whereas all these structure elements are designed without considering channels whatsoever + +

Multi channel input Structure Element

+ +

+ Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both + meta id + and facing input. +

+ +

+ It will use +

    +
  • master channel for its facing
  • +
  • channel `"id"` for meta tile entity id
  • +
+

+ Its code will then look like +

+import com.gtnewhorizon.structurelib.structure.IStructureElement;
+
+public class MyHatchElement<T> implements IStructureElement<T> {
+    //...
+    @Override
+    public PlaceResult survivalPlaceBlock(
+            Object o,
+            World world,
+            int x,
+            int y,
+            int z,
+            ItemStack trigger,
+            IItemSource s,
+            EntityPlayerMP actor,
+            Consumer chatter) {
+        if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
+        if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
+        ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
+        if (taken == null) return PlaceResult.REJECT;
+        return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
+    }
+}
+
+ +The code for placeBlock() and spawnHint() will be left as an exercise for the reader. + + +