Compare commits

...
Author SHA1 Message Date
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
4 changed files with 218 additions and 0 deletions

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,13 +707,39 @@ 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()}.
*
* @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);
return new StructureElement_Bridge<T>() {
private T lastContext;
@Override
public boolean check(T t, World world, int x, int y, int z) {
lastContext = t;
return check.check(t, world, x, y, z);
}
@@ -724,6 +750,7 @@ public class StructureUtility {
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
lastContext = t;
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return true;
TIER hintTier = tierExtractor.convert(hint.getKey(), hint.getValue());
@@ -785,6 +812,25 @@ public class StructureUtility {
env.getActor(),
env.getChatter());
}
@Nullable
@Override
public List<String> getDescription() {
if (lastContext == null) return description;
TIER currentTier = getter.apply(lastContext);
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;
}
};
}
@@ -1408,6 +1454,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 +1541,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 +1591,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 +1631,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 +1902,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 +2023,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 +2098,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 +2201,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 +3044,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