Compare commits

...
18 Commits
Author SHA1 Message Date
Luca 95d1533342 Add Context Parameter to get Description (#60)
Yes this is breaking, but only to the changes that where merged not even
24 hours ago.
2026-05-24 13:35:25 +08:00
Luca 19179ec894 improve description fallback (#59) 2026-05-23 19:42:15 +02:00
Luca d8b301457f Add getDescription() to IStructureElement (#57) 2026-05-23 09:42:20 +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
UltraProdigy a09e6058a9 Optimize Image & Workflow (#55) 2026-03-07 09:38:45 +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 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
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
Martin Robertz 34394ef8a5 update 2025-10-28 21:07:20 +01: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
39 changed files with 426 additions and 78 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.39:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.84-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.42'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.24'
}
@@ -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];
}
@@ -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(
@@ -319,6 +319,22 @@ 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");
}
/**
* Low level utility.
*
@@ -5,6 +5,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -42,6 +43,30 @@ public interface IStructureElement<T> {
return true;
}
/**
* Returns a human-readable description of what block(s) this element accepts. Used for diagnostic messages when
* structure checks fail (e.g. "expected Input Bus at (1, 2, 3)").
* <p>
* Unlike {@link #getBlocksToPlace}, which returns the full set of concrete block variants for autoplacing (e.g.
* every tier of Input Bus), this method returns <b>category-level</b> descriptions suitable for display to the
* player (e.g. just "Input Bus"). It also requires no parameters, no world, trigger item, or environment, making it
* usable in contexts where those are unavailable.
* <p>
* Implementations should return <b>lang keys</b> (e.g. {@code "tile.blockGlass.name"}) rather than pre-translated
* display names. The caller is responsible for translating these keys on the client side via
* {@code StatCollector.translateToLocal()}. If a key has no matching translation, {@code translateToLocal} returns
* the input string unchanged, so pre-translated fallback strings also work.
*
* @param context the context object (e.g. the multiblock controller), used to determine state-dependent
* descriptions
* @return a list of accepted block descriptions as lang keys (or display names as fallback), or null if no
* description is available
*/
@Nullable
default List<String> getDescription(T context) {
return null;
}
boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger);
boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger);
@@ -204,6 +229,12 @@ public interface IStructureElement<T> {
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return IStructureElement.this.spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return IStructureElement.this.getDescription(context);
}
};
}
@@ -1,7 +1,9 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -61,6 +63,19 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
return false;
}
@Nullable
@Override
default List<String> getDescription(T context) {
Set<String> descriptions = new LinkedHashSet<>();
for (IStructureElement<T> fallback : fallbacks()) {
List<String> desc = fallback.getDescription(context);
if (desc != null) {
descriptions.addAll(desc);
}
}
return descriptions.isEmpty() ? null : new ArrayList<>(descriptions);
}
@Nullable
@Override
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
@@ -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,
@@ -1,5 +1,6 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -65,4 +66,10 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
AutoPlaceEnvironment env) {
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return elem != null ? elem.getDescription(context) : null;
}
}
@@ -148,15 +148,32 @@ 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;
}
@@ -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))
@@ -706,6 +707,29 @@ public class StructureUtility {
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
Function<T, TIER> getter) {
List<String> descriptions = null;
if (allKnownTiers != null) {
descriptions = new java.util.ArrayList<>();
for (Pair<Block, Integer> tier : allKnownTiers) {
Item item = Item.getItemFromBlock(tier.getLeft());
if (item != null) {
descriptions.add(new ItemStack(item, 1, tier.getRight()).getUnlocalizedName() + ".name");
}
}
}
return ofBlocksTiered(tierExtractor, allKnownTiers, notSet, setter, getter, descriptions);
}
/**
* Like {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}, but with an explicit
* description that will be returned by {@link IStructureElement#getDescription(Object)}.
*
* @param description the description lang keys to attach to this element, or null for no description
* @see #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)
*/
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
Function<T, TIER> getter, @Nullable List<String> description) {
List<Pair<Block, Integer>> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers;
if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException();
IStructureElementCheckOnly<T> check = ofBlocksTiered(tierExtractor, notSet, setter, getter);
@@ -784,6 +808,24 @@ public class StructureUtility {
env.getActor(),
env.getChatter());
}
@Nullable
@Override
public List<String> getDescription(T context) {
TIER currentTier = getter.apply(context);
if (Objects.equals(currentTier, notSet)) return description;
for (Pair<Block, Integer> pair : hints) {
TIER pairTier = tierExtractor.convert(pair.getKey(), pair.getValue());
if (!Objects.equals(pairTier, currentTier)) continue;
Item item = Item.getItemFromBlock(pair.getKey());
if (item == null) continue;
return Collections
.singletonList(new ItemStack(item, 1, pair.getValue()).getUnlocalizedName() + ".name");
}
return description;
}
};
}
@@ -1407,10 +1449,65 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
}
};
} 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);
@@ -1439,6 +1536,14 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
}
};
}
}
@@ -1481,6 +1586,14 @@ public class StructureUtility {
// there is getSubItems on ItemBlock, but it's client only
return BlocksToPlace.create(defaultBlock, defaultMeta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
}
};
} else {
return new IStructureElement<T>() {
@@ -1513,6 +1626,14 @@ public class StructureUtility {
// there is getSubItems on ItemBlock, but it's client only
return BlocksToPlace.create(defaultBlock, defaultMeta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
}
};
}
}
@@ -1776,6 +1897,71 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
/**
* Wraps an element and overrides its description. Useful for providing human-readable names to elements whose
* underlying implementation doesn't provide a description (e.g. tiered block elements).
*
* @param description the description to use, or null to clear any existing description
* @param element the element to wrap
*/
public static <B extends IStructureElement<T>, T> IStructureElement<T> withDescription(
@Nullable List<String> description, B element) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return element.check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return description;
}
};
}
@@ -1832,6 +2018,12 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
@@ -1901,6 +2093,12 @@ public class StructureUtility {
if (predicate.test(t)) return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, env);
return placeResultWhenDisabled;
}
@Nullable
@Override
public List<String> getDescription(T context) {
return downstream.getDescription(context);
}
};
}
@@ -1998,6 +2196,12 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return elem.getDescription(context.getCurrentContext());
}
};
}
// endregion
@@ -2835,6 +3039,12 @@ public class StructureUtility {
warnNoExplicitSubChannel(env.getActor());
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return backing.getDescription(context);
}
};
}
// endregion
@@ -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