Compare commits

..
Author SHA1 Message Date
Chitak 985 89057ebb2d Clean up empty NBT when clearing projector channels (#75) 2026-09-20 09:13:19 +08:00
Eldrinn Elantey d829ae466b Localize and unify missing block messages in autoplace (#70)
StructureLib built the missing block name on the server, where only
en_US is loaded, so players on dedicated servers saw English names in
chat. Send the item's lang key and let the client translate it. Stacks
whose name is not a plain key, GT meta items among them, keep the old
server side name.

Players also saw two shapes of the same message: a bracketed name with
rarity color and an item tooltip from two of the survivalPlaceBlocks
branches, a bare yellow name from the stack list branch. All three now
build the name the same way.
2026-09-09 09:21:27 +08:00
11 changed files with 392 additions and 316 deletions

No files matched your search

@@ -146,7 +146,12 @@ public class ChannelDataAccessor {
*/
public static void wipeChannelData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
if (masterStack.stackTagCompound != null) masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
if (masterStack.stackTagCompound != null) {
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
if (masterStack.stackTagCompound.hasNoTags()) {
masterStack.stackTagCompound = null;
}
}
}
/**
@@ -67,7 +67,7 @@ public interface IStructureDefinition<T> {
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly
* matches
*/
IStructureElement<? super T>[] getStructureFor(String name);
IStructureElement<T>[] getStructureFor(String name);
boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC);
@@ -356,7 +356,7 @@ public interface IStructureDefinition<T> {
* @return true if iteration completed successfully.
* @param <T> type of context object.
*/
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<? super T>[] elements, World world,
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<T>[] elements, World world,
ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA,
int basePositionB, int basePositionC, boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
if (!world.isRemote && hintsOnly) {
@@ -28,9 +28,6 @@ import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
/**
* Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It
* represents what a particular element can be and how this particular element should be autoplaced.
*
* @param <T> Type of the context object. {@link IStructureElement} is contravariant over T, so parameters and fields of
* type {@link IStructureElement} should be declared as {@code IStructureElement<? super T>}
*/
public interface IStructureElement<T> {
@@ -216,7 +213,7 @@ public interface IStructureElement<T> {
* Forget the messed up class dependency graph for now. this is just so convenient.
*/
default IStructureElementNoPlacement<T> noPlacement() {
return new IStructureElementNoPlacement<>() {
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
@@ -21,11 +21,11 @@ import com.google.common.collect.Iterables;
*/
public interface IStructureElementChain<T> extends IStructureElement<T> {
IStructureElement<? super T>[] fallbacks();
IStructureElement<T>[] fallbacks();
@Override
default boolean check(T t, World world, int x, int y, int z) {
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.check(t, world, x, y, z)) {
return true;
}
@@ -35,7 +35,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.couldBeValid(t, world, x, y, z, trigger)) {
return true;
}
@@ -45,7 +45,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.spawnHint(t, world, x, y, z, trigger)) {
return true;
}
@@ -55,7 +55,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.placeBlock(t, world, x, y, z, trigger)) {
return true;
}
@@ -67,7 +67,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override
default List<String> getDescription(T context) {
Set<String> descriptions = new LinkedHashSet<>();
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
List<String> desc = fallback.getDescription(context);
if (desc != null) {
descriptions.addAll(desc);
@@ -82,7 +82,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
AutoPlaceEnvironment env) {
Predicate<ItemStack> predicate = null;
List<Iterable<ItemStack>> is = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
if (e == null) continue;
if (predicate == null) predicate = e.getPredicate();
@@ -99,7 +99,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
switch (result) {
case REJECT_CONTINUE:
@@ -124,7 +124,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
AutoPlaceEnvironment env) {
boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) {
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
switch (result) {
@@ -10,36 +10,36 @@ import net.minecraft.world.World;
/**
* Use StructureUtility to instantiate
*/
interface IStructureNavigate extends IStructureElement<Object> {
interface IStructureNavigate<T> extends IStructureElement<T> {
@Override
default boolean check(Object t, World world, int x, int y, int z) {
default boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
default boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default boolean spawnHint(Object t, World world, int x, int y, int z, ItemStack trigger) {
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default boolean placeBlock(Object t, World world, int x, int y, int z, ItemStack trigger) {
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
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;
}
@Override
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger,
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.SKIP;
}
@@ -21,42 +21,40 @@ public interface IStructureWalker<T> {
* @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<? super T> element, World world, int x, int y, int z, int a, int b, int c);
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
default boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
return false;
}
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<>() {
return new IStructureWalker<T>() {
@Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
int c) {
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
@Override
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a,
int b, int c) {
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
};
}
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<>() {
return new IStructureWalker<T>() {
@Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
int c) {
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
@Override
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a,
int b, int c) {
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
return true;
}
};
@@ -13,14 +13,14 @@ import net.minecraft.world.World;
class LazyStructureElement<T> implements IStructureElementDeferred<T> {
private Function<? super T, ? extends IStructureElement<? super T>> to;
private IStructureElement<? super T> elem;
private Function<T, IStructureElement<T>> to;
private IStructureElement<T> elem;
public LazyStructureElement(Function<? super T, ? extends IStructureElement<? super T>> to) {
public LazyStructureElement(Function<T, IStructureElement<T>> to) {
this.to = to;
}
private IStructureElement<? super T> get(T t) {
private IStructureElement<T> get(T t) {
if (to != null) {
elem = to.apply(t);
to = null;
@@ -16,17 +16,17 @@ import com.gtnewhorizon.structurelib.util.Vec3Impl;
public class StructureDefinition<T> implements IStructureDefinition<T> {
private final Map<Character, IStructureElement<? super T>> elements;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, IStructureElement<? super T>[]> structures;
private final Map<String, IStructureElement<T>[]> structures;
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
public static <B> Builder<B> builder() {
return new Builder<>();
}
private StructureDefinition(Map<Character, IStructureElement<? super T>> elements, Map<String, String> shapes,
Map<String, IStructureElement<? super T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
this.elements = elements;
this.shapes = shapes;
this.structures = structures;
@@ -40,7 +40,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
private static final char C = '\uC000';
private char d = '\uD000';
private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<? super T>> elements;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
@@ -51,7 +51,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
occupiedSpaces = new HashMap<>();
}
public Map<Character, IStructureElement<? super T>> getElements() {
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
@@ -181,18 +181,18 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
* @deprecated use the unboxed version
*/
@Deprecated
public Builder<T> addElement(Character name, IStructureElement<? super T> structurePiece) {
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public Builder<T> addElement(char name, IStructureElement<? super T> structurePiece) {
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public IStructureDefinition<T> build() {
Map<String, IStructureElement<? super T>[]> structures = compileStructureMap();
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
return new StructureDefinition<>(
new HashMap<>(elements),
new HashMap<>(shapes),
@@ -201,7 +201,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<? super T>[]> compileElementSetMap() {
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(
@@ -217,7 +217,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<? super T>[]> compileStructureMap() {
private Map<String, IStructureElement<T>[]> compileStructureMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(
@@ -238,7 +238,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
}
public Map<Character, IStructureElement<? super T>> getElements() {
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
@@ -246,13 +246,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
return shapes;
}
public Map<String, IStructureElement<? super T>[]> getStructures() {
public Map<String, IStructureElement<T>[]> getStructures() {
return structures;
}
@Override
public IStructureElement<? super T>[] getStructureFor(String name) {
IStructureElement<? super T>[] elements = structures.get(name);
public IStructureElement<T>[] getStructureFor(String name) {
IStructureElement<T>[] elements = structures.get(name);
if (elements == null) throw new NoSuchElementException(name);
return elements;
}
File diff suppressed because it is too large. Load diff
@@ -37,7 +37,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
}
@Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, int c) {
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
env.offsetABC[0] = a;
env.offsetABC[1] = b;
env.offsetABC[2] = c;
@@ -0,0 +1,64 @@
package com.gtnewhorizon.structurelib.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableMap;
import java.util.Spliterator;
import java.util.TreeMap;
import java.util.function.Consumer;
public class SortedRegistry<V> implements Iterable<V> {
private final NavigableMap<String, V> store = new TreeMap<>();
/**
* Store stuff in an array for faster iteration access than a red black tree
*/
private List<V> baked = Collections.emptyList();
public void register(String key, V val) {
if (key == null || val == null) throw new NullPointerException();
V old = store.putIfAbsent(key, val);
if (old != null) {
throw new IllegalArgumentException("Duplicate key: " + key);
}
// TODO allow a user to edit the order with a configuration file
baked = new ArrayList<>(store.values());
}
public int size() {
return store.size();
}
public boolean isEmpty() {
return store.isEmpty();
}
public boolean containsKey(String key) {
return store.containsKey(key);
}
public boolean containsValue(V value) {
return store.containsValue(value);
}
public V get(String key) {
return store.get(key);
}
@Override
public Iterator<V> iterator() {
return baked.iterator();
}
@Override
public void forEach(Consumer<? super V> action) {
baked.forEach(action);
}
@Override
public Spliterator<V> spliterator() {
return baked.spliterator();
}
}