d
This commit is contained in:
Brian Neumann-Fopiano
2026-08-05 15:45:08 -06:00
parent 70d355621c
commit ce8c4ff578
18 changed files with 1958 additions and 180 deletions
@@ -69,9 +69,14 @@ public final class IrisStructureLocator {
private static final Pattern NAMESPACED_RESOURCE_KEY = Pattern.compile("[a-z0-9_.-]+:[a-z0-9/._-]+");
private static final Cache<Engine, PlacementIndex> INDEX_CACHE = Caffeine.newBuilder().weakKeys().build();
private static final Cache<Engine, LocatableIndex> LOCATABLE_INDEX_CACHE =
Caffeine.newBuilder().weakKeys().build();
private static final PlacementIndex EMPTY_INDEX = new PlacementIndex(
Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(),
Collections.emptySet(), Collections.emptyList());
private static final LocatableIndex EMPTY_LOCATABLE_INDEX = new LocatableIndex(
Collections.emptySet(), Collections.emptySet(), Collections.emptySet(),
Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
private static final LocateResult NOT_FOUND_RESULT = new LocateResult(LocateStatus.NOT_FOUND, 0, 0, 0);
private static final LocateResult SEARCH_LIMIT_RESULT =
new LocateResult(LocateStatus.SEARCH_LIMIT_REACHED, 0, 0, 0);
@@ -97,6 +102,39 @@ public final class IrisStructureLocator {
|| placementIndex.vanillaAliases.contains(normalizedKey);
}
public static boolean hasLocatablePlacement(Engine engine, String key) {
if (engine == null || key == null || key.isBlank()) {
return false;
}
return locatableIndex(engine).normalizedKeys().contains(normalize(key));
}
public static boolean hasLocatableEditablePlacement(Engine engine, String key) {
if (engine == null || key == null || key.isBlank()) {
return false;
}
return locatableIndex(engine).normalizedEditableKeys().contains(normalize(key));
}
public static boolean hasLocatableNativePlacement(Engine engine, String key) {
if (engine == null || key == null || key.isBlank()) {
return false;
}
return locatableIndex(engine).normalizedNativeKeys().contains(normalize(key));
}
public static Set<String> locatableKeys(Engine engine) {
return locatableIndex(engine).keys();
}
public static Set<String> locatableEditableKeys(Engine engine) {
return locatableIndex(engine).editableKeys();
}
public static Set<String> locatableNativeKeys(Engine engine) {
return locatableIndex(engine).nativeKeys();
}
public static boolean hasNativePlacement(Engine engine, String key) {
if (engine == null || key == null || key.isBlank()) {
return false;
@@ -126,6 +164,7 @@ public final class IrisStructureLocator {
public static void invalidate(Engine engine) {
if (engine != null) {
INDEX_CACHE.invalidate(engine);
LOCATABLE_INDEX_CACHE.invalidate(engine);
}
}
@@ -796,7 +835,8 @@ public final class IrisStructureLocator {
List<IrisStructurePlacement> concentricRings = new ArrayList<>();
boolean hasDensity = false;
for (IrisStructurePlacement placement : index(engine).placements) {
if (!matches(placement, key, engine.getData())) {
if (!isSearchablePlacement(engine, placement)
|| !matches(placement, key, engine.getData())) {
continue;
}
if (placement.getDistribution() == StructureDistribution.RANDOM_SPREAD) {
@@ -805,7 +845,7 @@ public final class IrisStructureLocator {
StructurePlacementGrid.placementSalt(placement)));
} else if (placement.getDistribution() == StructureDistribution.CONCENTRIC_RINGS) {
concentricRings.add(placement);
} else if (isSearchableDensityPlacement(engine, placement)) {
} else if (placement.getDistribution() == StructureDistribution.DENSITY) {
hasDensity = true;
}
}
@@ -813,8 +853,18 @@ public final class IrisStructureLocator {
}
static boolean isSearchableDensityPlacement(Engine engine, IrisStructurePlacement placement) {
if (engine == null || placement == null || placement.getDistribution() != StructureDistribution.DENSITY
|| !(placement.getDensity() > 0.0) || engine.getHeight() <= 0) {
return placement != null
&& placement.getDistribution() == StructureDistribution.DENSITY
&& isSearchablePlacement(engine, placement);
}
static boolean isSearchablePlacement(Engine engine, IrisStructurePlacement placement) {
if (engine == null || placement == null || placement.getDistribution() == null
|| engine.getHeight() <= 0) {
return false;
}
if (placement.getDistribution() == StructureDistribution.DENSITY
&& !(placement.getDensity() > 0.0)) {
return false;
}
long worldMin = (long) engine.getMinHeight() + (placement.isUnderground() ? 1L : 0L);
@@ -952,6 +1002,64 @@ public final class IrisStructureLocator {
return INDEX_CACHE.get(engine, ignored -> build(engine));
}
private static LocatableIndex locatableIndex(Engine engine) {
if (engine == null) {
return EMPTY_LOCATABLE_INDEX;
}
return LOCATABLE_INDEX_CACHE.get(engine, ignored -> buildLocatableIndex(engine));
}
private static LocatableIndex buildLocatableIndex(Engine engine) {
IrisData data = engine.getData();
Set<String> keys = new LinkedHashSet<>();
Set<String> normalizedKeys = new LinkedHashSet<>();
Set<String> editableKeys = new LinkedHashSet<>();
Set<String> normalizedEditableKeys = new LinkedHashSet<>();
Set<String> nativeKeys = new LinkedHashSet<>();
Set<String> normalizedNativeKeys = new LinkedHashSet<>();
for (IrisStructurePlacement placement : index(engine).placements) {
if (!isSearchablePlacement(engine, placement)) {
continue;
}
if (placement.hasNativeStructures()) {
for (IrisNativeStructure source : placement.getNativeStructures()) {
addLocatableKey(source.getStructure(), keys, normalizedKeys);
addLocatableKey(source.getStructure(), nativeKeys, normalizedNativeKeys);
}
continue;
}
for (String structureKey : placement.getStructures()) {
IrisStructure structure = data.load(IrisStructure.class, structureKey, false);
if (structure == null) {
throw new IllegalStateException(
"Iris structure placement references missing structure '" + structureKey + "'");
}
addLocatableKey(structureKey, keys, normalizedKeys);
addLocatableKey(structureKey, editableKeys, normalizedEditableKeys);
addLocatableKey(structure.getLoadKey(), keys, normalizedKeys);
addLocatableKey(structure.getLoadKey(), editableKeys, normalizedEditableKeys);
addLocatableKey(structure.getVanillaSource(), keys, normalizedKeys);
addLocatableKey(structure.getVanillaSource(), editableKeys, normalizedEditableKeys);
}
}
return new LocatableIndex(
Collections.unmodifiableSet(keys),
Collections.unmodifiableSet(normalizedKeys),
Collections.unmodifiableSet(editableKeys),
Collections.unmodifiableSet(normalizedEditableKeys),
Collections.unmodifiableSet(nativeKeys),
Collections.unmodifiableSet(normalizedNativeKeys));
}
private static void addLocatableKey(String key, Set<String> keys, Set<String> normalizedKeys) {
if (key == null || key.isBlank()) {
return;
}
String trimmedKey = key.trim();
keys.add(trimmedKey);
normalizedKeys.add(normalize(trimmedKey));
}
private static PlacementIndex build(Engine engine) {
IrisData data = engine.getData();
Set<String> loadKeys = new LinkedHashSet<>();
@@ -1112,6 +1220,11 @@ public final class IrisStructureLocator {
List<IrisStructurePlacement> concentricRings, boolean hasDensity) {
}
private record LocatableIndex(Set<String> keys, Set<String> normalizedKeys,
Set<String> editableKeys, Set<String> normalizedEditableKeys,
Set<String> nativeKeys, Set<String> normalizedNativeKeys) {
}
private record ResolvedStart(int originX, int baseY, int originZ) {
}
@@ -18,42 +18,57 @@
package art.arcane.iris.util.common.director.specialhandlers;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.framework.StructureReachability;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.util.common.director.DirectorParameterHandler;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.entity.Player;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class StructureHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
KList<String> keys = new KList<>();
try {
for (String k : INMS.get().getStructureKeys()) {
if (k != null && !k.isEmpty()) {
keys.addIfMissing(k);
}
}
} catch (Throwable ignored) {
Engine activeEngine = engine();
if (activeEngine == null) {
return new KList<>();
}
try {
Engine e = engine();
if (e != null) {
for (String k : IrisStructureLocator.placedKeys(e)) {
if (k != null && !k.isEmpty()) {
keys.addIfMissing(k);
}
}
boolean nativeGenerationEnabled = nativeStructureGenerationEnabled();
PlatformStructureHooks structureHooks = IrisPlatforms.get().structureHooks();
Map<String, String> registeredKeys = distinctKeys(structureHooks.structureKeys());
Set<String> reachableKeys = StructureReachability.reachableKeys(activeEngine);
Map<String, String> suggestions = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : registeredKeys.entrySet()) {
if (isEligibleRegisteredKey(activeEngine, entry.getValue(), entry.getKey(), reachableKeys,
nativeGenerationEnabled)) {
suggestions.put(entry.getKey(), entry.getValue());
}
} catch (Throwable ignored) {
}
return keys;
Set<String> locatableKeys = IrisStructureLocator.locatableEditableKeys(activeEngine);
for (String key : locatableKeys) {
String normalizedKey = normalizeKey(key);
if (!normalizedKey.isEmpty()
&& !registeredKeys.containsKey(normalizedKey)
&& !IrisStructureLocator.hasNativePlacement(activeEngine, key)) {
suggestions.putIfAbsent(normalizedKey, key.trim());
}
}
return new KList<>(suggestions.values());
}
@Override
@@ -64,15 +79,12 @@ public class StructureHandler implements DirectorParameterHandler<String> {
@Override
public String parse(String in, boolean force) throws DirectorParsingException {
KList<String> options = getPossibilities(in);
if (options.isEmpty()) {
return in;
}
try {
return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0);
} catch (Throwable e) {
return in;
for (String option : options) {
if (option.equalsIgnoreCase(in)) {
return option;
}
}
return in;
}
@Override
@@ -86,4 +98,50 @@ public class StructureHandler implements DirectorParameterHandler<String> {
return f == null ? "minecraft_ancient_city" : f;
}
protected boolean nativeStructureGenerationEnabled() {
Player activePlayer = player();
return activePlayer != null && activePlayer.getWorld().canGenerateStructures();
}
private static Map<String, String> distinctKeys(List<String> keys) {
Map<String, String> distinct = new LinkedHashMap<>();
for (String key : keys) {
String normalizedKey = normalizeKey(key);
if (!normalizedKey.isEmpty()) {
distinct.putIfAbsent(normalizedKey, key.trim());
}
}
return distinct;
}
private static boolean isEligibleRegisteredKey(Engine engine, String key, String normalizedKey,
Set<String> reachableKeys, boolean nativeGenerationEnabled) {
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine, key, false);
boolean nativePlacement = IrisStructureLocator.hasNativePlacement(engine, key);
boolean locatableNativePlacement = nativePlacement
&& IrisStructureLocator.hasLocatableNativePlacement(engine, key);
boolean locatableEditableReplacement = !nativePlacement
&& decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS
&& IrisStructureLocator.hasLocatableEditablePlacement(engine, key);
return isEligibleRegisteredKey(
decision, nativePlacement, locatableNativePlacement, locatableEditableReplacement,
reachableKeys.contains(normalizedKey), nativeGenerationEnabled);
}
static boolean isEligibleRegisteredKey(IrisNativeStructureDecision decision, boolean nativePlacement,
boolean locatableNativePlacement,
boolean locatableEditableReplacement,
boolean reachable, boolean nativeGenerationEnabled) {
if (decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
return locatableEditableReplacement
|| nativeGenerationEnabled && nativePlacement && locatableNativePlacement;
}
return nativeGenerationEnabled && decision.generate()
&& (reachable || (nativePlacement && locatableNativePlacement));
}
private static String normalizeKey(String key) {
return key == null ? "" : key.trim().toLowerCase(Locale.ROOT);
}
}
@@ -77,6 +77,70 @@ public class IrisStructureLocatorContractTest {
assertFalse(IrisStructureLocator.isPlaced(engine, ""));
}
@Test
public void locatablePlacementsExcludeDisabledDensityConfigurations() {
Engine disabled = densityEngine(0.0, false, -64, 384, -2032, 2032);
Engine active = densityEngine(0.01, false, -64, 384, -2032, 2032);
assertTrue(IrisStructureLocator.isPlaced(disabled, "test:density"));
assertFalse(IrisStructureLocator.hasLocatablePlacement(disabled, "test:density"));
assertTrue(IrisStructureLocator.locatableKeys(disabled).isEmpty());
assertTrue(IrisStructureLocator.hasLocatablePlacement(active, "test:density"));
assertTrue(IrisStructureLocator.locatableKeys(active).contains("test:density"));
assertFalse(IrisStructureLocator.hasLocatablePlacement(null, "test:density"));
assertTrue(IrisStructureLocator.locatableKeys(null).isEmpty());
}
@Test
public void locatablePlacementsExcludeEveryDistributionOutsideWorldHeight() {
Engine randomSpread = densityEngine(1.0, false, -64, 384, 400, 500);
randomSpread.getDimension().getStructures().get(0)
.setDistribution(StructureDistribution.RANDOM_SPREAD);
Engine concentricRings = densityEngine(1.0, false, -64, 384, 400, 500);
concentricRings.getDimension().getStructures().get(0)
.setDistribution(StructureDistribution.CONCENTRIC_RINGS);
assertFalse(IrisStructureLocator.hasLocatablePlacement(randomSpread, "test:density"));
assertFalse(IrisStructureLocator.hasLocatablePlacement(concentricRings, "test:density"));
}
@Test
public void locatableIndexIncludesAliasesAndSeparatesEditableFromNativePlacements() {
IrisData data = mock(IrisData.class);
IrisStructure structure = new IrisStructure();
structure.setLoadKey("test:city");
structure.setVanillaSource("source:city");
when(data.load(IrisStructure.class, "test:city_definition", false)).thenReturn(structure);
IrisStructurePlacement editable = new IrisStructurePlacement();
editable.getStructures().add("test:city_definition");
IrisStructurePlacement nativePlacement = new IrisStructurePlacement();
nativePlacement.getNativeStructures().add(new IrisNativeStructure()
.setStructure("source:native_city"));
IrisDimension dimension = mock(IrisDimension.class);
KList<IrisStructurePlacement> placements = new KList<>();
placements.add(editable);
placements.add(nativePlacement);
Engine engine = mock(Engine.class);
when(engine.getData()).thenReturn(data);
when(engine.getDimension()).thenReturn(dimension);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getHeight()).thenReturn(384);
when(dimension.getStructures()).thenReturn(placements);
when(dimension.getAllRegions(engine)).thenReturn(new KList<>());
when(dimension.getReachableBiomes(engine)).thenReturn(new KList<>());
assertEquals(Set.of("test:city_definition", "test:city", "source:city", "source:native_city"),
IrisStructureLocator.locatableKeys(engine));
assertEquals(Set.of("test:city_definition", "test:city", "source:city"),
IrisStructureLocator.locatableEditableKeys(engine));
assertEquals(Set.of("source:native_city"), IrisStructureLocator.locatableNativeKeys(engine));
assertTrue(IrisStructureLocator.hasLocatableEditablePlacement(engine, "SOURCE:CITY"));
assertFalse(IrisStructureLocator.hasLocatableEditablePlacement(engine, "source:native_city"));
assertTrue(IrisStructureLocator.hasLocatableNativePlacement(engine, "SOURCE:NATIVE_CITY"));
assertFalse(IrisStructureLocator.hasLocatableNativePlacement(engine, "source:city"));
}
@Test
public void suppressesVanillaIsFalseForNullEngine() {
assertFalse(IrisStructureLocator.suppressesVanilla(null, "minecraft:ancient_city"));
@@ -0,0 +1,300 @@
package art.arcane.iris.util.common.director.specialhandlers;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.StructureReachability;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisImportedStructureControl;
import art.arcane.iris.engine.object.IrisNativeStructure;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisStructure;
import art.arcane.iris.engine.object.IrisStructurePlacement;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
import art.arcane.iris.engine.object.StructureDistribution;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.spi.PlatformWorld;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class StructureHandlerTest {
@Test
public void registeredEligibilityMatchesFindExecutionTruthTable() {
IrisNativeStructureDecision replacement = decision(NativeStructureGenerationStatus.REPLACED_BY_IRIS);
IrisNativeStructureDecision generated = decision(NativeStructureGenerationStatus.GENERATE_NATIVE);
IrisNativeStructureDecision disabled = decision(NativeStructureGenerationStatus.DISABLED_BY_PACK);
assertTrue(StructureHandler.isEligibleRegisteredKey(
replacement, false, false, true, false, false));
assertFalse(StructureHandler.isEligibleRegisteredKey(
replacement, false, false, false, true, true));
assertTrue(StructureHandler.isEligibleRegisteredKey(
replacement, true, true, false, false, true));
assertFalse(StructureHandler.isEligibleRegisteredKey(
replacement, true, true, false, false, false));
assertTrue(StructureHandler.isEligibleRegisteredKey(
generated, true, true, false, false, true));
assertFalse(StructureHandler.isEligibleRegisteredKey(
generated, true, false, false, false, true));
assertTrue(StructureHandler.isEligibleRegisteredKey(
generated, false, false, false, true, true));
assertFalse(StructureHandler.isEligibleRegisteredKey(
generated, false, false, false, true, false));
assertFalse(StructureHandler.isEligibleRegisteredKey(
disabled, true, true, false, true, true));
}
@Test
public void requiresActiveIrisEngineBeforeReadingPlatformRegistries() throws DirectorParsingException {
IrisPlatforms.unbind();
TestStructureHandler handler = new TestStructureHandler(null, true);
assertTrue(handler.getPossibilities().isEmpty());
assertEquals("manual:structure", handler.parse("manual:structure", false));
}
@Test
public void filtersAndDeduplicatesSuggestionsForActiveEngine() {
IrisData data = mock(IrisData.class);
Engine engine = mock(Engine.class);
IrisDimension dimension = mock(IrisDimension.class);
IrisWorld world = mock(IrisWorld.class);
PlatformWorld platformWorld = mock(PlatformWorld.class);
IrisPlatform platform = mock(IrisPlatform.class);
PlatformStructureHooks hooks = mock(PlatformStructureHooks.class);
IrisImportedStructureControl control = new IrisImportedStructureControl();
control.getDisabled().add("pack:disabled");
control.getDisabled().add("pack:replacement");
control.getDisabled().add("pack:dormant_replacement");
KList<IrisStructurePlacement> placements = new KList<>();
placements.add(nativePlacement("pack:replacement", StructureDistribution.RANDOM_SPREAD, 1.0));
placements.add(nativePlacement("pack:dormant_replacement", StructureDistribution.DENSITY, 0.0));
placements.add(nativePlacement("pack:explicit", StructureDistribution.RANDOM_SPREAD, 1.0));
placements.add(nativePlacement("pack:dormant_explicit", StructureDistribution.DENSITY, 0.0));
placements.add(nativePlacement("pack:unregistered_native", StructureDistribution.RANDOM_SPREAD, 1.0));
placements.add(nativePlacement("iris:custom", StructureDistribution.RANDOM_SPREAD, 1.0));
placements.add(editablePlacement("iris:custom_definition"));
placements.add(editablePlacement("iris:collision_definition"));
IrisStructure custom = new IrisStructure();
custom.setLoadKey("iris:custom");
IrisStructure collision = new IrisStructure();
collision.setLoadKey("pack:collision");
when(data.load(IrisStructure.class, "iris:custom_definition", false)).thenReturn(custom);
when(data.load(IrisStructure.class, "iris:collision_definition", false)).thenReturn(collision);
when(engine.getData()).thenReturn(data);
when(engine.getDimension()).thenReturn(dimension);
when(engine.getWorld()).thenReturn(world);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getHeight()).thenReturn(384);
when(world.platformWorld()).thenReturn(platformWorld);
when(dimension.getImportedStructures()).thenReturn(control);
when(dimension.getStructures()).thenReturn(placements);
when(dimension.getAllRegions(engine)).thenReturn(new KList<>());
when(dimension.getReachableBiomes(engine)).thenReturn(new KList<>());
when(platform.structureHooks()).thenReturn(hooks);
when(hooks.structureKeys()).thenReturn(List.of(
"pack:reachable",
"PACK:REACHABLE",
"pack:unreachable",
"pack:disabled",
"pack:replacement",
"pack:dormant_replacement",
"pack:explicit",
"pack:dormant_explicit",
"PACK:COLLISION"));
when(hooks.reachableStructureKeys(platformWorld)).thenReturn(List.of("PACK:REACHABLE"));
IrisPlatforms.unbind();
IrisPlatforms.bind(platform);
try {
KList<String> possibilities = new TestStructureHandler(engine, true).getPossibilities();
Set<String> distinctPossibilities = new LinkedHashSet<>(possibilities);
assertEquals(Set.of(
"pack:reachable",
"pack:replacement",
"pack:explicit",
"iris:custom_definition",
"iris:collision_definition"), distinctPossibilities);
assertEquals(distinctPossibilities.size(), possibilities.size());
assertFalse(possibilities.stream().anyMatch("pack:collision"::equalsIgnoreCase));
assertFalse(possibilities.contains("pack:unregistered_native"));
verify(hooks, times(1)).structureKeys();
verify(hooks, times(1)).reachableStructureKeys(platformWorld);
} finally {
IrisStructureLocator.invalidate(engine);
StructureReachability.invalidate(engine);
IrisPlatforms.unbind();
}
}
@Test
public void disabledWorldKeepsOnlyUnregisteredEditablePlacements() {
IrisData data = mock(IrisData.class);
Engine engine = mock(Engine.class);
IrisDimension dimension = mock(IrisDimension.class);
IrisWorld world = mock(IrisWorld.class);
PlatformWorld platformWorld = mock(PlatformWorld.class);
IrisPlatform platform = mock(IrisPlatform.class);
PlatformStructureHooks hooks = mock(PlatformStructureHooks.class);
IrisImportedStructureControl control = new IrisImportedStructureControl();
control.getDisabled().add("minecraft:replacement");
IrisStructure editable = new IrisStructure();
editable.setLoadKey("iris:editable");
IrisStructure replacement = new IrisStructure();
replacement.setLoadKey("iris:replacement");
replacement.setVanillaSource("minecraft:replacement");
KList<IrisStructurePlacement> placements = new KList<>();
placements.add(editablePlacement("iris:editable_definition"));
placements.add(editablePlacement("iris:replacement_definition"));
placements.add(nativePlacement("minecraft:explicit", StructureDistribution.RANDOM_SPREAD, 1.0));
when(data.load(IrisStructure.class, "iris:editable_definition", false)).thenReturn(editable);
when(data.load(IrisStructure.class, "iris:replacement_definition", false)).thenReturn(replacement);
when(engine.getData()).thenReturn(data);
when(engine.getDimension()).thenReturn(dimension);
when(engine.getWorld()).thenReturn(world);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getHeight()).thenReturn(384);
when(world.platformWorld()).thenReturn(platformWorld);
when(dimension.getImportedStructures()).thenReturn(control);
when(dimension.getStructures()).thenReturn(placements);
when(dimension.getAllRegions(engine)).thenReturn(new KList<>());
when(dimension.getReachableBiomes(engine)).thenReturn(new KList<>());
when(platform.structureHooks()).thenReturn(hooks);
when(hooks.structureKeys()).thenReturn(List.of(
"minecraft:replacement", "minecraft:explicit", "minecraft:reachable"));
when(hooks.reachableStructureKeys(platformWorld)).thenReturn(List.of("minecraft:reachable"));
IrisPlatforms.unbind();
IrisPlatforms.bind(platform);
try {
Set<String> possibilities = new LinkedHashSet<>(
new TestStructureHandler(engine, false).getPossibilities());
assertEquals(Set.of(
"minecraft:replacement",
"iris:editable_definition",
"iris:editable",
"iris:replacement_definition",
"iris:replacement"), possibilities);
assertFalse(possibilities.contains("minecraft:explicit"));
assertFalse(possibilities.contains("minecraft:reachable"));
} finally {
IrisStructureLocator.invalidate(engine);
StructureReachability.invalidate(engine);
IrisPlatforms.unbind();
}
}
@Test
public void propagatesRegistryFailures() {
Engine engine = mock(Engine.class);
IrisPlatform platform = mock(IrisPlatform.class);
PlatformStructureHooks hooks = mock(PlatformStructureHooks.class);
IllegalStateException failure = new IllegalStateException("registry unavailable");
when(platform.structureHooks()).thenReturn(hooks);
when(hooks.structureKeys()).thenThrow(failure);
IrisPlatforms.unbind();
IrisPlatforms.bind(platform);
try {
IllegalStateException thrown = assertThrows(
IllegalStateException.class, () -> new TestStructureHandler(engine, true).getPossibilities());
assertSame(failure, thrown);
} finally {
IrisPlatforms.unbind();
}
}
@Test
public void completionUsesOneReachabilitySnapshotAndLocatableKeys() throws IOException {
Path sourcePath = Path.of(
"src/main/java/art/arcane/iris/util/common/director/specialhandlers/StructureHandler.java");
String source = Files.readString(sourcePath);
int methodStart = source.indexOf("public KList<String> getPossibilities()");
int methodEnd = source.indexOf("@Override\n public String toString", methodStart);
String method = source.substring(methodStart, methodEnd);
assertEquals(1, occurrences(method, "StructureReachability.reachableKeys(activeEngine)"));
assertEquals(0, occurrences(method, "IrisStructureLocator.locatableKeys(activeEngine)"));
assertEquals(1, occurrences(method, "IrisStructureLocator.locatableEditableKeys(activeEngine)"));
assertTrue(method.indexOf("if (activeEngine == null)")
< method.indexOf("IrisPlatforms.get().structureHooks()"));
assertFalse(method.contains("INMS"));
assertFalse(method.contains("catch ("));
}
private static IrisNativeStructureDecision decision(NativeStructureGenerationStatus status) {
return new IrisNativeStructureDecision(status, 0, null, false, false, null, null);
}
private static IrisStructurePlacement nativePlacement(String key, StructureDistribution distribution,
double density) {
IrisStructurePlacement placement = new IrisStructurePlacement();
placement.getNativeStructures().add(new IrisNativeStructure().setStructure(key));
placement.setDistribution(distribution);
placement.setDensity(density);
return placement;
}
private static IrisStructurePlacement editablePlacement(String key) {
IrisStructurePlacement placement = new IrisStructurePlacement();
placement.getStructures().add(key);
return placement;
}
private static int occurrences(String source, String target) {
int count = 0;
int offset = 0;
while ((offset = source.indexOf(target, offset)) >= 0) {
count++;
offset += target.length();
}
return count;
}
private static final class TestStructureHandler extends StructureHandler {
private final Engine activeEngine;
private final boolean nativeGenerationEnabled;
private TestStructureHandler(Engine activeEngine, boolean nativeGenerationEnabled) {
this.activeEngine = activeEngine;
this.nativeGenerationEnabled = nativeGenerationEnabled;
}
@Override
public Engine engine() {
return activeEngine;
}
@Override
protected boolean nativeStructureGenerationEnabled() {
return nativeGenerationEnabled;
}
}
}