Compare commits

..
2 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
6 changed files with 64 additions and 57 deletions

No files matched your search

@@ -57,11 +57,13 @@ public interface IStructureElement<T> {
* {@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() {
default List<String> getDescription(T context) {
return null;
}
@@ -94,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;
}
/**
@@ -204,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;
}
/**
@@ -230,8 +232,8 @@ public interface IStructureElement<T> {
@Nullable
@Override
public List<String> getDescription() {
return IStructureElement.this.getDescription();
public List<String> getDescription(T context) {
return IStructureElement.this.getDescription(context);
}
};
}
@@ -266,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.
@@ -65,10 +65,10 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Nullable
@Override
default List<String> getDescription() {
default List<String> getDescription(T context) {
Set<String> descriptions = new LinkedHashSet<>();
for (IStructureElement<T> fallback : fallbacks()) {
List<String> desc = fallback.getDescription();
List<String> desc = fallback.getDescription(context);
if (desc != null) {
descriptions.addAll(desc);
}
@@ -97,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;
}
}
@@ -69,7 +69,7 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
@Nullable
@Override
public List<String> getDescription() {
return elem != null ? elem.getDescription() : null;
public List<String> getDescription(T context) {
return elem != null ? elem.getDescription(context) : null;
}
}
@@ -722,7 +722,7 @@ public class StructureUtility {
/**
* Like {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}, but with an explicit
* description that will be returned by {@link IStructureElement#getDescription()}.
* 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)
@@ -735,11 +735,8 @@ public class StructureUtility {
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);
}
@@ -750,7 +747,6 @@ 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());
@@ -795,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);
@@ -815,9 +811,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
if (lastContext == null) return description;
TIER currentTier = getter.apply(lastContext);
public List<String> getDescription(T context) {
TIER currentTier = getter.apply(context);
if (Objects.equals(currentTier, notSet)) return description;
for (Pair<Block, Integer> pair : hints) {
@@ -1457,7 +1452,7 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
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");
@@ -1544,7 +1539,7 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
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");
@@ -1594,7 +1589,7 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
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");
@@ -1634,7 +1629,7 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
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");
@@ -1905,8 +1900,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
return element.getDescription();
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
@@ -1964,7 +1959,7 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
public List<String> getDescription(T context) {
return description;
}
};
@@ -2026,8 +2021,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
return element.getDescription();
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
@@ -2037,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);
}
/**
@@ -2101,8 +2096,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
return downstream.getDescription();
public List<String> getDescription(T context) {
return downstream.getDescription(context);
}
};
}
@@ -2116,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.
@@ -2204,8 +2199,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
return elem.getDescription();
public List<String> getDescription(T context) {
return elem.getDescription(context.getCurrentContext());
}
};
}
@@ -3047,8 +3042,8 @@ public class StructureUtility {
@Nullable
@Override
public List<String> getDescription() {
return backing.getDescription();
public List<String> getDescription(T context) {
return backing.getDescription(context);
}
};
}
@@ -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);