Compare commits

..
7 Commits
Author SHA1 Message Date
Yoshyandgithub-actions[bot] 287e8317c9 Add reject_continue to PlaceResult and make SKIP short-circuit in ofChain (#63)
ofChain survival placement had a bug where SKIP (returned when a block
already exists) would not stop the chain, causing fallback alternatives
to replace already valid blocks , for example:
<img width="436" height="477" alt="image"
src="https://github.com/user-attachments/assets/7ff3d6fa-de98-4716-823f-3ad2abab7394"
/>

SKIP now short-circuits the chain, remaining alternatives are skipped
because the position is already valid.
To keep correct behavior for elements without autobuild (which
previously returned SKIP as a "no implementation" signal), a new
REJECT_CONTINUE value is added, which: continues silently like SKIP (no
error, not counted) in the walker and in the chain tries the next
alternative like REJECT
Default survivalPlaceBlock and IStructureElementCheckOnly now return
REJECT_CONTINUE instead of SKIP for unhandled paths.

Tested this with the example above, and fixed the issue:
<img width="435" height="498" alt="image"
src="https://github.com/user-attachments/assets/d92bce4d-b5ad-4051-af79-ffd8c819f9f7"
/>
survival placement also still works:
<img width="1234" height="916" alt="image"
src="https://github.com/user-attachments/assets/632d5556-524d-4dc5-a7f5-75c859cb6b70"
/>

this should ideally merged before
https://github.com/GTNewHorizons/GT5-Unofficial/pull/6922 , to fix this
underlying bug, but if possible not be tagged without the other PR,
which would cause all NEI previews of multis with water to not show any
water anymore

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-06 18:10:11 +02:00
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
35 changed files with 360 additions and 49 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
+2 -2
View File
@@ -1,8 +1,8 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.8.44:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.48-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')
+23 -3
View File
@@ -39,14 +39,34 @@ 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.
enableGenericInjection = true
+10 -10
View File
@@ -1,12 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/46949723aaa20c7b64d7ecfed7207034/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3cd7045fca9a72cd9bc7d14a385e594c/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect
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-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -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/.
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.10'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.24'
}
@@ -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);
@@ -71,7 +96,7 @@ public interface IStructureElement<T> {
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
return PlaceResult.SKIP;
return PlaceResult.REJECT_CONTINUE;
}
/**
@@ -181,7 +206,7 @@ public interface IStructureElement<T> {
if (StructureLibAPI.isDebugEnabled()) LOGGER.info(
"Fallback shim code of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
return PlaceResult.SKIP;
return PlaceResult.REJECT_CONTINUE;
}
/**
@@ -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);
}
};
}
@@ -237,16 +268,21 @@ public interface IStructureElement<T> {
enum PlaceResult {
/**
* 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?
* The element's check passed, the position is already valid. No placement is needed. In the walker: continue
* iteration (no error, not counted as placed). In ofChain: short-circuits, remaining alternatives are skipped.
*/
SKIP,
/**
* This element's space is occupied by other stuff and require player attention, or player missing required
* resource, or the block cannot be placed due to obscure mechanisms, or some other unforeseen situations.
* The element cannot handle this position (occupied, missing resources, etc.). In the walker: stop iteration
* with error particles. In ofChain: try next alternative.
*/
REJECT,
/**
* This element has no survivalPlaceBlock implementation (default fallback), or otherwise cannot place but is
* not an error condition. In the walker: continue iteration silently (like SKIP). In ofChain: try next
* alternative (like REJECT), so a better-suited element can handle it.
*/
REJECT_CONTINUE,
/**
* Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait
* for next round.
@@ -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,
@@ -82,47 +97,51 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
boolean haveSkip = false;
boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
switch (result) {
case REJECT_CONTINUE:
break;
case REJECT:
allContinue = false;
break;
case SKIP:
haveSkip = true;
break;
bufferedNoise.forEach(chatter);
return PlaceResult.SKIP;
default:
return result;
}
}
// dump all that noise back into upstream
bufferedNoise.forEach(chatter);
// TODO need reconsider to ensure this is the right course of action
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
return allContinue ? PlaceResult.REJECT_CONTINUE : PlaceResult.REJECT;
}
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
boolean haveSkip = false;
boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
switch (result) {
case REJECT_CONTINUE:
break;
case REJECT:
allContinue = false;
break;
case SKIP:
haveSkip = true;
break;
bufferedNoise.forEach(env.getChatter());
return PlaceResult.SKIP;
default:
return result;
}
}
// dump all that noise back into upstream
bufferedNoise.forEach(env.getChatter());
// TODO need reconsider to ensure this is the right course of action
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
return allContinue ? PlaceResult.REJECT_CONTINUE : PlaceResult.REJECT;
}
}
@@ -27,12 +27,12 @@ public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return PlaceResult.SKIP;
return PlaceResult.REJECT_CONTINUE;
}
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.SKIP;
return PlaceResult.REJECT_CONTINUE;
}
}
@@ -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;
}
}
@@ -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);
@@ -767,7 +791,7 @@ public class StructureUtility {
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return PlaceResult.REJECT; // TODO or SKIP?
if (hint == null) return PlaceResult.REJECT;
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
TIER tier = tierExtractor.convert(block, meta);
@@ -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);
}
};
}
@@ -1840,11 +2032,11 @@ public class StructureUtility {
/**
* Enable this structure element only if given predicate returns true.
* <p>
* Return SKIP when survival auto place if given predicate returns false.
* Return REJECT_CONTINUE when survival auto place if given predicate returns false.
*/
public static <T> IStructureElement<T> onlyIf(Predicate<? super T> predicate,
IStructureElement<? super T> downstream) {
return onlyIf(predicate, downstream, PlaceResult.SKIP);
return onlyIf(predicate, downstream, PlaceResult.REJECT_CONTINUE);
}
/**
@@ -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);
}
};
}
@@ -1913,10 +2111,10 @@ public class StructureUtility {
* using {@link #isAir()}. It will not attempt to capture any errors though, so next one will not be tried if
* previous one would crash.
* <p>
* (*): For survival auto place, it will * REJECT, if all structure element REJECT * SKIP, if 1 or more structure
* element SKIP and the rest structure element (0 or more) REJECT * any other result, **immediately** upon any
* structure element returns these other results. This behavior is not 100% fixed and might change later on, but we
* will send the notice on a best effort basis.
* (*): For survival auto place, it will: REJECT, if any structure element returns REJECT (and none returned ACCEPT,
* STOP, ACCEPT_STOP, or SKIP); REJECT_CONTINUE, if all structure elements return REJECT_CONTINUE (none returned
* REJECT); SKIP, as soon as any structure element returns SKIP; any other result, immediately upon any structure
* element returning those.
* <p>
* Take care while chaining, as it will try to call every structure element until it returns true. If none does it
* will finally return false.
@@ -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
@@ -43,9 +43,10 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
env.offsetABC[2] = c;
env.setSource(params.getSource());
PlaceResult placeResult = element.survivalPlaceBlock(object, world, x, y, z, trigger, env);
if (placeResult != PlaceResult.SKIP && built == -1) built = 0;
if (placeResult != PlaceResult.SKIP && placeResult != PlaceResult.REJECT_CONTINUE && built == -1) built = 0;
switch (placeResult) {
case SKIP:
case REJECT_CONTINUE:
return true;
case ACCEPT:
if (check) element.check(object, world, x, y, z);
@@ -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: 176 B

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 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