mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 15:04:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03f2c31191 | ||
|
|
9b318f68fb | ||
|
|
adc91bb1dd | ||
|
|
21198c4d39 | ||
|
|
e70ea765f8 | ||
|
|
56a083ce6f | ||
|
|
7389f349db | ||
|
|
5b6b51f80c | ||
|
|
1481b68133 | ||
|
|
ebf2d6f0e1 | ||
|
|
7e7064e006 | ||
|
|
ebcf62fd97 | ||
|
|
74dde96905 | ||
|
|
d73d2bffc2 | ||
|
|
94055bbd6a | ||
|
|
dc1b1fc4db | ||
|
|
fd38dfc31f | ||
|
|
cb82b73e78 | ||
|
|
91d4a8022e | ||
|
|
a12722c4b0 | ||
|
|
b2624a3d36 | ||
|
|
b34e901a93 | ||
|
|
e155c69b20 | ||
|
|
82622d1e15 | ||
|
|
a168702151 | ||
|
|
73f113ed9b | ||
|
|
c19ac860d4 | ||
|
|
d33cdd79c4 | ||
|
|
ed24f15033 | ||
|
|
15c34061bc |
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["gh-pages"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
- name: Generate Index
|
||||
shell: bash
|
||||
run: |
|
||||
find . -name javadoc -type d -print > index.txt
|
||||
echo feature/doc >> index.txt
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: '.'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Deploy Javadoc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- feature/doc
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
javadoc:
|
||||
concurrency: javadoc-publish
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
result_hash: ${{ steps.deploy.outputs.hash }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Generate Javadoc
|
||||
run: ./gradlew --no-daemon spotlessCheck javadoc
|
||||
|
||||
- name: Deploy 🚀
|
||||
id: deploy
|
||||
shell: bash
|
||||
run: |
|
||||
mv build/docs $RUNNER_TEMP/docs_output
|
||||
rm build -rf
|
||||
git checkout gh-pages
|
||||
mkdir -p $TARGET_FOLDER
|
||||
rm -rf $TARGET_FOLDER
|
||||
mv $RUNNER_TEMP/docs_output $TARGET_FOLDER -v
|
||||
git add $TARGET_FOLDER
|
||||
if git diff --cached --exit-code > /dev/null ; then
|
||||
echo Nothing to deploy
|
||||
exit 0
|
||||
fi
|
||||
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' > index.txt
|
||||
git add index.txt
|
||||
git config user.name "${GITHUB_ACTOR}"
|
||||
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||
git commit -m "Deploy javadoc from ref $TARGET_FOLDER"
|
||||
git push
|
||||
echo "::set-output name=hash::$(git rev-parse HEAD)"
|
||||
env:
|
||||
TARGET_FOLDER: ${{ github.ref_name }}
|
||||
COMMIT_AUTHOR: ${{ github.pusher.name }}
|
||||
COMMIT_EMAIL: ${{ github.pusher.email }}
|
||||
@@ -0,0 +1,24 @@
|
||||
javadoc {
|
||||
inputs.file "src/main/javadoc/overview.html"
|
||||
|
||||
include "com/gtnewhorizon/structurelib/alignment/**"
|
||||
include "com/gtnewhorizon/structurelib/constructable/**"
|
||||
include "com/gtnewhorizon/structurelib/structure/**"
|
||||
include "com/gtnewhorizon/structurelib/util/**"
|
||||
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
|
||||
|
||||
options {
|
||||
overview "src/main/javadoc/overview.html"
|
||||
links 'https://docs.oracle.com/javase/8/docs/api/'
|
||||
links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/'
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
}
|
||||
+8
-2
@@ -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())
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
// Add your dependencies here
|
||||
|
||||
dependencies {
|
||||
|
||||
compile("com.github.GTNewHorizons:GTNHLib:0.0.1:dev")
|
||||
runtime("com.github.GTNewHorizons:NotEnoughItems:2.2.20-GTNH:dev")
|
||||
}
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
// Add any additional repositiroes for your dependencies here
|
||||
|
||||
repositories {
|
||||
|
||||
maven {
|
||||
name = "GTNH"
|
||||
url = "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+184
@@ -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();
|
||||
}
|
||||
}
|
||||
+29
-23
@@ -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,15 +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);
|
||||
if (built > 0)
|
||||
// TODO somehow notify extensions that their inventory might have been modified and need to be
|
||||
// synced to client or saved
|
||||
playerMP.inventory.markDirty();
|
||||
else if (built == -1) {
|
||||
.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"));
|
||||
}
|
||||
@@ -92,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;
|
||||
+2
-2
@@ -335,7 +335,7 @@ public enum ExtendedFacing {
|
||||
/**
|
||||
* Translates relative to front facing offset to world offset
|
||||
*
|
||||
* @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B)
|
||||
* @param abcOffset A,B,C offset (facing relative {@code L-->R,U-->D,F-->B})
|
||||
* @return X, Y, Z offset in world
|
||||
*/
|
||||
public Vec3 getWorldOffset(Vec3 abcOffset) {
|
||||
@@ -358,7 +358,7 @@ public enum ExtendedFacing {
|
||||
* Translates world offset to relative front facing offset
|
||||
*
|
||||
* @param xyzOffset X,Y,Z offset in world
|
||||
* @return A, B, C offset (facing relative L-->R,U-->D,F-->B)
|
||||
* @return A, B, C offset (facing relative {@code L-->R,U-->D,F-->B})
|
||||
*/
|
||||
public Vec3 getOffsetABC(Vec3 xyzOffset) {
|
||||
return integerAxisSwap.translate(xyzOffset);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/**
|
||||
* This package provide a framework for rotating a given block, or a multiblock structure.
|
||||
* <p>
|
||||
* All public stuff in this package and its subpackages are considered part of the public API.
|
||||
* Backwards compatibility is maintained to the maximum extend possible.
|
||||
*/
|
||||
|
||||
@@ -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,14 +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.Arrays;
|
||||
import java.util.function.Function;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* This is the structure definition of your multi. You will have one of these for each multi.
|
||||
*
|
||||
* <h2>Cache the instances of this class</h2>
|
||||
* <p>
|
||||
* You will construct this definition once per multi, either at class load or at first call to your variant of {@code getStructureDefinition()}.
|
||||
* You can use a {@link ClassValue ClassValue<IStructureDefinition<?>>} if you have a template for subclass.
|
||||
* You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for simpler cases, but it is
|
||||
* slightly less performant as it will allocate new objects on each structure check, whereas {@link ClassValue} will
|
||||
* allocate once and cache it onwards.
|
||||
* <p>
|
||||
* <h2>Constructing</h2>
|
||||
* <p>
|
||||
* {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()}
|
||||
* <p>
|
||||
* Builder has two main methods:
|
||||
* <ul>
|
||||
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()}
|
||||
* is a 2d string array describing the layout of structure. You can optionally use `StructureUtility#transpose()`
|
||||
* to transpose the array to make the structure look nicer in source code.
|
||||
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addElement(Character, IStructureElement) addElement()}. It takes a char identifier (mapped to a char in any of {@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()} calls) and a {@link IStructureElement}.
|
||||
* </ul>
|
||||
* <h2>Position A, B and C</h2>
|
||||
*
|
||||
* Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
|
||||
* <ul>
|
||||
* <li>The "A" position would be the number of blocks on the left side of the controller, not counting controller itself.</li>
|
||||
* <li>The "B" position would be the number of blocks on the top side of the controller, not counting controller itself.</li>
|
||||
* <li>The "C" position would be the number of blocks between you and controller, not counting controller itself.</li>
|
||||
* </ul>
|
||||
* @param <T> Type of the context object. Usually this will be the multiblock controller.
|
||||
*/
|
||||
public interface IStructureDefinition<T> {
|
||||
/**
|
||||
* Used internally
|
||||
@@ -21,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,
|
||||
@@ -49,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,
|
||||
@@ -77,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,
|
||||
@@ -105,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,
|
||||
@@ -135,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
|
||||
@@ -178,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,
|
||||
@@ -196,140 +328,83 @@ public interface IStructureDefinition<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// change base position to base offset
|
||||
basePositionA = -basePositionA;
|
||||
basePositionB = -basePositionB;
|
||||
basePositionC = -basePositionC;
|
||||
|
||||
int[] abc = new int[] {basePositionA, basePositionB, basePositionC};
|
||||
int[] xyz = new int[3];
|
||||
|
||||
if (checkBlocksIfNotNullForceCheckAllIfTrue != null) {
|
||||
boolean success;
|
||||
if (checkBlocksIfNotNullForceCheckAllIfTrue) {
|
||||
for (IStructureElement<T> element : elements) {
|
||||
if (element.isNavigating()) {
|
||||
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
|
||||
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
|
||||
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
|
||||
} else {
|
||||
extendedFacing.getWorldOffset(abc, xyz);
|
||||
xyz[0] += basePositionX;
|
||||
xyz[1] += basePositionY;
|
||||
xyz[2] += basePositionZ;
|
||||
|
||||
StructureLib.LOGGER.info(
|
||||
"Multi [{}, {}, {}] step @ {} {}",
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
Arrays.toString(xyz),
|
||||
Arrays.toString(abc));
|
||||
|
||||
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
|
||||
if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) {
|
||||
if (DEBUG_MODE) {
|
||||
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", "
|
||||
+ basePositionZ + "] failed @ " + Arrays.toString(xyz) + " "
|
||||
+ Arrays.toString(abc));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", "
|
||||
+ basePositionZ + "] !blockExists @ " + Arrays.toString(xyz) + " "
|
||||
+ Arrays.toString(abc));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
abc[0] += 1;
|
||||
}
|
||||
}
|
||||
success = StructureUtility.iterateV2(
|
||||
elements,
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
(e, w, x, y, z) -> e.check(object, w, x, y, z),
|
||||
"check");
|
||||
} else {
|
||||
for (IStructureElement<T> element : elements) {
|
||||
if (element.isNavigating()) {
|
||||
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
|
||||
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
|
||||
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
|
||||
} else {
|
||||
extendedFacing.getWorldOffset(abc, xyz);
|
||||
xyz[0] += basePositionX;
|
||||
xyz[1] += basePositionY;
|
||||
xyz[2] += basePositionZ;
|
||||
|
||||
StructureLib.LOGGER.info(
|
||||
"Multi [{}, {}, {}] step @ {} {}",
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
Arrays.toString(xyz),
|
||||
Arrays.toString(abc));
|
||||
|
||||
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
|
||||
if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) {
|
||||
if (DEBUG_MODE) {
|
||||
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", "
|
||||
+ basePositionZ + "] failed @ " + Arrays.toString(xyz) + " "
|
||||
+ Arrays.toString(abc));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", "
|
||||
+ basePositionZ + "] !blockExists @ " + Arrays.toString(xyz) + " "
|
||||
+ Arrays.toString(abc));
|
||||
}
|
||||
}
|
||||
abc[0] += 1;
|
||||
}
|
||||
}
|
||||
success = StructureUtility.iterateV2(
|
||||
elements,
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)),
|
||||
"check force");
|
||||
}
|
||||
if (DEBUG_MODE) {
|
||||
if (StructureLibAPI.isDebugEnabled() && success) {
|
||||
StructureLib.LOGGER.info(
|
||||
"Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
|
||||
}
|
||||
return success;
|
||||
} else {
|
||||
if (hintsOnly) {
|
||||
for (IStructureElement<T> element : elements) {
|
||||
if (element.isNavigating()) {
|
||||
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
|
||||
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
|
||||
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
|
||||
} else {
|
||||
extendedFacing.getWorldOffset(abc, xyz);
|
||||
xyz[0] += basePositionX;
|
||||
xyz[1] += basePositionY;
|
||||
xyz[2] += basePositionZ;
|
||||
|
||||
element.spawnHint(object, world, xyz[0], xyz[1], xyz[2], trigger);
|
||||
|
||||
abc[0] += 1;
|
||||
}
|
||||
}
|
||||
StructureUtility.iterateV2(
|
||||
elements,
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
ignoreBlockUnloaded((e, w, x, y, z) -> {
|
||||
e.spawnHint(object, world, x, y, z, trigger);
|
||||
return true;
|
||||
}),
|
||||
"spawnHint");
|
||||
} else {
|
||||
for (IStructureElement<T> element : elements) {
|
||||
if (element.isNavigating()) {
|
||||
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
|
||||
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
|
||||
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
|
||||
} else {
|
||||
extendedFacing.getWorldOffset(abc, xyz);
|
||||
xyz[0] += basePositionX;
|
||||
xyz[1] += basePositionY;
|
||||
xyz[2] += basePositionZ;
|
||||
|
||||
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
|
||||
element.placeBlock(object, world, xyz[0], xyz[1], xyz[2], trigger);
|
||||
}
|
||||
abc[0] += 1;
|
||||
}
|
||||
}
|
||||
StructureUtility.iterateV2(
|
||||
elements,
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
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;
|
||||
|
||||
@@ -4,4 +4,36 @@ import net.minecraft.world.World;
|
||||
|
||||
public interface IStructureWalker<T> {
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z);
|
||||
|
||||
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
-3
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package com.gtnewhorizon.structurelib.util;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor.APIType;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
import java.util.*;
|
||||
@@ -375,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) {
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, glee8e, Code Chicken.
|
||||
*
|
||||
* This file is originally part of NotEnoughtItem by Code Chicken.
|
||||
* It is adapted to implement the standard Map interface by glease
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.gtnewhorizon.structurelib.util;
|
||||
|
||||
import static net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterators;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
/**
|
||||
* A map class for ItemStack keys with wildcard damage/NBT. Optimised for lookup.
|
||||
* <p>
|
||||
* This map does NOT support null values or null keys! null will be silently ignored!
|
||||
* <p>
|
||||
* Originally created by CodeChicken for NotEnoughItems. Adapted to {@code Map<ItemStack, T>} interface by glee8e
|
||||
* @author CodeChiken
|
||||
* @author glee8e
|
||||
*/
|
||||
public final class ItemStackMap<T> extends AbstractMap<ItemStack, T> {
|
||||
public static final NBTTagCompound WILDCARD_TAG;
|
||||
|
||||
static {
|
||||
WILDCARD_TAG = new NBTTagCompound();
|
||||
WILDCARD_TAG.setBoolean("*", true);
|
||||
}
|
||||
|
||||
private final HashMap<Item, DetailMap> itemMap = new HashMap<>();
|
||||
private int size;
|
||||
|
||||
static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) {
|
||||
ItemStack stack = new ItemStack(item, size, damage);
|
||||
stack.stackTagCompound = tag;
|
||||
return stack;
|
||||
}
|
||||
|
||||
static int actualDamage(ItemStack stack) {
|
||||
return Items.diamond.getDamage(stack);
|
||||
}
|
||||
|
||||
static int getKeyType(int damage, NBTTagCompound tag) {
|
||||
int i = 0;
|
||||
if (isWildcard(damage)) i = 1;
|
||||
if (isWildcard(tag)) i |= 2;
|
||||
return i;
|
||||
}
|
||||
|
||||
static ItemStack wildcard(Item item) {
|
||||
return newItemStack(item, 1, WILDCARD_VALUE, WILDCARD_TAG);
|
||||
}
|
||||
|
||||
static boolean isWildcard(int damage) {
|
||||
return damage == WILDCARD_VALUE;
|
||||
}
|
||||
|
||||
static boolean isWildcard(NBTTagCompound tag) {
|
||||
return tag != null && tag.getBoolean("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(Object key) {
|
||||
if (!(key instanceof ItemStack)) return null;
|
||||
ItemStack stack = ((ItemStack) key);
|
||||
if (stack.getItem() == null) return null;
|
||||
DetailMap map = itemMap.get(stack.getItem());
|
||||
return map == null ? null : map.get(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(ItemStack key, T value) {
|
||||
if (key == null || key.getItem() == null || value == null) return null;
|
||||
return itemMap.computeIfAbsent(key.getItem(), k -> new DetailMap()).put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(Object key) {
|
||||
if (!(key instanceof ItemStack)) return null;
|
||||
ItemStack stack = ((ItemStack) key);
|
||||
if (stack.getItem() == null) return null;
|
||||
DetailMap map = itemMap.get(stack.getItem());
|
||||
return map == null ? null : map.remove(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (!(key instanceof ItemStack)) return false;
|
||||
ItemStack stack = ((ItemStack) key);
|
||||
if (stack.getItem() == null) return false;
|
||||
DetailMap map = itemMap.get(stack.getItem());
|
||||
return map != null && map.get(stack) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
itemMap.clear();
|
||||
size = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<ItemStack, T>> entrySet() {
|
||||
return new SetView();
|
||||
}
|
||||
|
||||
private static class StackMetaKey {
|
||||
public final int damage;
|
||||
public final NBTTagCompound tag;
|
||||
|
||||
public StackMetaKey(int damage, NBTTagCompound tag) {
|
||||
this.damage = damage;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public StackMetaKey(ItemStack key) {
|
||||
this(actualDamage(key), key.stackTagCompound);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(damage, tag);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof StackMetaKey)) return false;
|
||||
StackMetaKey t = (StackMetaKey) o;
|
||||
return damage == t.damage && Objects.equal(tag, t.tag);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DetailIter<T> implements Iterator<Map.Entry<ItemStack, T>> {
|
||||
private final Item owner;
|
||||
private final ItemStackMap<T>.DetailMap backing;
|
||||
|
||||
@Nullable
|
||||
private final Iterator<Map.Entry<Integer, T>> damageIter;
|
||||
|
||||
@Nullable
|
||||
private final Iterator<Map.Entry<NBTTagCompound, T>> tagIter;
|
||||
|
||||
@Nullable
|
||||
private final Iterator<Map.Entry<StackMetaKey, T>> metaIter;
|
||||
|
||||
private DetailIterState state = DetailIterState.NOT_STARTED;
|
||||
private boolean removed = false;
|
||||
|
||||
private DetailIter(Map.Entry<Item, ItemStackMap<T>.DetailMap> input) {
|
||||
this.owner = input.getKey();
|
||||
this.backing = input.getValue();
|
||||
damageIter =
|
||||
backing.damageMap != null ? backing.damageMap.entrySet().iterator() : null;
|
||||
tagIter = backing.tagMap != null ? backing.tagMap.entrySet().iterator() : null;
|
||||
metaIter = backing.metaMap != null ? backing.metaMap.entrySet().iterator() : null;
|
||||
}
|
||||
|
||||
private DetailIterState nextState() {
|
||||
switch (state) {
|
||||
case NOT_STARTED:
|
||||
if (backing.hasWildcard) return DetailIterState.WILDCARD;
|
||||
case WILDCARD:
|
||||
case DAMAGE:
|
||||
if (damageIter != null && damageIter.hasNext()) return DetailIterState.DAMAGE;
|
||||
case TAG:
|
||||
if (tagIter != null && tagIter.hasNext()) return DetailIterState.TAG;
|
||||
case META:
|
||||
if (metaIter != null && metaIter.hasNext()) return DetailIterState.META;
|
||||
case DONE:
|
||||
return DetailIterState.DONE;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected value: " + state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (removed) throw new IllegalStateException("remove() called twice");
|
||||
state.remove(this);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextState() != DetailIterState.DONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<ItemStack, T> next() {
|
||||
DetailIterState nextState = nextState();
|
||||
if (nextState == DetailIterState.DONE) throw new NoSuchElementException();
|
||||
state = nextState;
|
||||
removed = false;
|
||||
return nextState.get(this);
|
||||
}
|
||||
|
||||
private enum DetailIterState {
|
||||
NOT_STARTED {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
throw new AssertionError("Should not call get on NOT_STARTED");
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
throw new IllegalStateException("next() never called");
|
||||
}
|
||||
},
|
||||
WILDCARD {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
return new Map.Entry<ItemStack, T>() {
|
||||
private final ItemStack key = wildcard(iter.owner);
|
||||
|
||||
@Override
|
||||
public ItemStack getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
return iter.backing.wildcard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T setValue(T value) {
|
||||
return iter.backing.wildcard = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
assert iter.backing.hasWildcard;
|
||||
iter.backing.wildcard = null;
|
||||
iter.backing.hasWildcard = false;
|
||||
}
|
||||
},
|
||||
DAMAGE {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
assert iter.damageIter != null;
|
||||
Map.Entry<Integer, T> entry = iter.damageIter.next();
|
||||
return new ItemStackEntry<>(newItemStack(iter.owner, 1, entry.getKey(), WILDCARD_TAG), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
assert iter.damageIter != null;
|
||||
iter.damageIter.remove();
|
||||
}
|
||||
},
|
||||
TAG {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
assert iter.tagIter != null;
|
||||
Map.Entry<NBTTagCompound, T> entry = iter.tagIter.next();
|
||||
return new ItemStackEntry<>(newItemStack(iter.owner, 1, WILDCARD_VALUE, entry.getKey()), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
assert iter.tagIter != null;
|
||||
iter.tagIter.remove();
|
||||
}
|
||||
},
|
||||
META {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
assert iter.metaIter != null;
|
||||
Map.Entry<StackMetaKey, T> entry = iter.metaIter.next();
|
||||
return new ItemStackEntry<>(
|
||||
newItemStack(iter.owner, 1, entry.getKey().damage, entry.getKey().tag), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
assert iter.metaIter != null;
|
||||
iter.metaIter.remove();
|
||||
}
|
||||
},
|
||||
DONE {
|
||||
@Override
|
||||
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
|
||||
throw new AssertionError("Should not call get on DONE");
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> void remove(DetailIter<T> iter) {
|
||||
throw new AssertionError("Should not call remove on DONE");
|
||||
}
|
||||
};
|
||||
|
||||
abstract <T> Map.Entry<ItemStack, T> get(DetailIter<T> iter);
|
||||
|
||||
abstract <T> void remove(DetailIter<T> iter);
|
||||
|
||||
private static class ItemStackEntry<T> implements Map.Entry<ItemStack, T> {
|
||||
private final ItemStack key;
|
||||
private final Map.Entry<?, T> entry;
|
||||
|
||||
public ItemStackEntry(ItemStack key, Map.Entry<?, T> entry) {
|
||||
this.key = key;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T setValue(T value) {
|
||||
return entry.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DetailMap {
|
||||
private boolean hasWildcard;
|
||||
private T wildcard;
|
||||
private HashMap<Integer, T> damageMap;
|
||||
private HashMap<NBTTagCompound, T> tagMap;
|
||||
private HashMap<StackMetaKey, T> metaMap;
|
||||
private int size;
|
||||
|
||||
public T get(ItemStack key) {
|
||||
if (wildcard != null) return wildcard;
|
||||
|
||||
if (damageMap != null) {
|
||||
final T ret = damageMap.get(actualDamage(key));
|
||||
if (ret != null) return ret;
|
||||
}
|
||||
if (tagMap != null) {
|
||||
final T ret = tagMap.get(key.stackTagCompound);
|
||||
if (ret != null) return ret;
|
||||
}
|
||||
if (metaMap != null) return metaMap.get(new StackMetaKey(key));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T put(ItemStack key, T value) {
|
||||
try {
|
||||
switch (getKeyType(actualDamage(key), key.stackTagCompound)) {
|
||||
case 0:
|
||||
if (metaMap == null) metaMap = new HashMap<>();
|
||||
return metaMap.put(new StackMetaKey(key), value);
|
||||
case 1:
|
||||
if (tagMap == null) tagMap = new HashMap<>();
|
||||
return tagMap.put(key.stackTagCompound, value);
|
||||
case 2:
|
||||
if (damageMap == null) damageMap = new HashMap<>();
|
||||
return damageMap.put(actualDamage(key), value);
|
||||
case 3:
|
||||
T ret = wildcard;
|
||||
wildcard = value;
|
||||
hasWildcard = true;
|
||||
return ret;
|
||||
}
|
||||
} finally {
|
||||
updateSize();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public T remove(ItemStack key) {
|
||||
try {
|
||||
switch (getKeyType(actualDamage(key), key.stackTagCompound)) {
|
||||
case 0:
|
||||
return metaMap != null ? metaMap.remove(new StackMetaKey(key)) : null;
|
||||
case 1:
|
||||
return tagMap != null ? tagMap.remove(key.stackTagCompound) : null;
|
||||
case 2:
|
||||
return damageMap != null ? damageMap.remove(actualDamage(key)) : null;
|
||||
case 3:
|
||||
T ret = wildcard;
|
||||
wildcard = null;
|
||||
hasWildcard = false;
|
||||
return ret;
|
||||
}
|
||||
} finally {
|
||||
updateSize();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateSize() {
|
||||
int newSize = (hasWildcard ? 1 : 0)
|
||||
+ (metaMap != null ? metaMap.size() : 0)
|
||||
+ (tagMap != null ? tagMap.size() : 0)
|
||||
+ (damageMap != null ? damageMap.size() : 0);
|
||||
|
||||
if (newSize != size) {
|
||||
ItemStackMap.this.size += newSize - size;
|
||||
size = newSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SetView extends AbstractSet<Map.Entry<ItemStack, T>> {
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Map.Entry)) return false;
|
||||
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
|
||||
return java.util.Objects.equals(get(entry.getKey()), entry.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Map.Entry<ItemStack, T> itemStackTEntry) {
|
||||
return itemStackTEntry.getKey() != null
|
||||
&& itemStackTEntry.getValue() != null
|
||||
&& put(itemStackTEntry.getKey(), itemStackTEntry.getValue()) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (!(o instanceof Map.Entry)) return false;
|
||||
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
|
||||
return ItemStackMap.this.remove(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Iterator<Map.Entry<ItemStack, T>> iterator() {
|
||||
return Iterators.concat(Iterators.transform(itemMap.entrySet().iterator(), DetailIter::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>API Overview</TITLE>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
StructureLib API documentation.
|
||||
|
||||
<h1>Key API class outline</h1>
|
||||
<p>
|
||||
Here we outline a few key API classes/interfaces.
|
||||
</p>
|
||||
|
||||
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}</h2>
|
||||
<p>
|
||||
This is the structure definition of your multi.
|
||||
You will have one `IStructureDefinition` for each multi.
|
||||
</p>
|
||||
|
||||
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureElement}</h2>
|
||||
<p>
|
||||
These are how you define which block will be accepted.
|
||||
</p>
|
||||
|
||||
<h2> {@link com.gtnewhorizon.structurelib.structure.StructureUtility}</h2>
|
||||
<p>
|
||||
This is the standard library of {@link com.gtnewhorizon.structurelib.structure.IStructureElement
|
||||
IStructureElement}s.
|
||||
The Standard Library contains structure elements that handle basic block element and elements that allow composing
|
||||
these basic elements.
|
||||
</p>
|
||||
|
||||
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}</h2>
|
||||
<p>
|
||||
This interface should be implemented by tile entities that support creative autoplacing and hint particle emitting.
|
||||
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
|
||||
IStructureDefinition}
|
||||
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#build(Object, ItemStack, String,
|
||||
World, ExtendedFacing, int, int, int, int, int, int) build()}
|
||||
or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#hints(Object, ItemStack, String, World,
|
||||
ExtendedFacing, int, int, int, int, int, int) hints()} method here.
|
||||
</p>
|
||||
|
||||
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}</h2>
|
||||
<p>
|
||||
This interface is an enhancement over {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable
|
||||
IConstructable}.
|
||||
If your tile entities also support survival autoplacing then you should implement this.
|
||||
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
|
||||
IStructureDefinition}
|
||||
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack,
|
||||
String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)
|
||||
survivalBuild()}.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}</h2>
|
||||
<p>
|
||||
This interface should be implemented on tile entities that will delegate all constructable or survival construable
|
||||
functionality to some other class.
|
||||
This is mostly only to support gregtech meta tile entity system.
|
||||
</p>
|
||||
<p>
|
||||
Return null if the delegate does not exist.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.util.InventoryUtility}</h2>
|
||||
<p>
|
||||
This class backs the standard implementations of {@link com.gtnewhorizon.structurelib.structure.IItemSource}.
|
||||
You might want to register new inventory provider or inventory extractor there to facilitate survival auto place.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link
|
||||
com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link
|
||||
com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}</h2>
|
||||
<p>
|
||||
These interfaces will be used to implement rotation.
|
||||
Provide a framework to rotate the block.
|
||||
You can skip this if you store ExtendedFacing and do rotation in other means.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}</h2>
|
||||
<p>
|
||||
This class contains the accessors for channel data held on a trigger item. See <a href="#channels">below</a> for more
|
||||
information.
|
||||
</p>
|
||||
|
||||
<h2>{@link com.gtnewhorizon.structurelib.StructureLibAPI}</h2>
|
||||
<p>
|
||||
This class contains the low level utility used for implementing new {@link
|
||||
com.gtnewhorizon.structurelib.structure.IStructureElement}.
|
||||
You will also need methods in this class to while implementing rotation.
|
||||
</p>
|
||||
<p>
|
||||
<b>IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS.</b>
|
||||
This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered)
|
||||
without
|
||||
breaking client code.
|
||||
</p>
|
||||
|
||||
<h1>Important Concepts</h1>
|
||||
<h2>Context Object</h2>
|
||||
<p>
|
||||
Context Object are what you pass to {@link
|
||||
com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int,
|
||||
int, int, int, int, boolean)} and alike as first parameter. It will hold the necessary states for {@link
|
||||
com.gtnewhorizon.structurelib.structure.IStructureElement}. It's usually, but not necessarily, the multiblock controller.
|
||||
</p>
|
||||
<p>
|
||||
Standard structure element library offers method to switch context objects if needed.
|
||||
</p>
|
||||
|
||||
<h2>Trigger Item</h2>
|
||||
<p>
|
||||
Trigger Item is the item used to trigger hint spawn, creative build and survival auto place.
|
||||
The trigger item will be passed to relevant classes as an indicator of structure tier.
|
||||
The item type and stack tag compound of this item stack are opaque objects that should not be parsed.
|
||||
</p>
|
||||
<p>
|
||||
StructureLib comes with a trigger item implementation.
|
||||
It does register it to the game registries, but does not come with a recipe.
|
||||
Usually there is no reason to reimplement this on your own.
|
||||
</p>
|
||||
|
||||
<h2><a href="#channels" id="channels">Channels</a></h2>
|
||||
|
||||
<p>
|
||||
Trigger item carries the player's choice of structure tier by its stack size.
|
||||
When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious
|
||||
restriction, as one ItemStack cannot have two stack sizes at the same time.
|
||||
Here we use the <i>channels</i> to bypass this restriction.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Basically, channels are information stored on the trigger item's NBT tags.
|
||||
One trigger item can carry multiple channels.
|
||||
Each channel can be identified with <b>lower case</b> strings and has the value being one positive Java int.
|
||||
There will be one and only one master channel and 0 or more sub channel.
|
||||
When a non-existent sub channel is queried, master channel will be used instead to fill its place.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel
|
||||
to use.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Structure Element Author will usually not need to consider channels, i.e. proceed as if this concept does not exist,
|
||||
unless it must take input from multiple channels.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Structure Definition Author will consider how to map each channel to each tiered component.
|
||||
He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element
|
||||
on which channel to use.
|
||||
Conceptually, it will return an ItemStack with the master channel being the value of selected channel.
|
||||
In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It's worth mentioning that the returned new ItemStack will still have all the channel data on it, so a multi-channel
|
||||
structure element can still query the channel it wants just fine. It is however impossible to recover the original
|
||||
master channel.
|
||||
</p>
|
||||
|
||||
<h3>why is this called channel?</h3>
|
||||
|
||||
Just give me a better name and I'd do the renaming.
|
||||
|
||||
<h3>Examples</h3>
|
||||
|
||||
<h4>For Structure Definition Author</h4>
|
||||
|
||||
<p>GT++ Chemical Plant is a multi with 4 distinctly tiered component.</p>
|
||||
|
||||
<p>It will use
|
||||
<ul>
|
||||
<li>master channel for its outer shell</li>
|
||||
<li>channel `"casing"` for machine casings</li>
|
||||
<li>channel `"pipe"` for pipe casings</li>
|
||||
<li>channel `"coil"` for heating coils</li>
|
||||
</ul>
|
||||
Its structure code will then look like
|
||||
<pre>
|
||||
.addElement('a', shellStructureElement())
|
||||
.addElement('b', withChannel("casing", machineCasingStructureElement()))
|
||||
.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
|
||||
.addElement('d', withChannel("coil", coilStructureElement()))
|
||||
</pre>
|
||||
whereas all these structure elements are designed without considering channels whatsoever
|
||||
|
||||
<h4> Multi channel input Structure Element</h4>
|
||||
|
||||
<p>
|
||||
Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both
|
||||
meta id
|
||||
and facing input.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It will use
|
||||
<ul>
|
||||
<li>master channel for its facing</li>
|
||||
<li>channel `"id"` for meta tile entity id</li>
|
||||
</ul>
|
||||
<p>
|
||||
Its code will then look like
|
||||
<pre>
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureElement;
|
||||
|
||||
public class MyHatchElement<T> implements IStructureElement<T> {
|
||||
//...
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(
|
||||
Object o,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer chatter) {
|
||||
if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
|
||||
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
||||
ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
|
||||
if (taken == null) return PlaceResult.REJECT;
|
||||
return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -26,12 +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 |
Reference in new issue
Block a user