Compare commits

...
8 Commits
32 changed files with 1745 additions and 280 deletions

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["gh-pages"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Generate Index
shell: bash
run: |
find . -name javadoc -type d -print > index.txt
echo feature/doc >> index.txt
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
+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.
@@ -24,6 +24,7 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
@@ -69,6 +70,10 @@ public class ClientProxy extends CommonProxy {
* counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough
*/
private static int renderThrough;
/**
* A throttle map for local player. Integrated server will use the throttle map in super class instead.
*/
private static final Map<Object, Long> localThrottleMap = new HashMap<>();
@Override
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
@@ -152,6 +157,19 @@ public class ClientProxy extends CommonProxy {
return true;
}
@Override
public void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
if (player instanceof EntityPlayerMP)
super.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
else if (player != null)
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, localThrottleMap);
}
static void removeGroup(HintGroup group) {
for (HintParticleInfo hintParticleInfo : group.getHints()) {
allHints.remove(hintParticleInfo);
@@ -250,7 +268,11 @@ public class ClientProxy extends CommonProxy {
private static class HintParticleInfo {
private final World w;
// these are the block coordinate for e.g. w.getBlock()
private final int x, y, z;
// these are the lower bounds for rendering. the upper bound would be X + size
// currently size is fixed to be 0.5
// it is not made into a constants because this will allow for easy changing during debug
private final double X, Y, Z;
private final IIcon[] icons;
private short[] tint;
@@ -301,7 +323,7 @@ public class ClientProxy extends CommonProxy {
}
public boolean isInFrustrum(Frustrum frustrum) {
return frustrum.isBoxInFrustum(x, y, z, X, Y, Z);
return frustrum.isBoxInFrustum(X, Y, Z, X + 0.5, Y + 0.5, Z + 0.5);
}
public void draw(Tessellator tes) {
@@ -310,7 +332,11 @@ public class ClientProxy extends CommonProxy {
int brightness = w.blockExists(x, 0, z) ? w.getLightBrightnessForSkyBlocks(x, y, z, 0) : 0;
tes.setBrightness(brightness);
tes.setColorRGBA((int) (tint[0] * .9F), (int) (tint[1] * .95F), (int) (tint[2] * 1F), 192);
tes.setColorRGBA(
(int) (tint[0] * .9F),
(int) (tint[1] * .95F),
(int) (tint[2] * 1F),
ConfigurationHandler.INSTANCE.getHintTransparency());
for (int i = 0; i < 6; i++) {
if (icons[i] == null) continue;
@@ -385,7 +411,8 @@ public class ClientProxy extends CommonProxy {
boolean sortRequired = false;
// The allGroups is implicitly sorted by creationTime
// here we exploit this ordering to reduce iteration size
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted - HOLOGRAM_LIFETIME;
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted
- ConfigurationHandler.INSTANCE.getHintLifespan();
int i;
for (i = 0; i < allGroups.size(); i++) {
if (allGroups.get(i).getCreationTime() > deadline) {
@@ -423,10 +450,13 @@ public class ClientProxy extends CommonProxy {
@SubscribeEvent
public void onWorldLoad(WorldEvent.Load e) {
if (e.world.isRemote) {
// flush hints. we are in a different world now.
allHintsForRender.clear();
allGroups.clear();
lastPlayerPos.yCoord = -1e30;
renderThrough = 0;
// clear throttles. hopefully a world switch is enough long as a cool down.
localThrottleMap.clear();
}
}
@@ -3,11 +3,15 @@ package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
@@ -61,4 +65,38 @@ public class CommonProxy {
return false;
}
}
private final Map<EntityPlayerMP, Map<Object, Long>> throttleMap = new WeakHashMap<>();
public void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
if (player instanceof EntityPlayerMP) {
Map<Object, Long> submap = throttleMap.computeIfAbsent((EntityPlayerMP) player, p -> new HashMap<>());
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, submap);
}
}
protected static void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend,
Map<Object, Long> submap) {
long now = System.currentTimeMillis();
Long old;
if (forceUpdateLastSend) {
old = submap.put(throttleKey, now);
} else {
old = submap.get(throttleKey);
}
if (old == null || now - old >= intervalRequired) {
player.addChatComponentMessage(text);
if (!forceUpdateLastSend) submap.put(throttleKey, now);
}
}
}
@@ -15,6 +15,8 @@ public enum ConfigurationHandler {
private Configuration config;
private int maxCoexistingHologram;
private boolean removeCollidingHologram;
private int hintLifespan;
private int hintTransparency;
private int autoPlaceBudget;
private int autoPlaceInterval;
@@ -52,6 +54,15 @@ public enum ConfigurationHandler {
"client.hologram",
true,
"An attempt will be made to remove an existing hologram if it collides with a new hologram.");
hintLifespan =
config.getInt("hintLifespan", "client.hologram", 400, 1, 20000, "Ticks before a hologram disappears.");
hintTransparency = config.getInt(
"hintTransparency",
"client.hologram",
192,
1,
255,
"Alpha value of hologram particles. Higher the value, the more \"ghostly\" the hologram will appear to be.");
autoPlaceBudget = config.getInt(
"autoPlaceBudget",
"common.hologram",
@@ -67,7 +78,7 @@ public enum ConfigurationHandler {
300,
0,
20000,
"Unit: milisecond. Minimal interval between two auto place round.\n"
"Unit: millisecond. Minimal interval between two auto place round.\n"
+ "As expected, server side settings will overrides client settings.\n"
+ "Note this relates to the wall clock, not in game ticks.\n"
+ "Value smaller than default is likely to be perceived as no minimal interval whatsoever.");
@@ -92,6 +103,14 @@ public enum ConfigurationHandler {
return removeCollidingHologram;
}
public int getHintLifespan() {
return hintLifespan;
}
public int getHintTransparency() {
return hintTransparency;
}
public int getAutoPlaceBudget() {
return autoPlaceBudget;
}
@@ -1,51 +0,0 @@
package com.gtnewhorizon.structurelib;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GLContext;
class GLHelper {
public static final boolean vboSupported;
private static final boolean arbVbo;
static {
ContextCapabilities contextcapabilities = GLContext.getCapabilities();
arbVbo = !contextcapabilities.OpenGL15 && contextcapabilities.GL_ARB_vertex_buffer_object;
vboSupported = contextcapabilities.OpenGL15 || arbVbo;
}
public static int glGenBuffers() {
return arbVbo ? ARBVertexBufferObject.glGenBuffersARB() : GL15.glGenBuffers();
}
public static void glBindBuffer(int target, int buffer) {
if (arbVbo) {
ARBVertexBufferObject.glBindBufferARB(target, buffer);
} else {
GL15.glBindBuffer(target, buffer);
}
}
public static void glBufferData(int target, ByteBuffer data, int usage) {
if (arbVbo) {
ARBVertexBufferObject.glBufferDataARB(target, data, usage);
} else {
GL15.glBufferData(target, data, usage);
}
}
public static void glDeleteBuffers(int buffer) {
if (arbVbo) {
ARBVertexBufferObject.glDeleteBuffersARB(buffer);
} else {
GL15.glDeleteBuffers(buffer);
}
}
public static boolean useVbo() {
return vboSupported;
}
}
@@ -42,14 +42,14 @@ public class StructureLib {
private static final String STRUCTURECOMPAT_MODID = "structurecompat";
public static boolean DEBUG_MODE;
public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic");
public static Logger LOGGER = LogManager.getLogger("StructureLib");
public static final Logger LOGGER = LogManager.getLogger("StructureLib");
@SidedProxy(
serverSide = "com.gtnewhorizon.structurelib.CommonProxy",
clientSide = "com.gtnewhorizon.structurelib.ClientProxy")
static CommonProxy proxy;
static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
static {
net.registerMessage(
@@ -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;
@@ -12,6 +13,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
@@ -55,25 +57,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 +124,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 +132,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 +146,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 +157,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 +168,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 +179,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 +188,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 +218,45 @@ 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);
}
/**
* Send chat to player, but throttled.
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
* to handle value equality.
* @param player player to send chat to
* @param text chat to send
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
* we purposefully chose to not use a bigger data type to limit how long this interval can be
*/
public static void addThrottledChat(
Object throttleKey, EntityPlayer player, IChatComponent text, short intervalRequired) {
addThrottledChat(throttleKey, player, text, intervalRequired, false);
}
/**
* Send chat to player, but throttled.
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
* to handle value equality.
* @param player player to send chat to
* @param text chat to send
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
* we purposefully chose to not use a bigger data type to limit how long this interval can be
* @param forceUpdateLastSend if true, always update the last send time, even if the message isn't actually sent.
*/
public static void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
proxy.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
}
}
@@ -122,7 +122,7 @@ public interface IAlignmentLimits {
}
/**
* Prefers rotation over flip, so both flip will get translated to opposite rotation and no flip
* Prefers rotation to flip, so both flip will get translated to opposite rotation and no flip
*
* @param flip the preferred flip to be used Horizontal or vertical
* @return this
@@ -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.
*/
@@ -29,10 +29,10 @@ import org.lwjgl.opengl.GL11;
public abstract class GuiScrollableList<T> {
protected final int slotHeight;
private final Minecraft mc = Minecraft.getMinecraft();
protected int originX;
protected int originY;
protected int width;
protected int height;
protected final int originX;
protected final int originY;
protected final int width;
protected final int height;
protected int minY;
protected int maxY;
protected int minX;
@@ -41,7 +41,7 @@ public abstract class GuiScrollableList<T> {
protected int mouseY;
protected int selectedIndex = -1;
protected int margin = 4;
protected List<ListSelectionListener<T>> listeners = new CopyOnWriteArrayList<>();
protected final List<ListSelectionListener<T>> listeners = new CopyOnWriteArrayList<>();
private int scrollUpButtonID;
private int scrollDownButtonID;
private float initialClickY = -2.0F;
@@ -153,11 +153,11 @@ public abstract class GuiScrollableList<T> {
public void actionPerformed(GuiButton b) {
if (b.enabled) {
if (b.id == scrollUpButtonID) {
amountScrolled -= slotHeight * 2 / 3;
amountScrolled -= slotHeight * 2f / 3;
initialClickY = -2.0F;
clampScrollToBounds();
} else if (b.id == scrollDownButtonID) {
amountScrolled += slotHeight * 2 / 3;
amountScrolled += slotHeight * 2f / 3;
initialClickY = -2.0F;
clampScrollToBounds();
}
@@ -325,9 +325,9 @@ public abstract class GuiScrollableList<T> {
public void handleDWheel(int mouseWheelDelta) {
if (!Mouse.isButtonDown(0)) {
if (mouseWheelDelta > 0) {
amountScrolled -= slotHeight / 2;
amountScrolled -= slotHeight / 2f;
} else /* if (mouseWheelDelta < 0) */ {
amountScrolled += slotHeight / 2;
amountScrolled += slotHeight / 2f;
}
}
}
@@ -56,12 +56,12 @@ public class ItemBlockHint extends ItemBlock {
"structurelib.blockhint.desc.4")); // Make sure it does not contain Air material.
break;
case 15:
aList.add(EnumChatFormatting.BLUE.toString()
aList.add(EnumChatFormatting.BLUE
+ translateToLocal("structurelib.blockhint.desc.5")); // ERROR, what did u expect?
break;
default: // WTF?
aList.add("Damn son where did you get that!?");
aList.add(EnumChatFormatting.BLUE.toString() + "From outer space... I guess...");
aList.add(EnumChatFormatting.BLUE + "From outer space... I guess...");
}
}
@@ -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,
@@ -17,7 +17,7 @@ import net.minecraft.item.ItemStack;
import org.apache.commons.lang3.tuple.Pair;
public class SetChannelDataMessage implements IMessage {
private List<Map.Entry<String, Integer>> data = new ArrayList<>();
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
public SetChannelDataMessage() {}
@@ -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,
@@ -0,0 +1,30 @@
package com.gtnewhorizon.structurelib.structure;
class ChatThrottleKey {
static class NoExplicitChannel {
private static final int hashOffset = NoExplicitChannel.class.hashCode();
private final String subchannel;
public NoExplicitChannel(String subchannel) {
this.subchannel = subchannel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NoExplicitChannel)) return false;
NoExplicitChannel that = (NoExplicitChannel) o;
return subchannel.equals(that.subchannel);
}
@Override
public int hashCode() {
// as this one will be placed within a map along with instances of different classes, it'd be best to add
// an offset based off class type to avoid collision
return subchannel.hashCode() + hashOffset;
}
}
}
@@ -11,19 +11,32 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
/**
* Represent an item source. Take only, cannot be placed back.
*/
public interface IItemSource {
/**
* We are probably not going to need to get a lot of items from this method
* We are probably not going to need to get a lot of items from this method,
* so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it
*/
@Nonnull
Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count);
/**
* Take exactly one item that matches the predicate.
*
* @return item stack, or null if none matches
*/
default ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
Map<ItemStack, Integer> take = take(predicate, simulate, 1);
return take.isEmpty() ? null : take.keySet().iterator().next();
}
/**
* Take exactly count amount of items matching predicate.
*
* @return true if at least count amount of items can be/is taken.
*/
default boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
// fast path for 1 item take requests
if (count == 1) return takeOne(predicate, simulate) != null;
@@ -33,22 +46,38 @@ public interface IItemSource {
return true;
}
/**
* Take exactly one item. ItemStack given must have a stack size of 1.
*
* @return true if found. false otherwise
*/
default boolean takeOne(ItemStack stack, boolean simulate) {
if (stack == null || stack.getItem() == null || stack.stackSize != 1) throw new IllegalArgumentException();
ItemStack took = takeOne(ItemStackPredicate.from(stack, NBTMode.EXACT), simulate);
return took == null || took.stackSize <= 0;
}
/**
* Take some item. Will not take item if it cannot find enough items to take.
*
* @return true if enough item can be/has been extracted. false otherwise
*/
default boolean takeAll(ItemStack stack, boolean simulate) {
// fast path for 1 item take requests
if (stack.stackSize == 1) return takeOne(stack, simulate);
ItemStackPredicate predicate = ItemStackPredicate.from(stack, NBTMode.EXACT);
Map<ItemStack, Integer> have = take(predicate, true, stack.stackSize);
if (have.values().stream().mapToInt(Integer::intValue).sum() < stack.stackSize) return false;
take(predicate, simulate, stack.stackSize);
if (simulate) return true;
take(predicate, false, stack.stackSize);
return true;
}
/**
* Construct an IItemSource from inventories associated with given player.
* <p>
* This will be backed by {@link InventoryUtility}
*/
static IItemSource fromPlayer(EntityPlayerMP player) {
return new IItemSource() {
@Nonnull
@@ -71,6 +100,11 @@ public interface IItemSource {
};
}
/**
* Construct an IItemSource from given inventory
* <p>
* This will be backed by {@link InventoryUtility}
*/
static IItemSource fromInventory(IInventory inv) {
return (p, s, c) -> InventoryUtility.takeFromInventory(new InventoryIterable<>(inv), p, s, c, true);
}
@@ -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,
@@ -193,7 +324,7 @@ public interface IStructureDefinition<T> {
int basePositionC,
boolean hintsOnly,
Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
if (world.isRemote ^ hintsOnly) {
if (!world.isRemote && hintsOnly) {
return false;
}
@@ -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,10 @@ public interface IStructureDefinition<T> {
return true;
}
/**
* Create a new instance of builder.
* @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;
@@ -3,7 +3,7 @@ package com.gtnewhorizon.structurelib.structure;
/**
* Implement this and use {@link StructureUtility#withContext(IStructureElement)} in case you need to record some
* temporary states that don't need to live in your main context, which would usually live for the whole lifetime of the
* structure than just during structure check. Usually just save some few bytes, but becomes non trivial if you need to
* structure than just during structure check. Usually just save some few bytes, but becomes non-trivial if you need to
* record too much stuff.
* <p>
* You will have to reset the context yourself though. The API can't guess when the old context becomes invalid.
@@ -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;
@@ -53,7 +53,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
* + is anything but air
* - is air checks
* space bar is skip
* ~ is also skip (but marks controller position, optional and logically it is a space...)
* ~ is also skip (but marks controller position, optional. implementation wise it is a space...)
* rest needs to be defined
* <p>
* next char is next block(a)
@@ -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>
@@ -28,7 +28,7 @@ item.structurelib.constructableTrigger.desc.3= (潜行右键可以直接完成
item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型
item.structurelib.constructableTrigger.desc.5=按下 §l[左SHIFT]§r 以显示子信道
item.structurelib.constructableTrigger.desc.lshift.0=包含 %d 个子信道
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待%d毫秒...
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待 %d 毫秒...
item.structurelib.constructableTrigger.gui.key=信道名:
item.structurelib.constructableTrigger.gui.value=值:
item.structurelib.constructableTrigger.gui.add=添加
@@ -39,8 +39,9 @@ item.structurelib.constructableTrigger.gui.wipe=§4全部清空
structurelib.autoplace.built_stat=已放置 %s 个方块
structurelib.autoplace.complete=§a这个多方块结构已经完成自动搭建!
structurelib.autoplace.error.not_enabled=§c这个多方块结构不支持在生存模式下自动搭建!
structurelib.autoplace.error.no_simple_block=§c建议放置§r%s§c,但没有找到它
structurelib.autoplace.error.no_item_stack=§c建议使用§r%s§c放置方块,但没有找到它
structurelib.autoplace.error.no_simple_block=§c建议放置 §r%s§c ,但没有找到它
structurelib.autoplace.error.no_item_stack=§c建议使用 §r%s§c 放置方块,但没有找到它
structurelib.autoplace.warning.no_explicit_channel=§6这个结构依赖于不存在的子信道 §r%s§6 。已使用主信道值替代。
structurelib.blockhint.desc.0=帮助你搭建机器
structurelib.blockhint.0.name=提示方块:1号