diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index 63d1b4b7..bff3d9f9 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -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 { 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)"). + *

+ * Unlike {@link #getBlocksToPlace}, which returns the full set of concrete block variants for autoplacing (e.g. + * every tier of Input Bus), this method returns category-level 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. + *

+ * Implementations should return lang keys (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 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 { 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 getDescription() { + return IStructureElement.this.getDescription(); + } }; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java index c527c76a..70406a1f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java @@ -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 extends IStructureElement { return false; } + @Nullable + @Override + default List getDescription() { + Set descriptions = new LinkedHashSet<>(); + for (IStructureElement fallback : fallbacks()) { + List 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, diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java index 0959ee67..0b800429 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java @@ -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 implements IStructureElementDeferred { AutoPlaceEnvironment env) { return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env); } + + @Nullable + @Override + public List getDescription() { + return elem != null ? elem.getDescription() : null; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 23c8354f..72ce0fa7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -707,6 +707,19 @@ public class StructureUtility { public static IStructureElement ofBlocksTiered(ITierConverter tierExtractor, @Nullable List> allKnownTiers, @Nullable TIER notSet, BiConsumer setter, Function 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 IStructureElement ofBlocksTiered(ITierConverter tierExtractor, + @Nullable List> allKnownTiers, @Nullable TIER notSet, BiConsumer setter, + Function getter, @Nullable List description) { List> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers; if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException(); IStructureElementCheckOnly check = ofBlocksTiered(tierExtractor, notSet, setter, getter); @@ -785,6 +798,13 @@ public class StructureUtility { env.getActor(), env.getChatter()); } + + @Nullable + @Override + public List getDescription() { + return description; + } + }; } @@ -1408,6 +1428,14 @@ public class StructureUtility { AutoPlaceEnvironment env) { return BlocksToPlace.create(block, meta); } + + @Nullable + @Override + public List 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() { @@ -1487,6 +1515,14 @@ public class StructureUtility { AutoPlaceEnvironment env) { return BlocksToPlace.create(block, meta); } + + @Nullable + @Override + public List 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 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() { @@ -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 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 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 , T> IStructureElement withDescription( + @Nullable List description, B element) { + return new IStructureElement() { + + @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 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 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 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 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 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 getDescription() { + return backing.getDescription(); + } }; } // endregion