Compare commits

...
24 Commits
Author SHA1 Message Date
Glease 03f2c31191 make hologram lifespan and transparency level configurable. 2022-09-02 16:44:12 +08:00
Glease 9b318f68fb throttle no explicit subchannel warning 2022-09-02 15:45:13 +08:00
Glease adc91bb1dd Add javadoc for APIs (#10) 2022-08-30 23:27:18 +08:00
Glease 21198c4d39 Create pages.yml 2022-08-29 12:12:19 +08:00
Glease e70ea765f8 spotless 2022-08-29 00:49:40 +08:00
Glease 56a083ce6f ofBlocksTiered bug fix 2022-08-29 00:37:50 +08:00
Glease 7389f349db add compat to hodgepodge
turns out we just cannot call render block here since our blocks are not 1x1x1 and I definitely don't want to do one draw call for every hint particle
2022-08-25 16:09:16 +08:00
Glease 5b6b51f80c use correct brightness 2022-08-25 16:07:42 +08:00
Glease 1481b68133 use profiler 2022-08-25 15:01:42 +08:00
Glease ebf2d6f0e1 try to reduce sorting 2022-08-24 19:43:10 +08:00
Glease 7e7064e006 add some todo... 2022-08-24 12:36:48 +08:00
Glease ebcf62fd97 allow some particle to render through walls 2022-08-24 12:23:56 +08:00
Glease 74dde96905 redo holograms render to decouple it from particle system 2022-08-24 12:23:56 +08:00
Glease d73d2bffc2 provide api to mark particle as error
we will probably need to have this render through the walls, but that's a task for later
2022-08-24 11:28:26 +08:00
Glease 94055bbd6a fix color 2022-08-24 10:40:14 +08:00
Glease dc1b1fc4db spotless 2022-08-23 21:33:25 +08:00
Glease fd38dfc31f add tab, arrow keys and enter handling to make this nicer 2022-08-23 20:22:38 +08:00
Glease cb82b73e78 add some warning if subchannel isn't set explicitly 2022-08-23 19:54:36 +08:00
Glease 91d4a8022e turn on debug in dev env by default 2022-08-23 18:57:17 +08:00
Glease a12722c4b0 configurable autoplace 2022-08-23 18:56:47 +08:00
Glease b2624a3d36 unconditionally mark unsatisfied hint as red
I don't think anyone would turn this off...
2022-08-23 16:59:51 +08:00
Glease b34e901a93 channels 2022-08-23 16:55:17 +08:00
Glease e155c69b20 fix incomplete creative build 2022-08-20 19:27:33 +08:00
Glease 82622d1e15 this is nbt sensitive 2022-08-20 19:27:05 +08:00
40 changed files with 3554 additions and 330 deletions

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["gh-pages"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Generate Index
shell: bash
run: |
find . -name javadoc -type d -print > index.txt
echo feature/doc >> index.txt
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
+64
View File
@@ -0,0 +1,64 @@
name: Deploy Javadoc
on:
push:
branches:
- master
- feature/doc
tags:
- '*'
permissions:
contents: write
pages: write
id-token: write
jobs:
javadoc:
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
result_hash: ${{ steps.deploy.outputs.hash }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v2
with:
java-version: 8
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Generate Javadoc
run: ./gradlew --no-daemon spotlessCheck javadoc
- name: Deploy 🚀
id: deploy
shell: bash
run: |
mv build/docs $RUNNER_TEMP/docs_output
rm build -rf
git checkout gh-pages
mkdir -p $TARGET_FOLDER
rm -rf $TARGET_FOLDER
mv $RUNNER_TEMP/docs_output $TARGET_FOLDER -v
git add $TARGET_FOLDER
if git diff --cached --exit-code > /dev/null ; then
echo Nothing to deploy
exit 0
fi
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Deploy javadoc from ref $TARGET_FOLDER"
git push
echo "::set-output name=hash::$(git rev-parse HEAD)"
env:
TARGET_FOLDER: ${{ github.ref_name }}
COMMIT_AUTHOR: ${{ github.pusher.name }}
COMMIT_EMAIL: ${{ github.pusher.email }}
+24
View File
@@ -0,0 +1,24 @@
javadoc {
inputs.file "src/main/javadoc/overview.html"
include "com/gtnewhorizon/structurelib/alignment/**"
include "com/gtnewhorizon/structurelib/constructable/**"
include "com/gtnewhorizon/structurelib/structure/**"
include "com/gtnewhorizon/structurelib/util/**"
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
options {
overview "src/main/javadoc/overview.html"
links 'https://docs.oracle.com/javase/8/docs/api/'
links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/'
}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
}
+8 -2
View File
@@ -57,7 +57,7 @@ plugins {
id 'com.palantir.git-version' version '0.13.0' apply false
id 'de.undercouch.download' version '5.0.1'
id 'com.github.gmazzo.buildconfig' version '3.0.3' apply false
id "com.diffplug.spotless" version "6.7.2"
id 'com.diffplug.spotless' version '6.7.2' apply false
}
verifySettingsGradle()
@@ -90,7 +90,13 @@ idea {
downloadSources = true
}
}
apply from: Blowdryer.file('spotless.gradle')
boolean disableSpotless = project.hasProperty("disableSpotless") ? project.disableSpotless.toBoolean() : false
if (!disableSpotless) {
apply plugin: 'com.diffplug.spotless'
apply from: Blowdryer.file('spotless.gradle')
}
if(JavaVersion.current() != JavaVersion.VERSION_1_8) {
throw new GradleException("This project requires Java 8, but it's running on " + JavaVersion.current())
+1
View File
@@ -2,4 +2,5 @@
dependencies {
compile("com.github.GTNewHorizons:GTNHLib:0.0.1:dev")
runtime("com.github.GTNewHorizons:NotEnoughItems:2.2.20-GTNH:dev")
}
@@ -1,49 +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
@@ -53,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) {
@@ -100,76 +204,110 @@ 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...
return Minecraft.getMinecraft().theWorld.getTotalWorldTime();
}
private static class HintParticleInfo {
private final int x, y, z;
private final List<EntityFXBlockHint> owner;
@Override
public void uploadChannels(ItemStack trigger) {
StructureLib.net.sendToServer(new SetChannelDataMessage(trigger));
}
public HintParticleInfo(int x, int y, int z, List<EntityFXBlockHint> owner) {
@Override
public void preInit(FMLPreInitializationEvent e) {
FMLCommonHandler.instance().bus().register(new FMLEventHandler());
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
}
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;
private final int x, y, z;
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
@@ -179,15 +317,201 @@ public class ClientProxy extends CommonProxy {
result = 31 * result + z;
return result;
}
public boolean isInFrustrum(Frustrum frustrum) {
return frustrum.isBoxInFrustum(x, y, z, X, Y, Z);
}
public void draw(Tessellator tes) {
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();
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;
}
}
}
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 (renderThrough > 0 && (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2)) {
// only sort if there is anything that need depth test disabled
// 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.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;
}
hint.draw(tes);
}
p.endStartSection("Draw");
tes.draw();
p.endSection();
GL11.glPopAttrib();
GL11.glPopMatrix();
p.endSection();
}
}
}
@@ -1,11 +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;
@@ -48,4 +54,49 @@ public class CommonProxy {
public long getOverworldTime() {
return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime();
}
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,10 @@ public enum ConfigurationHandler {
private Configuration config;
private int maxCoexistingHologram;
private boolean removeCollidingHologram;
private int hintLifespan;
private int hintTransparency;
private int autoPlaceBudget;
private int autoPlaceInterval;
ConfigurationHandler() {
FMLCommonHandler.instance().bus().register(this);
@@ -50,6 +54,34 @@ 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",
25,
1,
200,
"Max number of elements can be placed in one round of auto place.\n"
+ "As expected, server side settings will overrides client settings.\n"
+ "Certain larger multi might increase these values beyond this configured value.");
autoPlaceInterval = config.getInt(
"autoPlaceInterval",
"common.hologram",
300,
0,
20000,
"Unit: milisecond. 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.");
if (config.hasChanged()) {
config.save();
@@ -71,6 +103,22 @@ public enum ConfigurationHandler {
return removeCollidingHologram;
}
public int getHintLifespan() {
return hintLifespan;
}
public int getHintTransparency() {
return hintTransparency;
}
public int getAutoPlaceBudget() {
return autoPlaceBudget;
}
public int getAutoPlaceInterval() {
return autoPlaceInterval;
}
Configuration getConfig() {
return config;
}
@@ -0,0 +1,51 @@
package com.gtnewhorizon.structurelib;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GLContext;
class GLHelper {
public static final boolean vboSupported;
private static final boolean arbVbo;
static {
ContextCapabilities contextcapabilities = GLContext.getCapabilities();
arbVbo = !contextcapabilities.OpenGL15 && contextcapabilities.GL_ARB_vertex_buffer_object;
vboSupported = contextcapabilities.OpenGL15 || arbVbo;
}
public static int glGenBuffers() {
return arbVbo ? ARBVertexBufferObject.glGenBuffersARB() : GL15.glGenBuffers();
}
public static void glBindBuffer(int target, int buffer) {
if (arbVbo) {
ARBVertexBufferObject.glBindBufferARB(target, buffer);
} else {
GL15.glBindBuffer(target, buffer);
}
}
public static void glBufferData(int target, ByteBuffer data, int usage) {
if (arbVbo) {
ARBVertexBufferObject.glBufferDataARB(target, data, usage);
} else {
GL15.glBufferData(target, data, usage);
}
}
public static void glDeleteBuffers(int buffer) {
if (arbVbo) {
ARBVertexBufferObject.glDeleteBuffersARB(buffer);
} else {
GL15.glDeleteBuffers(buffer);
}
}
public static boolean useVbo() {
return vboSupported;
}
}
@@ -0,0 +1,23 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.gui.GuiScreenConfigureChannels;
import cpw.mods.fml.common.network.IGuiHandler;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
public class GuiHandler implements IGuiHandler {
@Override
public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
return null;
}
@Override
public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
switch (ID) {
case 0:
return new GuiScreenConfigureChannels(player.inventory.getStackInSlot(x));
default:
return null;
}
}
}
@@ -0,0 +1,7 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.util.IIcon;
public interface IStructureCompat {
void markTextureUsed(IIcon o);
}
@@ -1,15 +1,20 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.block.BlockHint;
import com.gtnewhorizon.structurelib.command.CommandConfigureChannels;
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;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.common.registry.GameRegistry;
@@ -20,6 +25,7 @@ import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -33,7 +39,8 @@ import org.apache.logging.log4j.Logger;
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
public class StructureLib {
public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug");
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");
@@ -49,10 +56,25 @@ public class StructureLib {
AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER);
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"));
} catch (IllegalArgumentException | NullPointerException e) {
// turn on debug by default in dev mode
// this will be overridden if above property is present and set to false
DEBUG_MODE = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
}
}
public static final XSTR RANDOM = new XSTR();
@Mod.Instance
static StructureLib INSTANCE;
static Object COMPAT;
static Block blockHint;
static Item itemBlockHint;
static Item itemFrontRotationTool;
@@ -76,6 +98,19 @@ public class StructureLib {
itemConstructableTrigger = new ItemConstructableTrigger(),
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
public void serverStarting(FMLServerStartingEvent e) {
e.registerServerCommand(new CommandConfigureChannels());
}
public static void addClientSideChatMessages(String... messages) {
@@ -93,4 +128,12 @@ public class StructureLib {
public static long getOverworldTime() {
return proxy.getOverworldTime();
}
public static StructureLib instance() {
return INSTANCE;
}
public CommonProxy proxy() {
return proxy;
}
}
@@ -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);
}
}
@@ -0,0 +1,184 @@
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.StructureLibAPI;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.stream.Stream;
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 (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)
|| !masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.hasKey(channel, NBT.TAG_INT)) return masterStack;
ItemStack ret = new ItemStack(
masterStack.getItem(),
masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel),
Items.feather.getDamage(masterStack));
ret.setTagCompound(masterStack.stackTagCompound);
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 (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()
&& masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
&& masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.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 (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)
|| !masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.hasKey(channel, NBT.TAG_INT)) return masterStack.stackSize;
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 (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();
NBTTagCompound main = masterStack.stackTagCompound;
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
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 (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;
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
NBTTagCompound tag = main.getCompoundTag(SECONDARY_HINT_TAG);
tag.removeTag(channel);
if (tag.hasNoTags()) main.removeTag(SECONDARY_HINT_TAG);
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);
return tag.func_150296_c().size();
}
}
@@ -1,14 +1,15 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import com.gtnewhorizon.structurelib.ConfigurationHandler;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import java.util.WeakHashMap;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.world.World;
@@ -17,10 +18,8 @@ import net.minecraftforge.common.util.ForgeDirection;
public class ConstructableUtility {
// TODO make these configurable and expose this to external world
public static final int COOLDOWN = 5;
private static final int BUDGET = 25;
public static boolean redBrokenOne = true; // TODO this is horrible
private static final WeakHashMap<EntityPlayerMP, Long> lastUse = new WeakHashMap<>();
private static long clientSideLastUse = 0;
private ConstructableUtility() {}
@@ -42,10 +41,11 @@ public class ConstructableUtility {
// not sneaking - client side will generate hologram. nothing to do on server side
if (!aPlayer.isSneaking()) return true;
long timePast = StructureLib.getOverworldTime() - getLastUseTick(aStack);
if (timePast < COOLDOWN) {
long timePast = System.currentTimeMillis() - getLastUseMilis(aPlayer);
if (timePast < ConfigurationHandler.INSTANCE.getAutoPlaceInterval()) {
aPlayer.addChatComponentMessage(new ChatComponentTranslation(
"item.structurelib.constructableTrigger.too_fast", COOLDOWN - timePast));
"item.structurelib.constructableTrigger.too_fast",
ConfigurationHandler.INSTANCE.getAutoPlaceInterval() - timePast));
return true;
}
} else if (!StructureLib.isCurrentPlayer(aPlayer)) {
@@ -75,13 +75,17 @@ public class ConstructableUtility {
constructable.construct(aStack, false);
} else if (constructable instanceof ISurvivalConstructable) {
int built = ((ISurvivalConstructable) constructable)
.survivalConstruct(aStack, BUDGET, IItemSource.fromPlayer(playerMP), playerMP);
.survivalConstruct(
aStack,
ConfigurationHandler.INSTANCE.getAutoPlaceBudget(),
IItemSource.fromPlayer(playerMP),
playerMP);
if (built > 0) {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.built_stat", built));
} else if (built == -1) {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.complete"));
}
setLastUseTickToStackTag(aStack);
setLastUseMilis(aPlayer);
} else {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.error.not_enabled"));
}
@@ -90,19 +94,23 @@ public class ConstructableUtility {
// client side
// particles and text
constructable.construct(aStack, true);
if (getLastUseTick(aStack) == 0)
if (System.currentTimeMillis() - getLastUseMilis(aPlayer) >= 300)
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
setLastUseMilis(aPlayer);
return false;
}
private static long getLastUseTick(ItemStack aStack) {
return aStack.hasTagCompound() ? aStack.getTagCompound().getLong("LastUse") : 0;
private static void setLastUseMilis(EntityPlayer aPlayer) {
if (!(aPlayer instanceof EntityPlayerMP))
// assume client side
clientSideLastUse = System.currentTimeMillis();
else lastUse.put((EntityPlayerMP) aPlayer, System.currentTimeMillis());
}
private static void setLastUseTickToStackTag(ItemStack aStack) {
NBTTagCompound tag = aStack.stackTagCompound;
if (tag == null) tag = aStack.stackTagCompound = new NBTTagCompound();
// here we force use the overworld tick time, in case the current world is over
tag.setLong("LastUse", StructureLib.getOverworldTime());
private static long getLastUseMilis(EntityPlayer aPlayer) {
if (!(aPlayer instanceof EntityPlayerMP))
// assume client side
return clientSideLastUse;
return lastUse.getOrDefault(aPlayer, 0L);
}
}
@@ -0,0 +1,4 @@
/**
* This package contains interfaces and utility methods for autobuilding a structure.
*/
package com.gtnewhorizon.structurelib.alignment.constructable;
@@ -335,7 +335,7 @@ public enum ExtendedFacing {
/**
* Translates relative to front facing offset to world offset
*
* @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B)
* @param abcOffset A,B,C offset (facing relative {@code L-->R,U-->D,F-->B})
* @return X, Y, Z offset in world
*/
public Vec3 getWorldOffset(Vec3 abcOffset) {
@@ -358,7 +358,7 @@ public enum ExtendedFacing {
* Translates world offset to relative front facing offset
*
* @param xyzOffset X,Y,Z offset in world
* @return A, B, C offset (facing relative L-->R,U-->D,F-->B)
* @return A, B, C offset (facing relative {@code L-->R,U-->D,F-->B})
*/
public Vec3 getOffsetABC(Vec3 xyzOffset) {
return integerAxisSwap.translate(xyzOffset);
@@ -1,4 +1,6 @@
/**
* This package provide a framework for rotating a given block, or a multiblock structure.
* <p>
* All public stuff in this package and its subpackages are considered part of the public API.
* Backwards compatibility is maintained to the maximum extend possible.
*/
@@ -0,0 +1,121 @@
package com.gtnewhorizon.structurelib.command;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
public class CommandConfigureChannels extends CommandBase {
@Override
public String getCommandName() {
return "sl_channel";
}
@Override
public String getCommandUsage(ICommandSender p_71518_1_) {
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
+ "/sl_channel <set> <channel name> <value>\n"
+ "<channel name> can have space in it. <value> must be positive integer";
}
@Override
public int getRequiredPermissionLevel() {
return 0;
}
@Override
public List<?> addTabCompletionOptions(ICommandSender p_71516_1_, String[] p_71516_2_) {
if (p_71516_2_.length == 1)
return getListOfStringsMatchingLastWord(p_71516_2_, "get", "set", "unset", "wipe", "getall");
return null;
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
if (args.length == 0) throw new WrongUsageException(getCommandUsage(sender));
if (!(sender instanceof EntityPlayerMP)) throw new WrongUsageException("must be a player");
EntityPlayerMP player = (EntityPlayerMP) sender;
ItemStack heldItem = player.getHeldItem();
if (heldItem == null || !(heldItem.getItem() instanceof ItemConstructableTrigger)) {
throw new WrongUsageException("must hold a hologram");
}
String channel;
switch (args[0]) {
case "getall":
if (!ChannelDataAccessor.hasSubChannel(heldItem)) {
sender.addChatMessage(new ChatComponentText("No subchannel"));
} else {
sender.addChatMessage(new ChatComponentTranslation(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(heldItem)));
ChannelDataAccessor.iterateChannelData(heldItem)
.map(e -> e.getKey() + ": " + e.getValue())
.map(ChatComponentText::new)
.forEach(player::addChatMessage);
}
return;
case "wipe":
ChannelDataAccessor.wipeChannelData(heldItem);
return;
case "get":
case "unset":
if (args.length < 2) throw new WrongUsageException(getCommandUsage(sender));
channel =
args.length == 2 ? args[1] : Arrays.stream(args).skip(1).collect(Collectors.joining());
break;
case "set":
if (args.length < 3) throw new WrongUsageException(getCommandUsage(sender));
channel = args.length == 3
? args[1]
: Arrays.stream(args).skip(1).limit(args.length - 2).collect(Collectors.joining());
break;
default:
throw new WrongUsageException(getCommandUsage(sender));
}
switch (args[0]) {
case "get":
if (ChannelDataAccessor.hasSubChannel(heldItem, channel))
sender.addChatMessage(new ChatComponentText(
channel + " value: " + ChannelDataAccessor.getChannelData(heldItem, channel)));
else sender.addChatMessage(new ChatComponentText(channel + " value: N/A"));
break;
case "clear":
if (ChannelDataAccessor.hasSubChannel(heldItem, channel))
sender.addChatMessage(new ChatComponentText(channel + " no value"));
else {
ChannelDataAccessor.unsetChannelData(heldItem, channel);
sender.addChatMessage(new ChatComponentText(channel + " cleared"));
player.inventoryContainer.detectAndSendChanges();
}
break;
case "set":
int value;
try {
value = Integer.parseInt(args[args.length - 1]);
} catch (NumberFormatException e) {
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
}
if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
ChannelDataAccessor.setChannelData(heldItem, channel, value);
player.inventoryContainer.detectAndSendChanges();
sender.addChatMessage(new ChatComponentText(channel + " value: " + value));
break;
}
}
}
@@ -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);
}
}
@@ -0,0 +1,400 @@
package com.gtnewhorizon.structurelib.gui;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.gui.GuiScrollableList.IGuiScreen;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen {
private static final int KEY_MAX_WIDTH = 50;
private final ItemStack trigger;
private final GuiChannelsList list;
private GuiTextField key, value;
protected int guiTop, guiLeft;
public GuiScreenConfigureChannels(ItemStack trigger) {
this.trigger = trigger;
list = new GuiChannelsList(152, 100, 12, 12, 14);
list.addSelectionListener((list, selectedIndex) -> {
Entry<String, Integer> e = list.getElementAt(selectedIndex);
key.setText(e.getKey());
value.setText(e.getValue().toString());
updateButtons();
});
}
@Override
public void initGui() {
super.initGui();
// so you can keep holding backspace to delete a long sequence of chars
Keyboard.enableRepeatEvents(true);
guiLeft = (this.width - this.getXSize()) / 2;
guiTop = (this.height - this.getYSize()) / 2;
key = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 119, 151 + 12 - 45, 12) {
@Override
public void writeText(String text) {
// force lower case
super.writeText(text.toLowerCase(Locale.ROOT));
updateButtons();
}
@Override
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
boolean flag = p_146192_1_ >= this.xPosition
&& p_146192_1_ < this.xPosition + this.width
&& p_146192_2_ >= this.yPosition
&& p_146192_2_ < this.yPosition + this.height;
if (flag && p_146192_3_ == 1) {
key.setText("");
value.setText("");
}
}
@Override
public void setText(String p_146180_1_) {
super.setText(p_146180_1_);
updateButtons();
}
};
value = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 139, 151 + 12 - 45, 12) {
@Override
public void writeText(String text) {
// ignore write requests containing non digit characters.
// we don't accept decimals or negative numbers, so we can safely use isDigit instead of something more
// advanced
// use codePoints() in case someone pasted an emoji by mistake
if (text != null && text.codePoints().allMatch(Character::isDigit)) {
super.writeText(text);
updateButtons();
}
}
@Override
public void setFocused(boolean p_146195_1_) {
if (!p_146195_1_ && isFocused() && !StringUtils.isBlank(getText())) {
int result;
try {
result = Math.max(Integer.parseInt(getText()), 1);
} catch (NumberFormatException e) {
result = 1;
}
setText(String.valueOf(result));
}
super.setFocused(p_146195_1_);
}
@Override
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
boolean flag = p_146192_1_ >= this.xPosition
&& p_146192_1_ < this.xPosition + this.width
&& p_146192_2_ >= this.yPosition
&& p_146192_2_ < this.yPosition + this.height;
if (flag && p_146192_3_ == 1) {
setText("");
}
}
@Override
public void setText(String p_146180_1_) {
super.setText(p_146180_1_);
updateButtons();
}
};
list.onGuiInit(this);
addButton(new GuiButton(
0, guiLeft + 12, guiTop + 157, 47, 20, I18n.format("item.structurelib.constructableTrigger.gui.add")));
addButton(new GuiButton(
1,
guiLeft + 65,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.unset")));
addButton(new GuiButton(
2,
guiLeft + 118,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.wipe")));
updateButtons();
}
@Override
public int getGuiLeft() {
return guiLeft;
}
@Override
public int getGuiTop() {
return guiTop;
}
@Override
public int getXSize() {
return 176;
}
@Override
public int getYSize() {
return 188;
}
@Override
public void addButton(GuiButton button) {
getButtonList().add(button);
}
@Override
public void removeButton(GuiButton button) {
getButtonList().remove(button);
}
@Override
public int getOverlayOffsetX() {
return 0;
}
@Override
public void doActionPerformed(GuiButton but) {
actionPerformed(but);
}
@SuppressWarnings("unchecked")
private List<GuiButton> getButtonList() {
return buttonList;
}
@Override
public void handleMouseInput() {
int delta = Mouse.getEventDWheel();
if (delta != 0) list.handleDWheel(delta);
super.handleMouseInput();
}
@Override
protected void mouseClicked(int mX, int mY, int button) {
key.mouseClicked(mX, mY, button);
value.mouseClicked(mX, mY, button);
super.mouseClicked(mX, mY, button);
}
@Override
protected void keyTyped(char aChar, int aKey) {
switch (aKey) {
case Keyboard.KEY_TAB:
if (key.isFocused()) {
key.setFocused(false);
value.setFocused(true);
} else {
key.setFocused(true);
value.setFocused(false);
}
return;
case Keyboard.KEY_RETURN:
case Keyboard.KEY_NUMPADENTER:
GuiButton add = getButtonList().get(0);
if (add.enabled) doActionPerformed(add);
return;
case Keyboard.KEY_UP:
if (list.selectedIndex > 0) list.setSelection(list.selectedIndex - 1);
return;
case Keyboard.KEY_DOWN:
if (list.selectedIndex < list.getNumElements() - 1) list.setSelection(list.selectedIndex + 1);
return;
}
if (key.textboxKeyTyped(aChar, aKey)) {
updateButtons();
return;
}
if (value.textboxKeyTyped(aChar, aKey)) return;
super.keyTyped(aChar, aKey);
}
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());
getButtonList().get(0).displayString = existing
? I18n.format("item.structurelib.constructableTrigger.gui.set")
: I18n.format("item.structurelib.constructableTrigger.gui.add");
getButtonList().get(0).enabled = !StringUtils.isBlank(value.getText());
getButtonList().get(1).enabled = existing && !StringUtils.isBlank(value.getText());
}
private int getValue() {
try {
return Integer.parseInt(value.getText());
} catch (NumberFormatException e) {
return -1;
}
}
@Override
protected void actionPerformed(GuiButton btn) {
if (btn == null) return;
switch (btn.id) {
case 0:
int value = getValue();
if (value <= 0) return;
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
break;
case 1:
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
break;
case 2:
ChannelDataAccessor.wipeChannelData(trigger);
break;
}
super.actionPerformed(btn);
}
@Override
public void onGuiClosed() {
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
StructureLib.instance().proxy().uploadChannels(trigger);
}
@Override
public void updateScreen() {
super.updateScreen();
key.updateCursorCounter();
value.updateCursorCounter();
}
@Override
public boolean doesGuiPauseGame() {
return false;
}
@Override
public void drawScreen(int mX, int mY, float partialTick) {
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
int topLeftX = (this.width - this.getXSize()) / 2;
int topLeftY = (this.height - this.getYSize()) / 2;
drawTexturedModalRect(topLeftX, topLeftY, 0, 0, getXSize(), getYSize());
super.drawScreen(mX, mY, partialTick);
list.drawScreen(mX, mY, partialTick);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.key"), guiLeft + 12, guiTop + 122, 0);
key.drawTextBox();
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.value"), guiLeft + 12, guiTop + 142, 0);
value.drawTextBox();
}
private class GuiChannelsList extends GuiScrollableList<Entry<String, Integer>> {
private List<Entry<String, Integer>> cache;
public GuiChannelsList(int width, int height, int originX, int originY, int slotHeight) {
super(width, height, originX, originY, slotHeight);
}
@Override
public Entry<String, Integer> getElementAt(int index) {
return ChannelDataAccessor.iterateChannelData(trigger)
.sorted()
.skip(index)
.findFirst()
.orElse(null);
}
@Override
public int getNumElements() {
return ChannelDataAccessor.countChannelData(trigger);
}
@Override
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
if (mXRelative >= margin + 1
&& mXRelative <= margin + 5
&& mYRelative >= margin / 2 + 1
&& mYRelative <= margin / 2 + 5) {
Entry<String, Integer> e = getElementAt(elementIndex);
if (e != null) {
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
return false;
}
}
return super.elementClicked(elementIndex, doubleClick, mXRelative, mYRelative);
}
@Override
protected void prepareDrawElements() {
cache = ChannelDataAccessor.iterateChannelData(trigger).sorted().collect(Collectors.toList());
}
@Override
protected void drawElement(
int elementIndex, int x, int y, int height, Tessellator tessellator, boolean isHovering) {
if (elementIndex < 0 || elementIndex >= cache.size()) {
return;
}
Entry<String, Integer> e = cache.get(elementIndex);
if (e == null) return;
if (elementIndex > 0) {
// args x1, x2, y1, color
drawHorizontalLine(minX + 1, maxX - 1, y - 2, 0xffaaaaaa);
}
int keyMaxWidth = KEY_MAX_WIDTH * 2;
if (isHovering) {
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
GL11.glColor4f(1, 1, 1, 1);
drawTexturedModalRect(x + margin + 1, y + margin / 2 + 1, 251, 251, 5, 5);
}
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, 0xffffff);
int valueX = x + margin + keyMaxWidth + margin;
fontRendererObj.drawString(trim(e.getValue().toString(), maxX - valueX), valueX, y + margin / 2, 0xffffff);
}
@Override
public void drawScreen(int mX, int mY, float partialTick) {
super.drawScreen(mX, mY, partialTick);
// args x1, y1, y2, color
drawVerticalLine(minX + margin + margin / 2 + KEY_MAX_WIDTH * 2, minY + 1, maxY - 1, 0xffaaaaaa);
}
public String trim(String e, int keyMaxWidth) {
// TODO optimize this to not create a billion string, or maybe cache this
String s = fontRendererObj.trimStringToWidth(e, keyMaxWidth);
if (s.length() != e.length()) {
StringBuilder buffer =
new StringBuilder(s).deleteCharAt(s.length() - 1).append("...");
while ((s = fontRendererObj.trimStringToWidth(buffer.toString(), keyMaxWidth)).length()
!= buffer.length())
// drop last original char
buffer.deleteCharAt(s.length() - 4);
do {
buffer.append('.');
} while (fontRendererObj.getStringWidth(buffer.toString()) <= keyMaxWidth);
s = buffer.deleteCharAt(s.length() - 1)
.insert(s.length() - 3, EnumChatFormatting.GRAY)
.toString();
}
return s;
}
}
}
@@ -0,0 +1,443 @@
package com.gtnewhorizon.structurelib.gui;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
/**
* Shamelessly stolen from EnderCore, with some amount of edit to fit in one file. Original code from CrazyPants.
* <p>
* As the code has been released into public domain, it is now LGPL as part of this library as a whole.
* You can go to EnderCore's repository to obtain a public domain copy though
*
* Major edits
* <ul>
* <li>Separate dwheel handling to handleDWheel. The next event calls often cause the mouse click event to
* not fire on other widgets</li>
* <li>scrollbar are now rendered within width</li>
* <li>added prepareDrawElements</li>
* </ul>
*
* @author CrazyPants
* @author glee8e
*/
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 int minY;
protected int maxY;
protected int minX;
protected int maxX;
protected int mouseX;
protected int mouseY;
protected int selectedIndex = -1;
protected int margin = 4;
protected List<ListSelectionListener<T>> listeners = new CopyOnWriteArrayList<>();
private int scrollUpButtonID;
private int scrollDownButtonID;
private float initialClickY = -2.0F;
private float scrollMultiplier;
private float amountScrolled;
private long lastClickedTime;
private boolean showSelectionBox = true;
public GuiScrollableList(int width, int height, int originX, int originY, int slotHeight) {
this.width = width;
this.height = height;
this.originX = originX;
this.originY = originY;
this.slotHeight = slotHeight;
minY = originY;
maxY = minY + height;
minX = originX;
maxX = minX + width;
}
public void onGuiInit(IGuiScreen gui) {
minY = originY + gui.getGuiTop();
maxY = minY + height;
minX = originX + gui.getGuiLeft();
maxX = minX + width;
}
public void addSelectionListener(ListSelectionListener<T> listener) {
listeners.add(listener);
}
public void removeSelectionListener(ListSelectionListener<T> listener) {
listeners.remove(listener);
}
public T getSelectedElement() {
return getElementAt(selectedIndex);
}
public void setSelection(T selection) {
setSelection(getIndexOf(selection));
}
public void setSelection(int index) {
if (index == selectedIndex) {
return;
}
selectedIndex = index;
for (ListSelectionListener<T> listener : listeners) {
listener.selectionChanged(this, selectedIndex);
}
}
public int getIndexOf(T element) {
if (element == null) {
return -1;
}
for (int i = 0; i < getNumElements(); i++) {
if (element.equals(getElementAt(i))) {
return i;
}
}
return -1;
}
public abstract T getElementAt(int index);
public abstract int getNumElements();
protected abstract void drawElement(
int elementIndex, int x, int y, int height, Tessellator tessellator, boolean isHovering);
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
return true;
}
public void setShowSelectionBox(boolean val) {
showSelectionBox = val;
}
protected int getContentHeight() {
return getNumElements() * slotHeight;
}
public void setScrollButtonIds(int scrollUpButtonID, int scrollDownButtonID) {
this.scrollUpButtonID = scrollUpButtonID;
this.scrollDownButtonID = scrollDownButtonID;
}
private void clampScrollToBounds() {
int i = getContentOverhang();
if (i < 0) {
i *= -1;
}
if (amountScrolled < 0.0F) {
amountScrolled = 0.0F;
}
if (amountScrolled > i) {
amountScrolled = i;
}
}
public int getContentOverhang() {
return getContentHeight() - (height - margin);
}
public void actionPerformed(GuiButton b) {
if (b.enabled) {
if (b.id == scrollUpButtonID) {
amountScrolled -= slotHeight * 2 / 3;
initialClickY = -2.0F;
clampScrollToBounds();
} else if (b.id == scrollDownButtonID) {
amountScrolled += slotHeight * 2 / 3;
initialClickY = -2.0F;
clampScrollToBounds();
}
}
}
/**
* draws the slot to the screen, pass in mouse's current x and y and partial
* ticks
*/
public void drawScreen(int mX, int mY, float partialTick) {
this.mouseX = mX;
this.mouseY = mY;
processMouseEvents();
if (getContentOverhang() > 0) maxX = minX + width - 6;
else maxX = minX + width;
clampScrollToBounds();
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_FOG);
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int sx = minX * sr.getScaleFactor();
int sw = width * sr.getScaleFactor();
int sy = mc.displayHeight - (maxY * sr.getScaleFactor());
int sh = height * sr.getScaleFactor();
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(sx, sy, sw, sh);
Tessellator tessellator = Tessellator.instance;
drawContainerBackground(tessellator);
int amountScrolled = (int) this.amountScrolled;
int contentYOffset = this.minY + margin - amountScrolled;
boolean hoveringPrecondition = mX >= minX && mX <= maxX && mY >= minY && mY <= maxY;
prepareDrawElements();
for (int i = 0; i < getNumElements(); ++i) {
int elementY = contentYOffset + i * this.slotHeight;
int slotHeight = this.slotHeight - margin;
if (elementY <= maxY && elementY + slotHeight >= minY) {
if (showSelectionBox && i == selectedIndex) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tessellator.startDrawingQuads();
tessellator.setColorOpaque_I(8421504);
tessellator.addVertexWithUV(minX, elementY + slotHeight + 2, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(maxX, elementY + slotHeight + 2, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(maxX, elementY - 2, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(minX, elementY - 2, 0.0D, 0.0D, 0.0D);
tessellator.setColorOpaque_I(0);
tessellator.addVertexWithUV(minX + 1, elementY + slotHeight + 1, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(maxX - 1, elementY + slotHeight + 1, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(maxX - 1, elementY - 1, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(minX + 1, elementY - 1, 0.0D, 0.0D, 0.0D);
tessellator.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
boolean isHovering = hoveringPrecondition && elementY - 2 <= mY && mY < elementY + slotHeight + 2;
drawElement(i, minX, elementY, slotHeight, tessellator, isHovering);
}
}
GL11.glDisable(GL11.GL_SCISSOR_TEST);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_ALPHA_TEST);
GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 0);
tessellator.addVertexWithUV(this.minX, this.minY + margin, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(this.maxX, this.minY + margin, 0.0D, 1.0D, 1.0D);
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(this.maxX, this.minY, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(this.minX, this.minY, 0.0D, 0.0D, 0.0D);
tessellator.draw();
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(this.minX, this.maxY, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(this.maxX, this.maxY, 0.0D, 1.0D, 1.0D);
tessellator.setColorRGBA_I(0, 0);
tessellator.addVertexWithUV(this.maxX, this.maxY - margin, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(this.minX, this.maxY - margin, 0.0D, 0.0D, 0.0D);
tessellator.draw();
renderScrollBar(tessellator);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glShadeModel(GL11.GL_FLAT);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glDisable(GL11.GL_BLEND);
}
protected void prepareDrawElements() {}
protected void renderScrollBar(Tessellator tessellator) {
int contentHeightOverBounds = getContentOverhang();
if (contentHeightOverBounds > 0) {
int clear = (maxY - minY) * (maxY - minY) / getContentHeight();
if (clear < 32) {
clear = 32;
}
if (clear > maxY - minY - 8) {
clear = maxY - minY - 8;
}
int y = (int) this.amountScrolled * (maxY - minY - clear) / contentHeightOverBounds + minY;
if (y < minY) {
y = minY;
}
GL11.glDisable(GL11.GL_TEXTURE_2D);
int scrollBarMinX = getScrollBarX();
int scrollBarMaxX = scrollBarMinX + 6;
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(scrollBarMinX, maxY, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, maxY, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, minY, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, minY, 0.0D, 0.0D, 0.0D);
tessellator.setColorRGBA_F(0.3f, 0.3f, 0.3f, 1);
tessellator.addVertexWithUV(scrollBarMinX, (y + clear), 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, (y + clear), 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, y, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
tessellator.setColorRGBA_F(0.7f, 0.7f, 0.7f, 1);
tessellator.addVertexWithUV(scrollBarMinX, (y + clear - 1), 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV((scrollBarMaxX - 1), (y + clear - 1), 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV((scrollBarMaxX - 1), y, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
tessellator.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
}
private void processMouseEvents() {
if (Mouse.isButtonDown(0)) {
processMouseBown();
} else {
initialClickY = -1.0F;
}
}
public void handleDWheel(int mouseWheelDelta) {
if (!Mouse.isButtonDown(0)) {
if (mouseWheelDelta > 0) {
amountScrolled -= slotHeight / 2;
} else /* if (mouseWheelDelta < 0) */ {
amountScrolled += slotHeight / 2;
}
}
}
private void processMouseBown() {
int contentHeightOverBounds;
if (initialClickY == -1.0F) {
if (mouseY >= minY && mouseY <= maxY && mouseX >= minX && mouseX <= maxX + 6) {
boolean clickInBounds = true;
int y = mouseY - minY + (int) amountScrolled - margin;
int mouseOverElement = y / slotHeight;
if (mouseX >= minX
&& mouseX <= maxX
&& mouseOverElement >= 0
&& y >= 0
&& mouseOverElement < getNumElements()) {
boolean doubleClick =
mouseOverElement == selectedIndex && Minecraft.getSystemTime() - lastClickedTime < 250L;
if (elementClicked(
mouseOverElement, doubleClick, mouseX - minX, y - slotHeight * mouseOverElement)) {
setSelection(mouseOverElement);
lastClickedTime = Minecraft.getSystemTime();
}
} else if (mouseX >= minX && mouseX <= maxX && y < 0) {
clickInBounds = false;
}
int scrollBarMinX = getScrollBarX();
int scrollBarMaxX = scrollBarMinX + 6;
if (mouseX >= scrollBarMinX && mouseX <= scrollBarMaxX) {
scrollMultiplier = -1.0F;
contentHeightOverBounds = getContentOverhang();
if (contentHeightOverBounds < 1) {
contentHeightOverBounds = 1;
}
int empty = (int) ((float) ((maxY - minY) * (maxY - minY)) / (float) getContentHeight());
if (empty < 32) {
empty = 32;
}
if (empty > maxY - minY - 8) {
empty = maxY - minY - 8;
}
scrollMultiplier /= (float) (maxY - minY - empty) / (float) contentHeightOverBounds;
} else {
scrollMultiplier = 1.0F;
}
if (clickInBounds) {
initialClickY = mouseY;
} else {
initialClickY = -2.0F;
}
} else {
initialClickY = -2.0F;
}
} else if (initialClickY >= 0.0F) {
// Scrolling
amountScrolled -= (mouseY - initialClickY) * scrollMultiplier;
initialClickY = mouseY;
}
}
protected int getScrollBarX() {
return maxX;
}
protected void drawContainerBackground(Tessellator tess) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tess.startDrawingQuads();
tess.setColorOpaque_I(2105376);
tess.addVertex(minX, maxY, 0.0D);
tess.addVertex(maxX, maxY, 0.0D);
tess.addVertex(maxX, minY, 0.0D);
tess.addVertex(minX, minY, 0.0D);
tess.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
public interface IGuiScreen {
int getGuiLeft();
int getGuiTop();
int getXSize();
int getYSize();
void addButton(GuiButton button);
void removeButton(GuiButton button);
int getOverlayOffsetX();
void doActionPerformed(GuiButton but);
}
public interface ListSelectionListener<T> {
void selectionChanged(GuiScrollableList<T> list, int selectedIndex);
}
}
@@ -3,16 +3,17 @@ package com.gtnewhorizon.structurelib.item;
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
import static net.minecraft.util.EnumChatFormatting.BLUE;
import static net.minecraft.util.StatCollector.translateToLocal;
import static net.minecraft.util.StatCollector.translateToLocalFormatted;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
import java.util.List;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import org.lwjgl.input.Keyboard;
public class ItemConstructableTrigger extends Item {
public ItemConstructableTrigger() {
@@ -21,6 +22,13 @@ public class ItemConstructableTrigger extends Item {
setCreativeTab(StructureLib.creativeTab);
}
@Override
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
if (world.isRemote && getMovingObjectPositionFromPlayer(world, player, true) == null)
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
return super.onItemRightClick(stack, world, player);
}
@Override
public boolean onItemUseFirst(
ItemStack stack,
@@ -39,34 +47,30 @@ public class ItemConstructableTrigger extends Item {
@SuppressWarnings("unchecked")
@Override
public void addInformation(ItemStack aStack, EntityPlayer ep, List aList, boolean boo) {
aList.add(
translateToLocal("item.structurelib.constructableTrigger.desc.0")); // Triggers Constructable Interface
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.1")); // Shows multiblock construction details,
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.2")); // just Use on a multiblock controller.
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.3")); // (Sneak Use in creative to build)
aList.add(BLUE
+ translateToLocal("item.structurelib.constructableTrigger.desc.4")); // Quantity affects tier/mode/type
}
@Override
public void onUpdate(
ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_, boolean p_77663_5_) {
// remove LastUse tags if it times out,
// so it can stack with other trigger item stacks
// don't remove it too soon though, to prevent chat spam
// this might be perceived as a hack, but ok this is simple enough to implement
if (p_77663_1_.hasTagCompound()
&& p_77663_1_.getTagCompound().getLong("LastUse") + ConstructableUtility.COOLDOWN * 2
< StructureLib.getOverworldTime()) {
NBTTagCompound tag = p_77663_1_.getTagCompound();
tag.removeTag("LastUse");
if (tag.hasNoTags()) p_77663_1_.setTagCompound(null);
if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
aList.add(translateToLocalFormatted(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(aStack)));
ChannelDataAccessor.iterateChannelData(aStack)
.map(e -> e.getKey() + ": " + e.getValue())
.forEach(aList::add);
} else {
aList.add(translateToLocal(
"item.structurelib.constructableTrigger.desc.0")); // Triggers Constructable Interface
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.1")); // Shows multiblock construction details,
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.2")); // just Use on a multiblock controller.
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.3")); // (Sneak Use in creative to build)
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.4")); // Quantity affects tier/mode/type
if (ChannelDataAccessor.hasSubChannel(aStack))
aList.add(translateToLocal("item.structurelib.constructableTrigger.desc.5"));
}
}
}
@@ -45,6 +45,10 @@ public abstract class AlignmentMessage implements IMessage {
ByteBufUtils.writeTag(pBuffer, tFXTag);
}
/**
* {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}
* @param provider
*/
private AlignmentMessage(IAlignmentProvider provider) {
if (!(provider instanceof TileEntity)) throw new IllegalArgumentException("Provider must be a TileEntity");
IAlignment alignment = provider.getAlignment();
@@ -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;
}
}
}
@@ -0,0 +1,67 @@
package com.gtnewhorizon.structurelib.net;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import cpw.mods.fml.common.network.ByteBufUtils;
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;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.entity.player.EntityPlayerMP;
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<>();
public SetChannelDataMessage() {}
public SetChannelDataMessage(ItemStack trigger) {
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
}
@Override
public void fromBytes(ByteBuf buf) {
data.clear();
int size = ByteBufUtils.readVarShort(buf);
for (int i = 0; i < size; i++) {
data.add(Pair.of(ByteBufUtils.readUTF8String(buf), ByteBufUtils.readVarInt(buf, 4)));
}
}
@Override
public void toBytes(ByteBuf buf) {
ByteBufUtils.writeVarShort(buf, data.size());
for (Entry<String, Integer> e : data) {
ByteBufUtils.writeUTF8String(buf, e.getKey());
ByteBufUtils.writeVarInt(buf, e.getValue(), 4);
}
}
public static class Handler implements IMessageHandler<SetChannelDataMessage, IMessage> {
@Override
public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) {
EntityPlayerMP player = ctx.getServerHandler().playerEntity;
ItemStack heldItem = player.getHeldItem();
if (heldItem != null && heldItem.getItem() instanceof ItemConstructableTrigger) {
ChannelDataAccessor.wipeChannelData(heldItem);
for (Entry<String, Integer> e : message.data) {
ChannelDataAccessor.setChannelData(heldItem, e.getKey(), e.getValue());
}
// since this is a set all channel request from the client, we would assume client already know
// what this would look like on the client, so no sync
} else {
StructureLib.LOGGER.warn(
"{} trying to set channel data on {}, which is not a hologram projector!",
player.getUniqueID(),
heldItem);
}
return null;
}
}
}
@@ -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;
}
}
}
@@ -1,15 +1,48 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import java.util.function.Function;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
/**
* This is the structure definition of your multi. You will have one of these for each multi.
*
* <h2>Cache the instances of this class</h2>
* <p>
* You will construct this definition once per multi, either at class load or at first call to your variant of {@code getStructureDefinition()}.
* You can use a {@link ClassValue ClassValue&lt;IStructureDefinition&lt;?>>} if you have a template for subclass.
* You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for simpler cases, but it is
* slightly less performant as it will allocate new objects on each structure check, whereas {@link ClassValue} will
* allocate once and cache it onwards.
* <p>
* <h2>Constructing</h2>
* <p>
* {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()}
* <p>
* Builder has two main methods:
* <ul>
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()}
* is a 2d string array describing the layout of structure. You can optionally use `StructureUtility#transpose()`
* to transpose the array to make the structure look nicer in source code.
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addElement(Character, IStructureElement) addElement()}. It takes a char identifier (mapped to a char in any of {@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()} calls) and a {@link IStructureElement}.
* </ul>
* <h2>Position A, B and C</h2>
*
* Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
* <ul>
* <li>The "A" position would be the number of blocks on the left side of the controller, not counting controller itself.</li>
* <li>The "B" position would be the number of blocks on the top side of the controller, not counting controller itself.</li>
* <li>The "C" position would be the number of blocks between you and controller, not counting controller itself.</li>
* </ul>
* @param <T> Type of the context object. Usually this will be the multiblock controller.
*/
public interface IStructureDefinition<T> {
/**
* Used internally
@@ -22,6 +55,21 @@ public interface IStructureDefinition<T> {
*/
IStructureElement<T>[] getStructureFor(String name);
/**
* Run a structure check.
* @param object context object. usually multiblock controller.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param forceCheckAllBlocks check all location even if chunk not currently loaded.
* @return true if successful, false otherwise
*/
default boolean check(
T object,
String piece,
@@ -50,6 +98,20 @@ public interface IStructureDefinition<T> {
forceCheckAllBlocks);
}
/**
* Spawn hint particles. Should not be called on server side.
* @param object context object. usually multiblock controller.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @return true if successful, false otherwise
*/
default boolean hints(
T object,
ItemStack trigger,
@@ -78,6 +140,23 @@ public interface IStructureDefinition<T> {
null);
}
/**
* Build the multi in creative mode on a best effort basis. Does not guarantee the structure is correct after a
* successful call.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @return true if sucessful, false otherwise
*/
default boolean build(
T object,
ItemStack trigger,
@@ -106,6 +185,26 @@ public interface IStructureDefinition<T> {
null);
}
/**
* Call {@link #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} if {@code hintsOnly},
* call {@link #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} otherwise.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param hintsOnly whether to spawn hints or do creative build.
* @return true if sucessful, false otherwise
* @see #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
*/
default boolean buildOrHints(
T object,
ItemStack trigger,
@@ -136,6 +235,19 @@ public interface IStructureDefinition<T> {
}
/**
* Cause a survival build.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param elementBudget build up to this many elements
* @param source from where to drain resource
* @param actor source of action
@@ -179,6 +291,25 @@ public interface IStructureDefinition<T> {
return walker.getBuilt();
}
/**
* Low level utility.
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param elements the structure piece
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param hintsOnly whether to spawn hints or do creative build. no effect if {@code checkBlocksIfNotNullForceCheckAllIfTrue} is not null.
* @param checkBlocksIfNotNullForceCheckAllIfTrue creative build or spawn hints if null. Force check not loaded chunk
* if true. Check only loaded chunk is false.
* @return true if iteration completed successfully.
* @param <T> type of context object.
*/
static <T> boolean iterate(
T object,
ItemStack trigger,
@@ -226,7 +357,7 @@ public interface IStructureDefinition<T> {
skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)),
"check force");
}
if (DEBUG_MODE && success) {
if (StructureLibAPI.isDebugEnabled() && success) {
StructureLib.LOGGER.info(
"Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
}
@@ -243,7 +374,10 @@ public interface IStructureDefinition<T> {
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z) -> e.spawnHint(object, world, x, y, z, trigger)),
ignoreBlockUnloaded((e, w, x, y, z) -> {
e.spawnHint(object, world, x, y, z, trigger);
return true;
}),
"spawnHint");
} else {
StructureUtility.iterateV2(
@@ -256,13 +390,21 @@ public interface IStructureDefinition<T> {
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z) -> e.placeBlock(object, world, x, y, z, trigger)),
ignoreBlockUnloaded((e, w, x, y, z) -> {
e.placeBlock(object, world, x, y, z, trigger);
return true;
}),
"placeBlock");
}
}
return true;
}
/**
* Create a new instance of builder.
* @return
* @param <T> type of context object
*/
static <T> StructureDefinition.Builder<T> builder() {
return StructureDefinition.builder();
}
@@ -1,11 +1,11 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import java.util.function.Consumer;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
@@ -23,10 +23,16 @@ public interface IStructureElement<T> {
/**
* Try place the block by taking resource from given IItemSource.
* <p>
* You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayerMP)}
* or its overloads to implement this.
*
* @param trigger trigger item
* @param s drain resources from this place
* @param actor source of action.
* @param chatter
* @param actor source of action. for very critical errors you can also just send the error messages here, bypassing
* any filter that chatter might have.
* @param chatter send error messages here. Caller will choose an appropriate way to forward it to player if the
* other fallbacks also fails.
*/
default PlaceResult survivalPlaceBlock(
T t,
@@ -39,7 +45,7 @@ public interface IStructureElement<T> {
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit");
if (DEBUG_MODE)
if (StructureLibAPI.isDebugEnabled())
LOGGER.error(
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
@@ -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;
@@ -1,8 +1,8 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.*;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import java.util.*;
import java.util.stream.Collectors;
@@ -130,7 +130,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
public IStructureDefinition<T> build() {
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
if (DEBUG_MODE) {
if (StructureLibAPI.isDebugEnabled()) {
return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures);
} else {
return structures::get;
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,6 @@
package com.gtnewhorizon.structurelib.structure;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
@@ -38,8 +37,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
if (check) element.check(object, world, x, y, z);
return ++built < elementBudget;
case REJECT:
if (ConstructableUtility.redBrokenOne)
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.
*/
@@ -376,7 +376,7 @@ public class InventoryUtility {
}
private static class ItemStackCounterImpl implements ItemStackCounter {
private final Map<ItemStack, Integer> store = new ItemStackMap<>();
private final Map<ItemStack, Integer> store = new ItemStackMap<>(true);
@Override
public void add(ItemStack stack, int stackSize) {
+235
View File
@@ -0,0 +1,235 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>API Overview</TITLE>
</HEAD>
<BODY>
StructureLib API documentation.
<h1>Key API class outline</h1>
<p>
Here we outline a few key API classes/interfaces.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}</h2>
<p>
This is the structure definition of your multi.
You will have one `IStructureDefinition` for each multi.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureElement}</h2>
<p>
These are how you define which block will be accepted.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.StructureUtility}</h2>
<p>
This is the standard library of {@link com.gtnewhorizon.structurelib.structure.IStructureElement
IStructureElement}s.
The Standard Library contains structure elements that handle basic block element and elements that allow composing
these basic elements.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}</h2>
<p>
This interface should be implemented by tile entities that support creative autoplacing and hint particle emitting.
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
IStructureDefinition}
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#build(Object, ItemStack, String,
World, ExtendedFacing, int, int, int, int, int, int) build()}
or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#hints(Object, ItemStack, String, World,
ExtendedFacing, int, int, int, int, int, int) hints()} method here.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}</h2>
<p>
This interface is an enhancement over {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable
IConstructable}.
If your tile entities also support survival autoplacing then you should implement this.
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
IStructureDefinition}
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack,
String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)
survivalBuild()}.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}</h2>
<p>
This interface should be implemented on tile entities that will delegate all constructable or survival construable
functionality to some other class.
This is mostly only to support gregtech meta tile entity system.
</p>
<p>
Return null if the delegate does not exist.
</p>
<h2>{@link com.gtnewhorizon.structurelib.util.InventoryUtility}</h2>
<p>
This class backs the standard implementations of {@link com.gtnewhorizon.structurelib.structure.IItemSource}.
You might want to register new inventory provider or inventory extractor there to facilitate survival auto place.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link
com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link
com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}</h2>
<p>
These interfaces will be used to implement rotation.
Provide a framework to rotate the block.
You can skip this if you store ExtendedFacing and do rotation in other means.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}</h2>
<p>
This class contains the accessors for channel data held on a trigger item. See <a href="#channels">below</a> for more
information.
</p>
<h2>{@link com.gtnewhorizon.structurelib.StructureLibAPI}</h2>
<p>
This class contains the low level utility used for implementing new {@link
com.gtnewhorizon.structurelib.structure.IStructureElement}.
You will also need methods in this class to while implementing rotation.
</p>
<p>
<b>IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS.</b>
This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered)
without
breaking client code.
</p>
<h1>Important Concepts</h1>
<h2>Context Object</h2>
<p>
Context Object are what you pass to {@link
com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int,
int, int, int, int, boolean)} and alike as first parameter. It will hold the necessary states for {@link
com.gtnewhorizon.structurelib.structure.IStructureElement}. It's usually, but not necessarily, the multiblock controller.
</p>
<p>
Standard structure element library offers method to switch context objects if needed.
</p>
<h2>Trigger Item</h2>
<p>
Trigger Item is the item used to trigger hint spawn, creative build and survival auto place.
The trigger item will be passed to relevant classes as an indicator of structure tier.
The item type and stack tag compound of this item stack are opaque objects that should not be parsed.
</p>
<p>
StructureLib comes with a trigger item implementation.
It does register it to the game registries, but does not come with a recipe.
Usually there is no reason to reimplement this on your own.
</p>
<h2><a href="#channels" id="channels">Channels</a></h2>
<p>
Trigger item carries the player's choice of structure tier by its stack size.
When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious
restriction, as one ItemStack cannot have two stack sizes at the same time.
Here we use the <i>channels</i> to bypass this restriction.
</p>
<p>
Basically, channels are information stored on the trigger item's NBT tags.
One trigger item can carry multiple channels.
Each channel can be identified with <b>lower case</b> strings and has the value being one positive Java int.
There will be one and only one master channel and 0 or more sub channel.
When a non-existent sub channel is queried, master channel will be used instead to fill its place.
</p>
<p>
Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel
to use.
</p>
<p>
Structure Element Author will usually not need to consider channels, i.e. proceed as if this concept does not exist,
unless it must take input from multiple channels.
</p>
<p>
Structure Definition Author will consider how to map each channel to each tiered component.
He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element
on which channel to use.
Conceptually, it will return an ItemStack with the master channel being the value of selected channel.
In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size.
</p>
<p>
It's worth mentioning that the returned new ItemStack will still have all the channel data on it, so a multi-channel
structure element can still query the channel it wants just fine. It is however impossible to recover the original
master channel.
</p>
<h3>why is this called channel?</h3>
Just give me a better name and I'd do the renaming.
<h3>Examples</h3>
<h4>For Structure Definition Author</h4>
<p>GT++ Chemical Plant is a multi with 4 distinctly tiered component.</p>
<p>It will use
<ul>
<li>master channel for its outer shell</li>
<li>channel `"casing"` for machine casings</li>
<li>channel `"pipe"` for pipe casings</li>
<li>channel `"coil"` for heating coils</li>
</ul>
Its structure code will then look like
<pre>
.addElement('a', shellStructureElement())
.addElement('b', withChannel("casing", machineCasingStructureElement()))
.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
.addElement('d', withChannel("coil", coilStructureElement()))
</pre>
whereas all these structure elements are designed without considering channels whatsoever
<h4> Multi channel input Structure Element</h4>
<p>
Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both
meta id
and facing input.
</p>
<p>
It will use
<ul>
<li>master channel for its facing</li>
<li>channel `"id"` for meta tile entity id</li>
</ul>
<p>
Its code will then look like
<pre>
import com.gtnewhorizon.structurelib.structure.IStructureElement;
public class MyHatchElement&lt;T> implements IStructureElement&lt;T> {
//...
@Override
public PlaceResult survivalPlaceBlock(
Object o,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer chatter) {
if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
if (taken == null) return PlaceResult.REJECT;
return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
}
}
</pre>
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
</BODY>
</HTML>
@@ -26,13 +26,22 @@ item.structurelib.constructableTrigger.desc.1=Shows multiblock construction sche
item.structurelib.constructableTrigger.desc.2=use on the multiblock controller.
item.structurelib.constructableTrigger.desc.3=(Sneak use on controller to build)
item.structurelib.constructableTrigger.desc.4=Quantity of schematics in item stack affects tier/mode/type
item.structurelib.constructableTrigger.too_fast=Please wait %d ticks before another autoplace...
item.structurelib.constructableTrigger.desc.5=Hold §l[LSHIFT]§r to display channel data
item.structurelib.constructableTrigger.desc.lshift.0=Contain %d sub channel.
item.structurelib.constructableTrigger.too_fast=Please wait %d milliseconds before another autoplace...
item.structurelib.constructableTrigger.gui.key=Key:
item.structurelib.constructableTrigger.gui.value=Value:
item.structurelib.constructableTrigger.gui.add=Add
item.structurelib.constructableTrigger.gui.set=Set
item.structurelib.constructableTrigger.gui.unset=Unset
item.structurelib.constructableTrigger.gui.wipe=§4Wipe
structurelib.autoplace.built_stat=Built %s blocks
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§6. Using master channel instead.
structurelib.blockhint.desc.0=Helps while building
structurelib.blockhint.0.name=Hint 1 dot
@@ -26,8 +26,17 @@ item.structurelib.constructableTrigger.desc.1=只能在多方块主机上使用
item.structurelib.constructableTrigger.desc.2=结构的示意图。 (译者注:请搭高使用)
item.structurelib.constructableTrigger.desc.3= (潜行右键可以直接完成搭建)
item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待%dtick...
item.structurelib.constructableTrigger.desc.5=按下 §l[左SHIFT]§r 以显示子信道
item.structurelib.constructableTrigger.desc.lshift.0=包含 %d 个子信道
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待%d毫秒...
item.structurelib.constructableTrigger.gui.key=信道名:
item.structurelib.constructableTrigger.gui.value=值:
item.structurelib.constructableTrigger.gui.add=添加
item.structurelib.constructableTrigger.gui.set=替换
item.structurelib.constructableTrigger.gui.unset=删除
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,但没有找到它
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB