Add javadoc for APIs (#10)

This commit is contained in:
Glease authored and GitHub committed 2022-08-30 23:27:18 +08:00
1 parent 21198c4d39
commit adc91bb1dd
19 files changed
+1476 -196

No files matched your search

+64
View File
@@ -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 }}
+24
View File
@@ -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
}
-72
View File
@@ -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
-62
View File
@@ -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<IStructureDefinition<?>>` 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.
@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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);
}
}
@@ -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.
*
* <a href="{@docRoot}/overview-summary.html#channels">Channels section on overview</a>
*/
public class ChannelDataAccessor {
private static final String SECONDARY_HINT_TAG = "channels";
private ChannelDataAccessor() {}
/**
* Return a trigger item that
* <ul>
* <li>Still carry all the subchannel in some unspecified ways</li>
* <li>Have its master channel data replaced with specified subchannel's data</li>
* </ul>
*
* 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<Entry<String, Integer>> 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);
@@ -0,0 +1,4 @@
/**
* This package contains interfaces and utility methods for autobuilding a structure.
*/
package com.gtnewhorizon.structurelib.alignment.constructable;
@@ -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);
@@ -1,4 +1,6 @@
/**
* This package provide a framework for rotating a given block, or a multiblock structure.
* <p>
* 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.
*/
@@ -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();
@@ -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,
@@ -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,
@@ -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.
*
* <h2>Cache the instances of this class</h2>
* <p>
* 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&lt;IStructureDefinition&lt;?>>} 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.
* <p>
* <h2>Constructing</h2>
* <p>
* {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()}
* <p>
* Builder has two main methods:
* <ul>
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) 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.
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addElement(Character, IStructureElement) addElement()}. It takes a char identifier (mapped to a char in any of {@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()} calls) and a {@link IStructureElement}.
* </ul>
* <h2>Position A, B and C</h2>
*
* Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
* <ul>
* <li>The "A" position would be the number of blocks on the left side of the controller, not counting controller itself.</li>
* <li>The "B" position would be the number of blocks on the top side of the controller, not counting controller itself.</li>
* <li>The "C" position would be the number of blocks between you and controller, not counting controller itself.</li>
* </ul>
* @param <T> Type of the context object. Usually this will be the multiblock controller.
*/
public interface IStructureDefinition<T> {
/**
* Used internally
@@ -22,6 +55,21 @@ public interface IStructureDefinition<T> {
*/
IStructureElement<T>[] 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<T> {
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<T> {
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<T> {
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<T> {
}
/**
* 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<T> {
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 <T> type of context object.
*/
static <T> boolean iterate(
T object,
ItemStack trigger,
@@ -226,7 +357,7 @@ public interface IStructureDefinition<T> {
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<T> {
return true;
}
/**
* Create a new instance of builder.
* @return
* @param <T> type of context object
*/
static <T> StructureDefinition.Builder<T> builder() {
return StructureDefinition.builder();
}
@@ -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<T> {
/**
* Try place the block by taking resource from given IItemSource.
* <p>
* You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayerMP)}
* 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<T> {
EntityPlayerMP actor,
Consumer<IChatComponent> 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<T> {
ACCEPT,
/**
* Combination of ACCEPT and STOP.
*
* <p>
* Element placed successfully. To proceed further would require stopping the placement and wait for next round.
*/
ACCEPT_STOP;
@@ -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<T> implements IStructureDefinition<T> {
public IStructureDefinition<T> build() {
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
if (DEBUG_MODE) {
if (StructureLibAPI.isDebugEnabled()) {
return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures);
} else {
return structures::get;
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,4 @@
/**
* Adder interfaces used in various standard structure elements.
*/
package com.gtnewhorizon.structurelib.structure.adders;
@@ -1,4 +1,6 @@
/**
* This package provide support for multiblock structure checking.
* <p>
* 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.
*/
+235
View File
@@ -0,0 +1,235 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>API Overview</TITLE>
</HEAD>
<BODY>
StructureLib API documentation.
<h1>Key API class outline</h1>
<p>
Here we outline a few key API classes/interfaces.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}</h2>
<p>
This is the structure definition of your multi.
You will have one `IStructureDefinition` for each multi.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureElement}</h2>
<p>
These are how you define which block will be accepted.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.StructureUtility}</h2>
<p>
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.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}</h2>
<p>
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.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}</h2>
<p>
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()}.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}</h2>
<p>
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.
</p>
<p>
Return null if the delegate does not exist.
</p>
<h2>{@link com.gtnewhorizon.structurelib.util.InventoryUtility}</h2>
<p>
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.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link
com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link
com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}</h2>
<p>
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.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}</h2>
<p>
This class contains the accessors for channel data held on a trigger item. See <a href="#channels">below</a> for more
information.
</p>
<h2>{@link com.gtnewhorizon.structurelib.StructureLibAPI}</h2>
<p>
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.
</p>
<p>
<b>IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS.</b>
This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered)
without
breaking client code.
</p>
<h1>Important Concepts</h1>
<h2>Context Object</h2>
<p>
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.
</p>
<p>
Standard structure element library offers method to switch context objects if needed.
</p>
<h2>Trigger Item</h2>
<p>
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.
</p>
<p>
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.
</p>
<h2><a href="#channels" id="channels">Channels</a></h2>
<p>
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 <i>channels</i> to bypass this restriction.
</p>
<p>
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 <b>lower case</b> 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.
</p>
<p>
Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel
to use.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<h3>why is this called channel?</h3>
Just give me a better name and I'd do the renaming.
<h3>Examples</h3>
<h4>For Structure Definition Author</h4>
<p>GT++ Chemical Plant is a multi with 4 distinctly tiered component.</p>
<p>It will use
<ul>
<li>master channel for its outer shell</li>
<li>channel `"casing"` for machine casings</li>
<li>channel `"pipe"` for pipe casings</li>
<li>channel `"coil"` for heating coils</li>
</ul>
Its structure code will then look like
<pre>
.addElement('a', shellStructureElement())
.addElement('b', withChannel("casing", machineCasingStructureElement()))
.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
.addElement('d', withChannel("coil", coilStructureElement()))
</pre>
whereas all these structure elements are designed without considering channels whatsoever
<h4> Multi channel input Structure Element</h4>
<p>
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.
</p>
<p>
It will use
<ul>
<li>master channel for its facing</li>
<li>channel `"id"` for meta tile entity id</li>
</ul>
<p>
Its code will then look like
<pre>
import com.gtnewhorizon.structurelib.structure.IStructureElement;
public class MyHatchElement&lt;T> implements IStructureElement&lt;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);
}
}
</pre>
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
</BODY>
</HTML>