mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 15:04:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23605fc22d | ||
|
|
8ee90a7338 | ||
|
|
9bd19b9254 | ||
|
|
df4c54ea8d | ||
|
|
e71edd50b0 | ||
|
|
1b7cee7c4e | ||
|
|
03f2c31191 | ||
|
|
9b318f68fb | ||
|
|
adc91bb1dd | ||
|
|
21198c4d39 | ||
|
|
e70ea765f8 | ||
|
|
56a083ce6f | ||
|
|
7389f349db | ||
|
|
5b6b51f80c | ||
|
|
1481b68133 | ||
|
|
ebf2d6f0e1 | ||
|
|
7e7064e006 | ||
|
|
ebcf62fd97 | ||
|
|
74dde96905 | ||
|
|
d73d2bffc2 | ||
|
|
94055bbd6a |
No files matched your search
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -1,51 +1,107 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
|
||||
|
||||
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
|
||||
import java.util.*;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.culling.Frustrum;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
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;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class ClientProxy extends CommonProxy {
|
||||
|
||||
private static final short[] NO_TINT = {255, 255, 255, 0};
|
||||
private static final short[] RGBA_NO_TINT = {255, 255, 255, 255};
|
||||
private static final short[] RGBA_RED_TINT = {255, 128, 128, 0};
|
||||
private static final Map<HintParticleInfo, HintGroup> allHints = new HashMap<>();
|
||||
/**
|
||||
* All batches of hints.
|
||||
*/
|
||||
private static final List<HintGroup> allGroups = new ArrayList<>();
|
||||
|
||||
public static int HOLOGRAM_LIFETIME = 200;
|
||||
/**
|
||||
* Current batch of hints. Belongs to the same logical multiblock.
|
||||
*/
|
||||
private static HintGroup currentHints;
|
||||
/**
|
||||
* Collection of all hints. Sorted from farthest (index 0) to nearest (index size - 1)
|
||||
* <p>
|
||||
* 10k should be more than enough, if not a bit wasteful
|
||||
*/
|
||||
private static final List<HintParticleInfo> allHintsForRender = new ArrayList<>(10000);
|
||||
/**
|
||||
* If the diff to current player position is too great sort the allHints.
|
||||
* Initial value is very far off below y=0 but not huge enough to make a NaN.
|
||||
* <p>
|
||||
* We are using this supposedly immutable object as a mutable object here.
|
||||
* So be aware.
|
||||
*/
|
||||
private static final Vec3 lastPlayerPos = Vec3.createVectorHelper(0, -1e30, 0);
|
||||
/**
|
||||
* if true rebuild the allHints list from all hintOwners (and sort it)
|
||||
*/
|
||||
private static boolean allHintsDirty = false;
|
||||
/**
|
||||
* 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) {
|
||||
EntityFXBlockHint hint = new EntityFXBlockHint(w, x, y, z, icons).withColorTint(RGBa);
|
||||
Minecraft.getMinecraft().effectRenderer.addEffect(hint);
|
||||
ensureHinting();
|
||||
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
|
||||
|
||||
// check and remove colliding holograms
|
||||
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
HintParticleInfo info = new HintParticleInfo(x, y, z, currentHints);
|
||||
EntityFXBlockHint dupe = allHints.inverse().get(info);
|
||||
if (dupe != null) {
|
||||
List<EntityFXBlockHint> owner = allHints.get(dupe).owner;
|
||||
// in case someone forget to call endHinting
|
||||
if (owner != currentHints) {
|
||||
hintOwners.remove(owner);
|
||||
owner.forEach(EntityFXBlockHint::setDead);
|
||||
owner.clear();
|
||||
}
|
||||
HintGroup dupe = allHints.get(info);
|
||||
if (dupe != null && dupe != currentHints) {
|
||||
allGroups.remove(dupe);
|
||||
removeGroup(dupe);
|
||||
}
|
||||
allHints.forcePut(hint, info);
|
||||
}
|
||||
currentHints.add(hint);
|
||||
allHints.put(info, currentHints);
|
||||
currentHints.getHints().add(info);
|
||||
allHintsDirty = true;
|
||||
|
||||
EntityFX particle = new WeightlessParticleFX(
|
||||
w,
|
||||
x + RANDOM.nextFloat() * 0.5F,
|
||||
y + RANDOM.nextFloat() * 0.5F,
|
||||
z + RANDOM.nextFloat() * 0.5F,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
particle.setRBGColorF(0, 0.6F * RANDOM.nextFloat(), 0.8f);
|
||||
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,25 +111,71 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
@Override
|
||||
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
|
||||
hintParticleTinted(w, x, y, z, icons, NO_TINT);
|
||||
hintParticleTinted(w, x, y, z, icons, RGBA_NO_TINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hintParticle(World w, int x, int y, int z, Block block, int meta) {
|
||||
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), NO_TINT);
|
||||
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBA_NO_TINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) {
|
||||
if (player instanceof EntityPlayerMP) return super.updateHintParticleTint(player, w, x, y, z, rgBa);
|
||||
if (player != getCurrentPlayer()) return false; // how?
|
||||
HintParticleInfo info = new HintParticleInfo(x, y, z, null);
|
||||
EntityFXBlockHint existing = allHints.inverse().get(info);
|
||||
if (existing != null) {
|
||||
existing.withColorTint(rgBa);
|
||||
return true;
|
||||
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
|
||||
if (hint == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
hint.setTint(rgBa);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static HintParticleInfo getHintParticleInfo(World w, int x, int y, int z) {
|
||||
HintParticleInfo info = new HintParticleInfo(w, x, y, z, null, null);
|
||||
HintGroup existing = allHints.get(info);
|
||||
if (existing != null) {
|
||||
for (HintParticleInfo hint : existing.getHints()) {
|
||||
if (hint.equals(info)) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
|
||||
if (player instanceof EntityPlayerMP) return super.markHintParticleError(player, w, x, y, z);
|
||||
if (player != getCurrentPlayer()) return false; // how?
|
||||
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
|
||||
if (hint == null) {
|
||||
return false;
|
||||
}
|
||||
hint.setTint(RGBA_RED_TINT);
|
||||
hint.setRenderThrough();
|
||||
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);
|
||||
if (hintParticleInfo.renderThrough) renderThrough--;
|
||||
}
|
||||
allHintsDirty = true;
|
||||
}
|
||||
|
||||
private static IIcon[] createIIconFromBlock(Block block, int meta) {
|
||||
@@ -102,49 +204,29 @@ public class ClientProxy extends CommonProxy {
|
||||
return player == Minecraft.getMinecraft().thePlayer;
|
||||
}
|
||||
|
||||
private static final BiMap<EntityFXBlockHint, HintParticleInfo> allHints = HashBiMap.create();
|
||||
private static final List<List<EntityFXBlockHint>> hintOwners = new ArrayList<>();
|
||||
private static List<EntityFXBlockHint> currentHints;
|
||||
|
||||
public static void onHintDead(EntityFXBlockHint fx) {
|
||||
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) allHints.remove(fx);
|
||||
for (Iterator<List<EntityFXBlockHint>> iterator = hintOwners.iterator(); iterator.hasNext(); ) {
|
||||
List<EntityFXBlockHint> list = iterator.next();
|
||||
if (list.remove(fx)) {
|
||||
if (list.isEmpty()) iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
if (currentHints != null) hintOwners.add(currentHints);
|
||||
currentHints = new LinkedList<>();
|
||||
if (currentHints != null) endHinting(w);
|
||||
currentHints = new HintGroup();
|
||||
}
|
||||
|
||||
private void ensureHinting() {
|
||||
if (currentHints == null) currentHints = new LinkedList<>();
|
||||
if (currentHints == null) currentHints = new HintGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
while (!hintOwners.isEmpty() && hintOwners.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
|
||||
List<EntityFXBlockHint> list = hintOwners.remove(0);
|
||||
list.forEach(EntityFXBlockHint::setDead);
|
||||
list.clear();
|
||||
if (!w.isRemote || currentHints == null) return;
|
||||
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
|
||||
allGroups.remove(0);
|
||||
}
|
||||
if (!currentHints.isEmpty()) hintOwners.add(currentHints);
|
||||
if (!currentHints.getHints().isEmpty()) allGroups.add(currentHints);
|
||||
// we use the player existence time here as some worlds don't really advance the time ticker
|
||||
currentHints.setCreationTime(getCurrentPlayer().ticksExisted);
|
||||
currentHints = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preInit(FMLPreInitializationEvent e) {
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getOverworldTime() {
|
||||
// there is no overworld, better just hope current world time is ok...
|
||||
@@ -156,27 +238,80 @@ public class ClientProxy extends CommonProxy {
|
||||
StructureLib.net.sendToServer(new SetChannelDataMessage(trigger));
|
||||
}
|
||||
|
||||
private static class HintParticleInfo {
|
||||
private final int x, y, z;
|
||||
private final List<EntityFXBlockHint> owner;
|
||||
@Override
|
||||
public void preInit(FMLPreInitializationEvent e) {
|
||||
FMLCommonHandler.instance().bus().register(new FMLEventHandler());
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
|
||||
}
|
||||
|
||||
public HintParticleInfo(int x, int y, int z, List<EntityFXBlockHint> owner) {
|
||||
static void markTextureUsed(IIcon icon) {
|
||||
if (StructureLib.COMPAT instanceof IStructureCompat)
|
||||
((IStructureCompat) StructureLib.COMPAT).markTextureUsed(icon);
|
||||
}
|
||||
|
||||
private static class HintGroup {
|
||||
private final List<HintParticleInfo> hints = new LinkedList<>();
|
||||
private int creationTime = -1;
|
||||
|
||||
public List<HintParticleInfo> getHints() {
|
||||
return hints;
|
||||
}
|
||||
|
||||
public void setCreationTime(int creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public int getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
private boolean renderThrough;
|
||||
|
||||
private final long creationTime = System.currentTimeMillis(); // use tick time instead maybe
|
||||
|
||||
public HintParticleInfo(World w, int x, int y, int z, IIcon[] icons, short[] tint) {
|
||||
this.w = w;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.owner = owner;
|
||||
X = x + 0.25;
|
||||
Y = y + 0.25;
|
||||
Z = z + 0.25;
|
||||
this.icons = icons;
|
||||
this.tint = tint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!(o instanceof HintParticleInfo)) return false;
|
||||
|
||||
HintParticleInfo that = (HintParticleInfo) o;
|
||||
|
||||
if (x != that.x) return false;
|
||||
if (y != that.y) return false;
|
||||
return z == that.z;
|
||||
return x == that.x && y == that.y && z == that.z;
|
||||
}
|
||||
|
||||
public void setTint(short[] tint) {
|
||||
this.tint = tint;
|
||||
}
|
||||
|
||||
public void setRenderThrough() {
|
||||
if (!this.renderThrough) {
|
||||
ClientProxy.renderThrough += 1;
|
||||
allHintsDirty = true;
|
||||
}
|
||||
this.renderThrough = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,15 +321,222 @@ public class ClientProxy extends CommonProxy {
|
||||
result = 31 * result + z;
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isInFrustrum(Frustrum frustrum) {
|
||||
return frustrum.isBoxInFrustum(X, Y, Z, X + 0.5, Y + 0.5, Z + 0.5);
|
||||
}
|
||||
|
||||
public void draw(Tessellator tes, double eyeX, double eyeY, double eyeZ) {
|
||||
double size = 0.5;
|
||||
|
||||
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),
|
||||
ConfigurationHandler.INSTANCE.getHintTransparency());
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (icons[i] == null) continue;
|
||||
|
||||
markTextureUsed(icons[i]);
|
||||
|
||||
double u = icons[i].getMinU();
|
||||
double U = icons[i].getMaxU();
|
||||
double v = icons[i].getMinV();
|
||||
double V = icons[i].getMaxV();
|
||||
|
||||
// cube is a very special model. its facings can be rendered correctly by viewer distance without using
|
||||
// surface normals and view vector
|
||||
// here we do a 2 pass render.
|
||||
// first pass we draw obstructed faces (i.e. faces that are further away from player)
|
||||
// second pass we draw unobstructed faces
|
||||
for (int j = 0; j < 2; j++) {
|
||||
switch (i) { // {DOWN, UP, NORTH, SOUTH, WEST, EAST}
|
||||
case 0:
|
||||
if ((Y >= eyeY) != (j == 1)) continue;
|
||||
tes.setNormal(0, -1, 0);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
tes.addVertexWithUV(X, Y, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
break;
|
||||
case 1:
|
||||
if ((Y + size <= eyeY) != (j == 1)) continue;
|
||||
tes.setNormal(0, 1, 0);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
break;
|
||||
case 2:
|
||||
if ((Z >= eyeZ) != (j == 1)) continue;
|
||||
tes.setNormal(0, 0, -1);
|
||||
tes.addVertexWithUV(X, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, u, V);
|
||||
break;
|
||||
case 3:
|
||||
if ((Z <= eyeZ) != (j == 1)) continue;
|
||||
tes.setNormal(0, 0, 1);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
break;
|
||||
case 4:
|
||||
if ((X >= eyeX) != (j == 1)) continue;
|
||||
tes.setNormal(-1, 0, 0);
|
||||
tes.addVertexWithUV(X, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z, u, V);
|
||||
break;
|
||||
case 5:
|
||||
if ((X + size <= eyeX) != (j == 1)) continue;
|
||||
tes.setNormal(1, 0, 0);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public double getSquareDistanceTo(Vec3 point) {
|
||||
return point.squareDistanceTo(X, Y, Z);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FMLEventHandler {
|
||||
private void resetPlayerLocation() {
|
||||
lastPlayerPos.xCoord = Minecraft.getMinecraft().thePlayer.posX;
|
||||
lastPlayerPos.yCoord = Minecraft.getMinecraft().thePlayer.posY;
|
||||
lastPlayerPos.zCoord = Minecraft.getMinecraft().thePlayer.posZ;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientTick(ClientTickEvent e) {
|
||||
if (e.phase == Phase.END && Minecraft.getMinecraft().thePlayer != null) {
|
||||
Vec3 playerPos = Minecraft.getMinecraft().thePlayer.getPosition(1);
|
||||
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
|
||||
- ConfigurationHandler.INSTANCE.getHintLifespan();
|
||||
int i;
|
||||
for (i = 0; i < allGroups.size(); i++) {
|
||||
if (allGroups.get(i).getCreationTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != 0) {
|
||||
sortRequired = true;
|
||||
// clear this block and go no further
|
||||
List<HintGroup> toRemove = allGroups.subList(0, i);
|
||||
toRemove.forEach(ClientProxy::removeGroup);
|
||||
toRemove.clear();
|
||||
}
|
||||
if (allHintsDirty) {
|
||||
allHintsForRender.clear();
|
||||
// we need greatly efficient sequential access and mildly efficient sorting
|
||||
// rebuild is sort of rare, and it's expected to lag anyway
|
||||
// so no min-heap here
|
||||
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
|
||||
sortRequired = true;
|
||||
}
|
||||
if (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2) {
|
||||
// only redo sort if player moved some distance
|
||||
// default is 0.1 block
|
||||
// if there was a full rebuild, go sort it as well
|
||||
allHintsForRender.sort(Comparator.comparingDouble(info -> -info.getSquareDistanceTo(playerPos)));
|
||||
resetPlayerLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForgeEventHandler {
|
||||
@SubscribeEvent
|
||||
public void onWorldLoad(WorldEvent.Load e) {
|
||||
if (e.world.isRemote) {
|
||||
allHints.clear();
|
||||
hintOwners.clear();
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderWorldLast(RenderWorldLastEvent e) {
|
||||
if (allHintsForRender.isEmpty()) return;
|
||||
|
||||
// seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here.
|
||||
// Please consider contributing patches if you find something to optimize.
|
||||
|
||||
Profiler p = Minecraft.getMinecraft().mcProfiler;
|
||||
|
||||
p.startSection("HintParticle");
|
||||
p.startSection("Prepare");
|
||||
Frustrum frustrum = new Frustrum();
|
||||
Entity entitylivingbase = Minecraft.getMinecraft().renderViewEntity;
|
||||
double d0 = entitylivingbase.lastTickPosX
|
||||
+ (entitylivingbase.posX - entitylivingbase.lastTickPosX) * e.partialTicks;
|
||||
double d1 = entitylivingbase.lastTickPosY
|
||||
+ (entitylivingbase.posY - entitylivingbase.lastTickPosY) * e.partialTicks;
|
||||
double d2 = entitylivingbase.lastTickPosZ
|
||||
+ (entitylivingbase.posZ - entitylivingbase.lastTickPosZ) * e.partialTicks;
|
||||
frustrum.setPosition(d0, d1, d2);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT); // TODO figure out original states
|
||||
// we need the back facing rendered because the thing is transparent
|
||||
GL11.glDisable(GL11.GL_CULL_FACE);
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glEnable(GL11.GL_BLEND); // enable blend so it is transparent
|
||||
GL11.glBlendFunc(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
// depth test begin as enabled
|
||||
boolean renderThrough = false;
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
|
||||
GL11.glTranslated(-RenderManager.renderPosX, -RenderManager.renderPosY, -RenderManager.renderPosZ);
|
||||
Tessellator tes = Tessellator.instance;
|
||||
tes.startDrawingQuads();
|
||||
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(); i < allHintsForRenderSize; i++) {
|
||||
HintParticleInfo hint = allHintsForRender.get(i);
|
||||
if (!hint.isInFrustrum(frustrum)) continue;
|
||||
if (renderThrough != hint.renderThrough) {
|
||||
if (i > 0) {
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
tes.startDrawingQuads();
|
||||
p.endStartSection("Prepare");
|
||||
}
|
||||
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
else GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
renderThrough = hint.renderThrough;
|
||||
}
|
||||
// TODO verify if we need to add eyeHeight
|
||||
hint.draw(tes, d0, d1, d2);
|
||||
}
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
p.endSection();
|
||||
|
||||
GL11.glPopAttrib();
|
||||
GL11.glPopMatrix();
|
||||
p.endSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
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;
|
||||
|
||||
@@ -51,4 +56,47 @@ public class CommonProxy {
|
||||
}
|
||||
|
||||
public void uploadChannels(ItemStack trigger) {}
|
||||
|
||||
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
|
||||
if (player instanceof EntityPlayerMP) { // just in case
|
||||
StructureLib.net.sendTo(new ErrorHintParticleMessage(x, (short) y, z), (EntityPlayerMP) player);
|
||||
return true;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
public interface IStructureCompat {
|
||||
void markTextureUsed(IIcon o);
|
||||
}
|
||||
@@ -6,9 +6,11 @@ import com.gtnewhorizon.structurelib.item.ItemBlockHint;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
|
||||
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
|
||||
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.util.XSTR;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
@@ -37,16 +39,17 @@ import org.apache.logging.log4j.Logger;
|
||||
acceptableRemoteVersions = "*",
|
||||
guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
|
||||
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(
|
||||
@@ -54,6 +57,7 @@ public class StructureLib {
|
||||
net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT);
|
||||
net.registerMessage(UpdateHintParticleMessage.Handler.class, UpdateHintParticleMessage.class, 2, Side.CLIENT);
|
||||
net.registerMessage(SetChannelDataMessage.Handler.class, SetChannelDataMessage.class, 3, Side.SERVER);
|
||||
net.registerMessage(ErrorHintParticleMessage.Handler.class, ErrorHintParticleMessage.class, 4, Side.CLIENT);
|
||||
|
||||
try {
|
||||
DEBUG_MODE = Boolean.parseBoolean(System.getProperty("structurelib.debug"));
|
||||
@@ -69,6 +73,8 @@ public class StructureLib {
|
||||
@Mod.Instance
|
||||
static StructureLib INSTANCE;
|
||||
|
||||
static Object COMPAT;
|
||||
|
||||
static Block blockHint;
|
||||
static Item itemBlockHint;
|
||||
static Item itemFrontRotationTool;
|
||||
@@ -93,6 +99,13 @@ public class StructureLib {
|
||||
itemConstructableTrigger.getUnlocalizedName());
|
||||
proxy.preInit(e);
|
||||
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
|
||||
|
||||
if (Loader.isModLoaded(STRUCTURECOMPAT_MODID)) {
|
||||
COMPAT = Loader.instance()
|
||||
.getIndexedModList()
|
||||
.get(STRUCTURECOMPAT_MODID)
|
||||
.getMod();
|
||||
}
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
|
||||
@@ -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,24 +57,74 @@ 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.
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -80,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
|
||||
*/
|
||||
@@ -92,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
|
||||
*/
|
||||
@@ -101,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
|
||||
*/
|
||||
@@ -110,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
|
||||
*/
|
||||
@@ -119,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
|
||||
*/
|
||||
@@ -126,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;
|
||||
}
|
||||
@@ -145,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
|
||||
|
||||
+87
-10
@@ -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;
|
||||
+2
-2
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.gtnewhorizon.structurelib.entity.fx;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ClientProxy;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class EntityFXBlockHint extends EntityFX {
|
||||
private IIcon[] icons;
|
||||
private short[] mRGBa = {255, 255, 255, 0};
|
||||
|
||||
public EntityFXBlockHint(World world) {
|
||||
this(world, 0, 0, 0, Blocks.stone, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param world
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @param icons DOWN, UP, NORTH, SOUTH, WEST, EAST
|
||||
*/
|
||||
public EntityFXBlockHint(World world, int x, int y, int z, IIcon[] icons) {
|
||||
super(world, x + .25, y + .5, z + .25);
|
||||
particleGravity = 0;
|
||||
prevPosX = posX;
|
||||
prevPosY = posY;
|
||||
prevPosZ = posZ;
|
||||
noClip = true;
|
||||
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
|
||||
this.icons = icons;
|
||||
}
|
||||
|
||||
public EntityFXBlockHint(World world, int x, int y, int z, Block block, int meta) {
|
||||
super(world, x + .25, y + .5, z + .25);
|
||||
particleGravity = 0;
|
||||
prevPosX = posX;
|
||||
prevPosY = posY;
|
||||
prevPosZ = posZ;
|
||||
noClip = true;
|
||||
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
|
||||
icons = new IIcon[6];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
icons[i] = block.getIcon(i, meta);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityFXBlockHint withColorTint(short[] coloure) {
|
||||
this.mRGBa = coloure;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderParticle(
|
||||
Tessellator tes,
|
||||
float subTickTime,
|
||||
float p_70539_3_,
|
||||
float p_70539_4_,
|
||||
float p_70539_5_,
|
||||
float p_70539_6_,
|
||||
float p_70539_7_) {
|
||||
float size = .5f;
|
||||
float X = (float) (prevPosX + (posX - prevPosX) * (double) subTickTime - EntityFX.interpPosX);
|
||||
float Y = (float) (prevPosY + (posY - prevPosY) * (double) subTickTime - EntityFX.interpPosY) - size / 2;
|
||||
float Z = (float) (prevPosZ + (posZ - prevPosZ) * (double) subTickTime - EntityFX.interpPosZ);
|
||||
GL11.glDisable(GL11.GL_CULL_FACE);
|
||||
GL11.glDepthMask(false);
|
||||
tes.setColorRGBA((int) (mRGBa[0] * .9F), (int) (mRGBa[1] * .95F), (int) (mRGBa[2] * 1F), 192);
|
||||
|
||||
// var8, var9 - X U
|
||||
// var 10, var 11 - Y V
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (icons[i] == null) {
|
||||
continue;
|
||||
}
|
||||
double u = icons[i].getMinU();
|
||||
double U = icons[i].getMaxU();
|
||||
double v = icons[i].getMinV();
|
||||
double V = icons[i].getMaxV();
|
||||
switch (i) { // {DOWN, UP, NORTH, SOUTH, WEST, EAST}
|
||||
case 0:
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
tes.addVertexWithUV(X, Y, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
break;
|
||||
case 1:
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
break;
|
||||
case 2:
|
||||
tes.addVertexWithUV(X, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, u, V);
|
||||
break;
|
||||
case 3:
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
break;
|
||||
case 4:
|
||||
tes.addVertexWithUV(X, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z, u, V);
|
||||
break;
|
||||
case 5:
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
|
||||
break;
|
||||
}
|
||||
}
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glDepthMask(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFXLayer() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
return pass == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDead() {
|
||||
super.setDead();
|
||||
ClientProxy.onHintDead(this);
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,8 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
private void updateButtons() {
|
||||
// this will be called from setText of key and value. NEVER UPDATE THE VALUE OF THESE HERE OR GET A
|
||||
// STACKOVERFLOW!
|
||||
boolean existing = ChannelDataAccessor.hasSubChannel(trigger, key.getText());
|
||||
String keyText = key.getText();
|
||||
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
|
||||
getButtonList().get(0).displayString = existing
|
||||
? I18n.format("item.structurelib.constructableTrigger.gui.set")
|
||||
: I18n.format("item.structurelib.constructableTrigger.gui.add");
|
||||
|
||||
@@ -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...");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.gtnewhorizon.structurelib.net;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import cpw.mods.fml.common.network.simpleimpl.IMessage;
|
||||
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
|
||||
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ErrorHintParticleMessage implements IMessage {
|
||||
private int x;
|
||||
private short y;
|
||||
private int z;
|
||||
|
||||
public ErrorHintParticleMessage() {}
|
||||
|
||||
public ErrorHintParticleMessage(int x, short y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
x = buf.readInt();
|
||||
y = buf.readShort();
|
||||
z = buf.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
buf.writeInt(x);
|
||||
buf.writeShort(y);
|
||||
buf.writeInt(z);
|
||||
}
|
||||
|
||||
public static class Handler implements IMessageHandler<ErrorHintParticleMessage, IMessage> {
|
||||
@Override
|
||||
public IMessage onMessage(ErrorHintParticleMessage msg, MessageContext ctx) {
|
||||
boolean updateResult = StructureLibAPI.markHintParticleError(
|
||||
StructureLib.getCurrentPlayer(), StructureLib.getCurrentPlayer().worldObj, msg.x, msg.y, msg.z);
|
||||
if (StructureLibAPI.isDebugEnabled())
|
||||
LOGGER.debug(
|
||||
"Server instructed to mark hint particle at ({}, {}, {}) error, result {}!",
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z,
|
||||
updateResult);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IStructureDefinition<?>>} if you have a template for subclass.
|
||||
* You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for simpler cases, but it is
|
||||
* slightly less performant as it will allocate new objects on each structure check, whereas {@link ClassValue} will
|
||||
* allocate once and cache it onwards.
|
||||
* <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());
|
||||
@@ -97,6 +103,7 @@ public interface IStructureElement<T> {
|
||||
/**
|
||||
* This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or
|
||||
* some other unforeseen situations.
|
||||
* TODO this definition doesn't seem right. Should we separate SKIP from EXISTS, or SKIP from ERROR?
|
||||
*/
|
||||
SKIP,
|
||||
/**
|
||||
@@ -114,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
+1
-1
@@ -37,7 +37,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
|
||||
if (check) element.check(object, world, x, y, z);
|
||||
return ++built < elementBudget;
|
||||
case REJECT:
|
||||
StructureLibAPI.updateHintParticleTint(actor, world, x, y, z, new short[] {255, 128, 128, 0});
|
||||
StructureLibAPI.markHintParticleError(actor, world, x, y, z);
|
||||
return false;
|
||||
case ACCEPT_STOP:
|
||||
if (check) element.check(object, world, x, y, z);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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<T> implements IStructureElement<T> {
|
||||
//...
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(
|
||||
Object o,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer chatter) {
|
||||
if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
|
||||
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
||||
ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
|
||||
if (taken == null) return PlaceResult.REJECT;
|
||||
return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -41,7 +41,7 @@ structurelib.autoplace.complete=§aThis multiblock has completed!
|
||||
structurelib.autoplace.error.not_enabled=§cThis multiblock does not support autoplace in survival!
|
||||
structurelib.autoplace.error.no_simple_block=§cSuggested to place §r%s§c but none was found
|
||||
structurelib.autoplace.error.no_item_stack=§cSuggested to place block by using §r%s§c but none was found
|
||||
structurelib.autoplace.warning.no_explicit_channel=§6This structure depends on nonexistent sub channel §r%s§c. Using master channel instead.
|
||||
structurelib.autoplace.warning.no_explicit_channel=§6This structure depends on nonexistent sub channel §r%s§6. Using master channel instead.
|
||||
|
||||
structurelib.blockhint.desc.0=Helps while building
|
||||
structurelib.blockhint.0.name=Hint 1 dot
|
||||
|
||||
@@ -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号
|
||||
|
||||
Reference in new issue
Block a user