Compare commits

...
Author SHA1 Message Date
Martin Robertz 517aabf219 Merge branch 'master' into structure-sockets-and-controller-pos 2026-04-04 14:38:08 +02:00
Martin Robertz 40edf05109 update+gradle+bs+deps 2026-04-03 22:32:39 +02:00
evgengoldwar 4e5a92aff7 Add more info (#56)
Fixing the issue where the ofBlock method does not show a hint about
which block cannot be placed.
<img width="942" height="261" alt="image"
src="https://github.com/user-attachments/assets/2162c68e-e7ef-4fa4-ab86-61321884bfe7"
/>
2026-03-26 15:23:20 +01:00
Martin Robertz afc90899f9 Merge branch 'master' into structure-sockets-and-controller-pos 2026-03-10 20:22:28 +01:00
UltraProdigy a09e6058a9 Optimize Image & Workflow (#55) 2026-03-07 09:38:45 +01:00
Martin Robertz f4546ac4f8 fix 2026-03-05 09:15:43 +01:00
Martin Robertz 15e9be364a Merge branch 'master' into structure-sockets-and-controller-pos 2026-03-02 11:11:40 +01:00
danyadev e774dd5e10 optimize StructureDefinition.addShape (#54)
Before: 161ms of loading time
After: 60.7ms of loading time

An example with Forge of Gods
<img width="1917" height="513" alt="image"
src="https://github.com/user-attachments/assets/61e9b420-7cc2-4e76-89f3-864bd6d7a77f"
/>
<img width="1918" height="490" alt="image"
src="https://github.com/user-attachments/assets/5297956c-41c8-43f7-9451-e29e26609a7e"
/>
2026-03-01 13:03:05 +01:00
Martin Robertz e864b9b292 Merge branch 'master' into structure-sockets-and-controller-pos 2026-01-05 22:06:47 +01:00
Martin Robertz 0866dbee98 fix 2026-01-05 22:05:18 +01:00
Martin Robertz 208f7ffc41 update deps+bs+gradle 2026-01-05 21:28:00 +01:00
Martin Robertz 4f1edf1319 Merge branch 'master' into structure-sockets-and-controller-pos 2026-01-05 21:25:48 +01:00
Worive 88a6021546 Autocomplete channel name with tab (#53) 2026-01-05 10:21:14 +08:00
UltraProdigy a05267a566 Replace Dot Hints With Number Hints (#52)
As title says, makes the holograms much more readable.

Textures By: bapy_
Original PR By: kstvr32

Supercedes: https://github.com/GTNewHorizons/StructureLib/pull/32

Examples:
<img width="2534" height="1351" alt="image"
src="https://github.com/user-attachments/assets/c24f60b2-d654-4b15-b917-4003141a2b69"
/>
<img width="2538" height="1350" alt="image"
src="https://github.com/user-attachments/assets/a5ca3f42-ca96-4675-8e80-c89fba359fbf"
/>
2025-12-17 08:04:19 +01:00
Wilhelm Schuster dd3413dcaa Don't enable buttons in channel configuration when channel key is empty (#51)
The channel configuration GUI would allow pressing the "Add" button even
though the channel key field was empty, leading to a crash.

fixes
https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/22838
2025-12-15 09:33:15 +08:00
RecursivePineapple 26b88a5f27 disable spotless for new test class 2025-11-02 14:41:25 -05:00
RecursivePineapple 87903fead7 Defer shape compilation into .build() + socket cleanup 2025-11-02 14:40:20 -05:00
RecursivePineapple 1166c651d2 Add coordinate system tests 2025-11-02 14:39:50 -05:00
RecursivePineapple 7f42cc1de8 Add useful methods & constructors to Position 2025-11-02 14:31:56 -05:00
Martin Robertz 34394ef8a5 update 2025-10-28 21:07:20 +01:00
RecursivePineapple ff3904ec1a Merge branch 'master' into structure-sockets-and-controller-pos 2025-10-28 12:34:10 -04:00
RecursivePineapple c3671609d3 Track structure controller position and add structure sockets 2025-10-28 12:34:01 -04:00
RecursivePineapple 7c0d3f4bd6 Expose ability to iterate over structures (#49) 2025-10-28 12:52:40 +08:00
Martin Robertz cd4f3617be update 2025-09-20 15:44:09 +02:00
Wilhelm Schuster 256bc60d35 Fix crash trying to open Hologram Projector GUI on java 8 (#48) 2025-09-19 00:16:47 +08:00
Martin Robertz 70426ab7b3 update 2025-09-17 13:53:22 +02:00
Wilhelm Schuster 0030c02e9a Small improvements to the channel config GUI (#47) 2025-09-15 22:06:29 +02:00
Wilhelm Schuster 7382db1bbd Fix Hologram Projector Shift-Use opening both GUIs (#46) 2025-09-14 19:00:59 +02:00
Wilhelm Schuster b669cbecf1 Fix Hologram Projector config GUI not releasing cursor (#45) 2025-09-14 10:21:21 +02:00
46 changed files with 848 additions and 138 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
name: Optimize images
on:
pull_request:
branches: [ master, main, release/** ]
paths:
- "**/*.png"
push:
branches: [ master, main, release/** ]
paths:
- "**/*.png"
jobs:
optimize-images:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/optimize-images.yml@master
secrets: inherit
+3 -2
View File
@@ -1,9 +1,10 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.6.38:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.69-GTNH:dev")
api("com.github.GTNewHorizons:GTNHLib:0.9.47:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.84-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
}
+39 -4
View File
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# Enables modern Java syntax support. Valid values:
# - false: No modern syntax, Java 8 only
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
# - modern: Native modern Java bytecode, no downgrading
enableModernJavaSyntax = jabel
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Target JVM version for JVM Downgrader bytecode downgrading.
# Only used when enableModernJavaSyntax = jvmDowngrader
# downgradeTargetVersion = 8
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
# Classes will be available in META-INF/versions/N/ for each version N in this list.
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
# jvmDowngraderMultiReleaseVersions = 21,25
# Specifies how JVM Downgrader API stubs are provided. Options:
# - shade: Shade minimized stubs into the jar
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
# - external: Another dependency provides stubs (no constraint, no warning)
# - (empty): Warning reminding you to configure stubs
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
# jvmDowngraderStubsProvider =
# Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
@@ -96,7 +120,9 @@ usesMixinDebug = false
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin =
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
# Specify the package that contains all of your Mixins. The package must exist or
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
# it must match with this or the build will fail.
mixinsPackage = mixins
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
@@ -172,6 +198,12 @@ curseForgeRelations =
# projects. New projects should not use this parameter.
# customArchiveBaseName =
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
# runClientWorkingDirectory = run/client
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
# runServerWorkingDirectory = run/server
# Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag.
@@ -201,3 +233,6 @@ curseForgeRelations =
# This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.parallel = true
+12
View File
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d4fd992c9557644e637ebe98263e0ae7/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/faa12903720d410b387cc69ccafb1a74/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+3 -6
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+1 -2
View File
@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.41'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.24'
}
@@ -5,7 +5,11 @@ import static java.lang.Math.abs;
import net.minecraft.util.Vec3;
import net.minecraftforge.common.util.ForgeDirection;
import org.joml.Vector3i;
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
import com.gtnewhorizon.structurelib.coords.CoordinateSystem;
import com.gtnewhorizon.structurelib.coords.Position;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
public class IntegerAxisSwap {
@@ -91,4 +95,32 @@ public class IntegerAxisSwap {
out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2];
out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2];
}
public Vector3i translate(Vector3i point) {
return new Vector3i(
forFirstAxis.get0() * point.x() + forFirstAxis.get1() * point.y() + forFirstAxis.get2() * point.z(),
forSecondAxis.get0() * point.x() + forSecondAxis.get1() * point.y() + forSecondAxis.get2() * point.z(),
forThirdAxis.get0() * point.x() + forThirdAxis.get1() * point.y() + forThirdAxis.get2() * point.z());
}
public Vector3i inverseTranslate(Vector3i point) {
return new Vector3i(
forFirstAxis.get0() * point.x() + forSecondAxis.get0() * point.y() + forThirdAxis.get0() * point.z(),
forFirstAxis.get1() * point.x() + forSecondAxis.get1() * point.y() + forThirdAxis.get1() * point.z(),
forFirstAxis.get2() * point.x() + forSecondAxis.get2() * point.y() + forThirdAxis.get2() * point.z());
}
public <C extends CoordinateSystem<C, ?>> Position<C> translate(Position<?> point) {
return new Position<>(
forFirstAxis.get0() * point.x() + forFirstAxis.get1() * point.y() + forFirstAxis.get2() * point.z(),
forSecondAxis.get0() * point.x() + forSecondAxis.get1() * point.y() + forSecondAxis.get2() * point.z(),
forThirdAxis.get0() * point.x() + forThirdAxis.get1() * point.y() + forThirdAxis.get2() * point.z());
}
public <C extends CoordinateSystem<C, ?>> Position<C> inverseTranslate(Position<?> point) {
return new Position<>(
forFirstAxis.get0() * point.x() + forSecondAxis.get0() * point.y() + forThirdAxis.get0() * point.z(),
forFirstAxis.get1() * point.x() + forSecondAxis.get1() * point.y() + forThirdAxis.get1() * point.z(),
forFirstAxis.get2() * point.x() + forSecondAxis.get2() * point.y() + forThirdAxis.get2() * point.z());
}
}
@@ -24,6 +24,7 @@ import net.minecraftforge.common.util.ForgeDirection;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
import com.gtnewhorizon.structurelib.coords.StructureRelativeCoords;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
public enum ExtendedFacing {
@@ -166,6 +167,7 @@ public enum ExtendedFacing {
private final String name;
private final IntegerAxisSwap integerAxisSwap;
private final StructureRelativeCoords coordinateSystem;
ExtendedFacing(String name) {
this.name = name;
@@ -247,6 +249,7 @@ public enum ExtendedFacing {
this.b = b;
this.c = c;
integerAxisSwap = new IntegerAxisSwap(a, b, c);
coordinateSystem = new StructureRelativeCoords(integerAxisSwap);
}
public static ExtendedFacing of(ForgeDirection direction, Rotation rotation, Flip flip) {
@@ -437,4 +440,8 @@ public enum ExtendedFacing {
public ForgeDirection getRelativeForwardInWorld() {
return c.getOpposite();
}
public StructureRelativeCoords asCoordinateSystem() {
return coordinateSystem;
}
}
@@ -50,6 +50,10 @@ public class BlockHint extends Block {
@Override
public IIcon getIcon(int aSide, int aMeta) {
// For numbered hints, top and bottom are blank
if (aSide <= 1 && aMeta <= 11) {
return hint[13];
}
return hint[aMeta];
}
@@ -0,0 +1,40 @@
package com.gtnewhorizon.structurelib.coords;
/**
* Controller-relative coordinates. The controller is at 0,0,0. No axis swapping takes place, each axis points in the
* same directions as the matching world axis.
*/
public class ControllerRelativeCoords implements CoordinateSystem<ControllerRelativeCoords, WorldCoords> {
public final int controllerX, controllerY, controllerZ;
public ControllerRelativeCoords(int controllerX, int controllerY, int controllerZ) {
this.controllerX = controllerX;
this.controllerY = controllerY;
this.controllerZ = controllerZ;
}
@Override
public Position<ControllerRelativeCoords> translate(Position<WorldCoords> position) {
return translate(position, controllerX, controllerY, controllerZ);
}
@Override
public Position<WorldCoords> translateInverse(Position<ControllerRelativeCoords> position) {
return translateInverse(position, controllerX, controllerY, controllerZ);
}
public static Position<ControllerRelativeCoords> translate(Position<WorldCoords> position, int controllerX,
int controllerY, int controllerZ) {
position.sub(controllerX, controllerY, controllerZ);
return CoordinateSystem.transmute(position);
}
public static Position<WorldCoords> translateInverse(Position<ControllerRelativeCoords> position, int controllerX,
int controllerY, int controllerZ) {
position.add(controllerX, controllerY, controllerZ);
return CoordinateSystem.transmute(position);
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizon.structurelib.coords;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
/**
* A coordinate system is a statically-checked helper for determining which coordinates a {@link Position} has.
* Translation methods never copy a {@link Position} - if a position must remain unchanged, it must be copied
* separately. Most coordinate systems will have static methods that allow you to avoid allocations, though some (such
* as the ones provided by {@link ExtendedFacing#asCoordinateSystem()}) have no reason to be static.
*
* @param <Self>
* @param <Parent>
*/
public interface CoordinateSystem<Self extends CoordinateSystem<Self, Parent>, Parent extends CoordinateSystem<Parent, ?>> {
/**
* Translates a position from the parent coordinate system into this one.
*/
Position<Self> translate(Position<Parent> position);
/**
* Translates a position from this coordinate system into the parent.
*/
Position<Parent> translateInverse(Position<Self> position);
/**
* Helper method that converts a position's coordinate system. Should not be used unless you know it's correct.
*/
static <P1 extends CoordinateSystem<P1, ?>, P2 extends CoordinateSystem<P2, ?>> Position<P1> transmute(
Position<P2> pos) {
// noinspection unchecked
return (Position<P1>) pos;
}
}
@@ -0,0 +1,73 @@
package com.gtnewhorizon.structurelib.coords;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import net.minecraft.world.ChunkPosition;
import org.joml.Vector3f;
import org.joml.Vector3i;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
/**
* A {@link BlockPos} with a statically-checked coordinate system.
*/
public class Position<C extends CoordinateSystem<C, ?>> extends BlockPos {
public Position() {
}
public Position(int x, int y, int z) {
super(x, y, z);
}
public Position(float x, float y, float z) {
super(MathHelper.floor_float(x), MathHelper.floor_float(y), MathHelper.floor_float(z));
}
public Position(double x, double y, double z) {
super(MathHelper.floor_double(x), MathHelper.floor_double(y), MathHelper.floor_double(z));
}
public Position(ChunkPosition chunkPosition) {
super(chunkPosition);
}
public Position(Vector3i v) {
super(v.x, v.y, v.z);
}
public Position(Vector3f v) {
this(v.x, v.y, v.z);
}
public Position(Vec3 v) {
this(v.xCoord, v.yCoord, v.zCoord);
}
public Position(Vec3Impl v) {
this(v.get0(), v.get1(), v.get2());
}
public Position<C> copy() {
return new Position<>(x, y, z);
}
public Vector3i toVector3i() {
return new Vector3i(x, y, z);
}
public Vector3f toVector3f() {
return new Vector3f(x, y, z);
}
public Vec3 toVec3() {
return Vec3.createVectorHelper(x, y, z);
}
public Vec3Impl toVec3Impl() {
return new Vec3Impl(x, y, z);
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizon.structurelib.coords;
/// The structure definition-local coords. 0,0,0 is the front-most left-most top-most element. Coordinates increase as
/// you go down, right, or back.
public class StructureDefinitionCoords implements CoordinateSystem<StructureDefinitionCoords, StructureRelativeCoords> {
public final int offsetX, offsetY, offsetZ;
public StructureDefinitionCoords(int offsetX, int offsetY, int offsetZ) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
}
@Override
public Position<StructureDefinitionCoords> translate(Position<StructureRelativeCoords> position) {
return translate(position, offsetX, offsetY, offsetZ);
}
@Override
public Position<StructureRelativeCoords> translateInverse(Position<StructureDefinitionCoords> position) {
return translateInverse(position, offsetX, offsetY, offsetZ);
}
public static Position<StructureDefinitionCoords> translate(Position<StructureRelativeCoords> position, int offsetX,
int offsetY, int offsetZ) {
position.add(offsetX, offsetY, offsetZ);
return CoordinateSystem.transmute(position);
}
public static Position<StructureRelativeCoords> translateInverse(Position<StructureDefinitionCoords> position,
int offsetX, int offsetY, int offsetZ) {
position.sub(offsetX, offsetY, offsetZ);
return CoordinateSystem.transmute(position);
}
}
@@ -0,0 +1,31 @@
package com.gtnewhorizon.structurelib.coords;
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
/**
* Coordinates relative to the structure. 0,0,0 is the controller. 0,0,1 is the block immediately behind the controller
* (looking at it from its face). 1,0,0 is the block to the right of the controller. 0,1,0 is the block below the
* controller.
*/
public class StructureRelativeCoords implements CoordinateSystem<StructureRelativeCoords, ControllerRelativeCoords> {
public final IntegerAxisSwap axisSwap;
public StructureRelativeCoords(IntegerAxisSwap axisSwap) {
this.axisSwap = axisSwap;
}
@Override
public Position<StructureRelativeCoords> translate(Position<ControllerRelativeCoords> position) {
position.set(axisSwap.translate(position));
return CoordinateSystem.transmute(position);
}
@Override
public Position<ControllerRelativeCoords> translateInverse(Position<StructureRelativeCoords> position) {
position.set(axisSwap.inverseTranslate(position));
return CoordinateSystem.transmute(position);
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizon.structurelib.coords;
/**
* World block coordinates
*/
public class WorldCoords implements CoordinateSystem<WorldCoords, WorldCoords> {
public static final WorldCoords INSTANCE = new WorldCoords();
private WorldCoords() {}
@Override
public Position<WorldCoords> translate(Position<WorldCoords> position) {
return position;
}
@Override
public Position<WorldCoords> translateInverse(Position<WorldCoords> position) {
return position;
}
}
@@ -164,40 +164,17 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
list.onGuiInit(this);
autoCompleteList.onGuiInit(this);
addButton(
new GuiButton(
ADD_BTN,
guiLeft + 12,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.add")));
addButton(
new GuiButton(
UNSET_BTN,
guiLeft + 65,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.unset")));
addButton(
new GuiButton(
WIPE_BTN,
guiLeft + 118,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.wipe")));
addButton(new GuiButton(ADD_BTN, guiLeft + 12, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "add")));
addButton(new GuiButton(UNSET_BTN, guiLeft + 65, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "unset")));
addButton(new GuiButton(WIPE_BTN, guiLeft + 118, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "wipe")));
updateButtons();
tooltipSplitCache.clear();
info = StreamSupport
.stream(
Splitter.on("\\n").split(
StatCollector.translateToLocal("item.structurelib.constructableTrigger.gui.info"))
.spliterator(),
Splitter.on("\\n").split(StatCollector.translateToLocal(I18N_PREFIX + "info")).spliterator(),
false)
.flatMap(line -> fontRendererObj.listFormattedStringToWidth(line, width * 3 / 5).stream())
.flatMap(line -> fontRendererObj.listFormattedStringToWidth(line, width * 1 / 3).stream())
.collect(Collectors.toList());
}
@@ -241,8 +218,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
actionPerformed(but);
}
@SuppressWarnings("unchecked")
private List<GuiButton> getButtonList() {
public List<GuiButton> getButtonList() {
return buttonList;
}
@@ -285,6 +261,11 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
switch (aKey) {
case Keyboard.KEY_TAB:
if (key.isFocused()) {
if (autoCompleteList.getSelectedElement() != null) {
key.setText(autoCompleteList.getSelectedElement());
return;
}
key.setFocused(false);
value.setFocused(true);
} else {
@@ -322,11 +303,10 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
}
break;
}
if (key.textboxKeyTyped(aChar, aKey)) {
if (key.textboxKeyTyped(aChar, aKey) || value.textboxKeyTyped(aChar, aKey)) {
updateButtons();
return;
}
if (value.textboxKeyTyped(aChar, aKey)) return;
super.keyTyped(aChar, aKey);
}
@@ -337,7 +317,8 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
getButtonList().get(ADD_BTN).displayString = existing ? I18n.format(I18N_PREFIX + "set")
: I18n.format(I18N_PREFIX + "add");
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(value.getText())
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText())
&& !StringUtils.isBlank(value.getText())
&& Integer.parseInt(value.getText()) > 0;
getButtonList().get(UNSET_BTN).enabled = existing && !StringUtils.isBlank(value.getText());
@@ -397,22 +378,10 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
int topLeftY = (this.height - this.getYSize()) / 2;
drawTexturedModalRect(topLeftX, topLeftY, 0, 0, getXSize(), getYSize());
list.drawScreen(mX, mY, partialTick);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.title"),
guiLeft + 12,
guiTop + 9,
0);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.key"),
guiLeft + 12,
guiTop + 122,
0);
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "title"), guiLeft + 12, guiTop + 9, 0);
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "key"), guiLeft + 12, guiTop + 122, 0);
key.drawTextBox();
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.value"),
guiLeft + 12,
guiTop + 142,
0);
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "value"), guiLeft + 12, guiTop + 142, 0);
value.drawTextBox();
}
@@ -455,7 +424,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
tooltip.addAll(
fontRendererObj.listFormattedStringToWidth(
StatCollector.translateToLocalFormatted(
"item.structurelib.constructableTrigger.gui.channels.from",
I18N_PREFIX + "channels.from",
Loader.instance().getIndexedModList().get(e.getKey()).getName()),
maxLine));
tooltip.addAll(
@@ -28,11 +28,12 @@ public class ItemConstructableTrigger extends Item {
@Override
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
if (!world.isRemote && getMovingObjectPositionFromPlayer(world, player, true) == null) {
if (player.isSneaking()) {
StructureLib.instance().proxy().displayConfigGUI("registries");
} else {
if (getMovingObjectPositionFromPlayer(world, player, true) == null) {
// channel gui is opened from server and config gui from client
if (!world.isRemote && !player.isSneaking()) {
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
} else if (world.isRemote && player.isSneaking()) {
StructureLib.instance().proxy().displayConfigGUI("registries");
}
}
return super.onItemRightClick(stack, world, player);
@@ -3,8 +3,11 @@ package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
import java.util.List;
import java.util.function.Function;
import javax.annotation.Nonnull;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
@@ -15,6 +18,8 @@ import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.coords.Position;
import com.gtnewhorizon.structurelib.coords.StructureDefinitionCoords;
/**
* This is the structure definition of your multi. You will have one of these for each multi.
@@ -319,6 +324,65 @@ public interface IStructureDefinition<T> {
return walker.getBuilt();
}
default void iterate(String piece, World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY,
int basePositionZ, int basePositionA, int basePositionB, int basePositionC, IStructureWalker<T> predicate) {
StructureUtility.iterateV2(
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
predicate,
"customIterate");
}
/**
* Gets the controller position for a piece. Controller positions are denoted by a tilde.
*
* @param piece The piece name
* @return The controller position, or null if it doesn't exist for the given piece
*/
Position<StructureDefinitionCoords> getControllerPosition(String piece);
/**
* Gets the coordinate system for a piece. This is used to translate structure definition coordinates into structure
* relative coordinates.
*
* @param piece The piece
* @return The coordinate system
* @throws IllegalArgumentException When the piece does not exist
*/
StructureDefinitionCoords getCoordinateSystem(String piece);
/**
* Gets a socket from a piece.
*
* @param piece The piece name
* @param socket The socket character
* @return The socket's position
* @throws IllegalArgumentException When the piece does not exist
* @throws IllegalArgumentException When the socket does not exist
* @throws IllegalStateException When more than one socket exists with this character
* @see StructureDefinition.Builder#addSocket(char, char)
*/
Position<StructureDefinitionCoords> getSocket(String piece, char socket);
/**
* Gets all sockets with a given character for a piece.
*
* @param piece The piece name
* @param socket The socket character
* @return The list of socket positions, or an empty list if the socket is not present
* @throws IllegalArgumentException When the piece does not exist
* @see StructureDefinition.Builder#addSocket(char, char)
*/
@Nonnull
List<Position<StructureDefinitionCoords>> getAllSockets(String piece, char socket);
/**
* Low level utility.
*
@@ -2,8 +2,25 @@ package com.gtnewhorizon.structurelib.structure;
import net.minecraft.world.World;
interface IStructureWalker<T> {
/**
* A functional interface that is used to iterate over an arbitrary structure.
*/
@FunctionalInterface
public interface IStructureWalker<T> {
/**
* Called once per structure element.
*
* @param element The current element
* @param world The current world
* @param x The absolute X position of the element in world coordinates
* @param y The absolute Y position of the element in world coordinates
* @param z The absolute Z position of the element in world coordinates
* @param a The relative A position of the element in structure-local coordinates (pre transform)
* @param b The relative B position of the element in structure-local coordinates (pre transform)
* @param c The relative C position of the element in structure-local coordinates (pre transform)
* @return False to stop iterating, true to continue.
*/
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
@@ -4,33 +4,49 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.isAir;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.notAir;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.gtnewhorizon.structurelib.coords.Position;
import com.gtnewhorizon.structurelib.coords.StructureDefinitionCoords;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import it.unimi.dsi.fastutil.chars.Char2CharOpenHashMap;
import it.unimi.dsi.fastutil.chars.CharOpenHashSet;
public class StructureDefinition<T> implements IStructureDefinition<T> {
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, IStructureElement<T>[]> structures;
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
private final Map<String, Position<StructureDefinitionCoords>> controllers;
private final Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations;
public static <B> Builder<B> builder() {
return new Builder<>();
}
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces,
Map<String, Position<StructureDefinitionCoords>> controllers,
Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations) {
this.elements = elements;
this.shapes = shapes;
this.structures = structures;
this.occupiedSpaces = occupiedSpaces;
this.controllers = controllers;
this.socketLocations = socketLocations;
}
public static class Builder<T> {
@@ -41,12 +57,18 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
private char d = '\uD000';
private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String[][]> uncompiledShapes;
private final Map<String, String> shapes;
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
private final Map<String, Position<StructureDefinitionCoords>> controllers = new HashMap<>();
private final Char2CharOpenHashMap socketTranslations = new Char2CharOpenHashMap();
private final Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations = new HashMap<>();
private Builder() {
navigates = new HashMap<>();
elements = new HashMap<>();
uncompiledShapes = new HashMap<>();
shapes = new HashMap<>();
occupiedSpaces = new HashMap<>();
}
@@ -82,7 +104,69 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
* @return this builder
*/
public Builder<T> addShape(String name, String[][] structurePiece) {
uncompiledShapes.put(name, structurePiece);
return this;
}
/**
* @deprecated use the unboxed version
*/
@Deprecated
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
/**
* Denotes {@code name} as a socket. Sockets are virtual structure elements that record their position in the
* structure for future retrieval. They are transformed into real structure elements and do not need their own
* element (see {@code replacement}).
*
* @param name The socket name character
* @param replacement The replacement character
* @see IStructureDefinition#getSocket(String, char)
* @see IStructureDefinition#getAllSockets(String, char)
*/
public Builder<T> addSocket(char name, char replacement) {
socketTranslations.put(name, replacement);
return this;
}
/**
* Like {@link #addSocket(char, char)} but without the replacement. The structure must have a structure element
* with the given character.
*
* @param name The socket name character
*/
public Builder<T> addSocket(char name) {
// Adding a name -> name entry like this isn't ideal but this is the cleanest solution
socketTranslations.put(name, name);
return this;
}
public IStructureDefinition<T> build() {
uncompiledShapes.forEach(this::compileShape);
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
return new StructureDefinition<>(
new HashMap<>(elements),
new HashMap<>(shapes),
structures,
occupiedSpaces,
controllers,
socketLocations);
}
private Builder<T> compileShape(String name, String[][] structurePiece) {
StringBuilder builder = new StringBuilder();
if (structurePiece.length > 0) {
for (String[] strings : structurePiece) {
if (strings.length > 0) {
@@ -95,6 +179,11 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
builder.setLength(builder.length() - 1);
}
// Always create a map for this piece, even if it doesn't have any sockets
// noinspection UnstableApiUsage
socketLocations.put(name, MultimapBuilder.hashKeys().arrayListValues().build());
Set<Vec3Impl> occupiedSpace = new HashSet<>();
// these track the global current location
int aa = 0, bb = 0, cc = 0;
@@ -103,6 +192,15 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
// I do know these are pretty bad variable naming, but I have no better idea
for (int i = 0; i < builder.length(); i++) {
char ch = builder.charAt(i);
// Translate the element char if it's a socket, and record the socket position
if (socketTranslations.containsKey(ch)) {
socketLocations.get(name).put(ch, new Position<>(aa, bb, cc));
ch = socketTranslations.get(ch);
builder.setCharAt(i, ch);
}
if (ch == ' ') {
builder.setCharAt(i, A);
ch = A;
@@ -110,6 +208,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
builder.setCharAt(i, A);
ch = A;
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
controllers.put(name, new Position<>(aa, bb, cc));
}
if (ch == A) {
aa++;
@@ -148,76 +247,87 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
occupiedSpaces.put(name, occupiedSpace);
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
boolean hasPlus = false;
boolean hasMinus = false;
if (built.contains("+")) {
int write = 0;
for (int read = 0; read < builder.length(); read++) {
char ch = builder.charAt(read);
if (ch == A || ch == B || ch == C) {
continue;
}
if (ch == '+') {
hasPlus = true;
} else if (ch == '-') {
hasMinus = true;
}
if (read != write) builder.setCharAt(write, ch);
write++;
}
builder.setLength(write);
if (hasPlus) {
addElement('+', notAir());
}
if (built.contains("-")) {
if (hasMinus) {
addElement('-', isAir());
}
shapes.put(name, built);
shapes.put(name, builder.toString());
return this;
}
/**
* @deprecated use the unboxed version
*/
@Deprecated
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public IStructureDefinition<T> build() {
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
return new StructureDefinition<>(
new HashMap<>(elements),
new HashMap<>(shapes),
structures,
occupiedSpaces);
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().chars().mapToObj(c -> (char) c).distinct().map(elements::get)
.toArray(IStructureElement[]::new)));
} else {
throw new RuntimeException(
"Missing Structure Element bindings for (chars as integers): "
+ Arrays.toString(missing.toArray()));
}
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileStructureMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().chars().mapToObj(c -> elements.get((char) c))
.toArray(IStructureElement[]::new)));
} else {
throw new RuntimeException(
"Missing Structure Element bindings for (chars as integers): "
+ Arrays.toString(missing.toArray()));
CharOpenHashSet missing = findMissing();
if (!missing.isEmpty()) {
throw new RuntimeException("Missing Structure Element bindings for: " + missing);
}
Map<String, IStructureElement<T>[]> map = new HashMap<>();
for (Entry<String, String> e : shapes.entrySet()) {
char[] chars = e.getValue().toCharArray();
IStructureElement<T>[] compiled = new IStructureElement[chars.length];
int a = 0, b = 0, c = 0;
for (int i = 0; i < chars.length; i++) {
char ch = chars[i];
IStructureElement<T> element = this.elements.get(ch);
compiled[i] = element;
if (element.isNavigating()) {
a = (element.resetA() ? 0 : a) + element.getStepA();
b = (element.resetB() ? 0 : b) + element.getStepB();
c = (element.resetC() ? 0 : c) + element.getStepC();
} else {
a++;
}
}
if (map.put(e.getKey(), compiled) != null) {
throw new IllegalStateException("Duplicate shape: " + e.getKey());
}
}
return map;
}
private Set<Integer> findMissing() {
return shapes.values().stream().flatMapToInt(CharSequence::chars)
.filter(i -> !elements.containsKey((char) i)).boxed().collect(Collectors.toSet());
private CharOpenHashSet findMissing() {
CharOpenHashSet missing = new CharOpenHashSet();
for (String shape : shapes.values()) {
for (char c : shape.toCharArray()) {
if (!elements.containsKey(c)) {
missing.add(c);
}
}
}
return missing;
}
}
@@ -246,4 +356,61 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
if (occupiedSpace == null) throw new NoSuchElementException(name);
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
}
@Override
public Position<StructureDefinitionCoords> getControllerPosition(String piece) {
return controllers.get(piece);
}
@Override
public StructureDefinitionCoords getCoordinateSystem(String piece) {
var controller = controllers.get(piece);
if (controller == null) {
throw new IllegalArgumentException("Invalid piece: " + piece);
}
return new StructureDefinitionCoords(controller.x, controller.y, controller.z);
}
@Override
public Position<StructureDefinitionCoords> getSocket(String piece, char socket) {
var piecePositions = socketLocations.get(piece);
if (piecePositions == null) {
throw new IllegalArgumentException("Invalid piece: " + piece);
}
var positions = piecePositions.get(socket);
if (positions.isEmpty()) {
throw new IllegalArgumentException("Socket " + socket + " for piece " + piece + " does not exist");
}
if (positions.size() > 1) {
throw new IllegalStateException(
"Socket " + socket
+ " for piece "
+ piece
+ " has several positions, but expected one and only one");
}
return positions.get(0).copy();
}
@Override
@Nonnull
public List<Position<StructureDefinitionCoords>> getAllSockets(String piece, char socket) {
var piecePositions = socketLocations.get(piece);
if (piecePositions == null) {
throw new IllegalArgumentException("Invalid piece: " + piece);
}
var list = new ArrayList<>(piecePositions.get(socket));
list.replaceAll(Position::copy);
return list;
}
}
@@ -36,7 +36,10 @@ import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
@@ -338,9 +341,7 @@ public class StructureUtility {
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
if (!s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation(
"structurelib.autoplace.error.no_simple_block",
stack.func_151000_E()));
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (block instanceof ICustomBlockSetting) {
@@ -447,7 +448,7 @@ public class StructureUtility {
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
if (!assumeStackPresent && !s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation("structurelib.autoplace.error.no_item_stack", stack.func_151000_E()));
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
@@ -1411,6 +1412,53 @@ public class StructureUtility {
} else {
return new IStructureElement<T>() {
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
IItemSource source = env.getSource();
EntityPlayer actor = env.getActor();
Consumer<IChatComponent> chatter = env.getChatter();
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (e.getStacks() == null) {
ItemStack taken = source.takeOne(e.getPredicate(), true);
return StructureUtility.survivalPlaceBlock(
taken,
NBTMode.EXACT,
taken.stackTagCompound,
false,
world,
x,
y,
z,
source,
actor,
chatter);
}
for (ItemStack stack : e.getStacks()) {
if (!source.takeOne(stack, true)) {
IChatComponent name = new ChatComponentText(stack.getDisplayName());
name.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW));
env.getChatter()
.accept(new ChatComponentTranslation("structurelib.autoplace.missing_block", name));
continue;
}
return StructureUtility.survivalPlaceBlock(
stack,
NBTMode.EXACT,
stack.stackTagCompound,
false,
world,
x,
y,
z,
source,
actor,
chatter);
}
return PlaceResult.REJECT;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
@@ -34,11 +34,11 @@ item.structurelib.constructableTrigger.gui.title=Sub Channels
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
item.structurelib.constructableTrigger.gui.set=Update
item.structurelib.constructableTrigger.gui.unset=Remove
item.structurelib.constructableTrigger.gui.wipe=§4Clear
item.structurelib.constructableTrigger.gui.channels.from=§rMod: §9§o%s
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in its structure and use sub channels to indicate which variant to use.\n Sub channels are has a sequence of lower case characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\nYou can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of §oIndicates value X for sub channel Y§r.
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in their structure and a sub channel allows indication of which variant to use.\n All sub channels have a sequence of lowercase characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\n You can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of "§oIndicates§r §ovalue§r §oX§r §ofor§r §osub§r §ochannel§r §oY§r".
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
@@ -51,6 +51,7 @@ structurelib.autoplace.error.not_enabled=§cThis multiblock does not support aut
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.autoplace.missing_block=§cMissing block: %s
structurelib.blockhint.desc.0=Helps while building
structurelib.blockhint.0.name=Hint 1 dot
Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 361 B

@@ -0,0 +1,102 @@
package com.gtnewhorizon.structurelib.coords;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import com.gtnewhorizon.structurelib.structure.StructureUtility;
// spotless:off
class CoordinateTests {
private IStructureDefinition<?> getDefinition() {
String[][] main = {
{ " ", " ", " ", " ", " " },
{ " ", " ", " ", " ", " " },
{ " ", " ", " X ", " ", " " },
{ " ", " ", " YY ", " ", " " },
{ " ~ ", " ", " ", " ", " " }
};
return IStructureDefinition.builder()
.addShape("main", StructureUtility.transpose(main))
.addSocket('X', ' ')
.addSocket('Y', ' ')
.build();
}
@Test
void testSocketAndCoordSystemTranslationToWorld() {
IStructureDefinition<?> def = getDefinition();
// Note: the position is the same object between all of these transforms, it is never copied, its generic just
// changes
var p = def.getSocket("main", 'X');
Assertions.assertEquals(new Position<>(2, 2, 2), p);
var p2 = def.getCoordinateSystem("main").translateInverse(p);
Assertions.assertEquals(new Position<>(0, -2, 2), p2);
var p3 = ExtendedFacing.NORTH_NORMAL_NONE.asCoordinateSystem().translateInverse(p2);
Assertions.assertEquals(new Position<>(0, 2, 2), p3);
var p4 = ControllerRelativeCoords.translateInverse(p3, 5, 5, 5);
Assertions.assertEquals(new Position<>(5, 7, 7), p4);
}
@Test
void testSocketAndCoordSystemTranslationToStructureDef() {
IStructureDefinition<?> def = getDefinition();
// Note: the position is the same object between all of these transforms, it is never copied, its generic just
// changes
var p = new Position<WorldCoords>(5, 7, 7);
var p2 = ControllerRelativeCoords.translate(p, 5, 5, 5);
Assertions.assertEquals(new Position<>(0, 2, 2), p2);
var p3 = ExtendedFacing.NORTH_NORMAL_NONE.asCoordinateSystem().translate(p2);
Assertions.assertEquals(new Position<>(0, -2, 2), p3);
var p4 = def.getCoordinateSystem("main").translate(p3);
Assertions.assertEquals(new Position<>(2, 2, 2), p4);
Assertions.assertEquals(def.getSocket("main", 'X'), p4);
}
@Test
void testSocketGetters() {
IStructureDefinition<?> def = getDefinition();
Assertions.assertThrows(
IllegalArgumentException.class,
() -> def.getSocket("invalid", 'X'));
Assertions.assertThrows(
IllegalStateException.class,
() -> def.getSocket("main", 'Y'));
Assertions.assertThrows(
IllegalArgumentException.class,
() -> def.getSocket("main", 'Z'));
Assertions.assertThrows(
IllegalArgumentException.class,
() -> def.getAllSockets("invalid", 'X'));
Assertions.assertEquals(
Collections.emptyList(),
def.getAllSockets("main", 'Z'));
Assertions.assertEquals(
Arrays.asList(new Position<>(2, 2, 2)),
def.getAllSockets("main", 'X'));
Assertions.assertEquals(
Arrays.asList(new Position<>(2, 3, 2), new Position<>(3, 3, 2)),
def.getAllSockets("main", 'Y'));
}
}
// spotless:on