Add getDescription() to IStructureElement (#57)

This commit is contained in:
Luca authored and GitHub committed 2026-05-23 09:42:20 +02:00
1 parent 40edf05109
commit d8b301457f
4 files changed
+192

No files matched your search

@@ -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,28 @@ 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.
*
* @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() {
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 +227,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() {
return IStructureElement.this.getDescription();
}
};
}
@@ -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() {
Set<String> descriptions = new LinkedHashSet<>();
for (IStructureElement<T> fallback : fallbacks()) {
List<String> desc = fallback.getDescription();
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,
@@ -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() {
return elem != null ? elem.getDescription() : null;
}
}
@@ -707,6 +707,19 @@ 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) {
return ofBlocksTiered(tierExtractor, allKnownTiers, notSet, setter, getter, null);
}
/**
* Like {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}, but with an explicit
* description that will be returned by {@link IStructureElement#getDescription()}.
*
* @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);
@@ -785,6 +798,13 @@ public class StructureUtility {
env.getActor(),
env.getChatter());
}
@Nullable
@Override
public List<String> getDescription() {
return description;
}
};
}
@@ -1408,6 +1428,14 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription() {
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>() {
@@ -1487,6 +1515,14 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription() {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
}
};
}
}
@@ -1529,6 +1565,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() {
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>() {
@@ -1561,6 +1605,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() {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
}
};
}
}
@@ -1824,6 +1876,71 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription() {
return element.getDescription();
}
};
}
/**
* 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() {
return description;
}
};
}
@@ -1880,6 +1997,12 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription() {
return element.getDescription();
}
};
}
@@ -1949,6 +2072,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() {
return downstream.getDescription();
}
};
}
@@ -2046,6 +2175,12 @@ public class StructureUtility {
AutoPlaceEnvironment env) {
return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription() {
return elem.getDescription();
}
};
}
// endregion
@@ -2883,6 +3018,12 @@ public class StructureUtility {
warnNoExplicitSubChannel(env.getActor());
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, env);
}
@Nullable
@Override
public List<String> getDescription() {
return backing.getDescription();
}
};
}
// endregion