This commit is contained in:
Brian Neumann-Fopiano
2026-08-05 01:16:30 -06:00
parent aaccbacf32
commit cd217c05f9
174 changed files with 28830 additions and 2645 deletions
@@ -27,7 +27,8 @@ import art.arcane.iris.engine.framework.GenerationSessionLease;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.nativegen.NativeStructureStartInjector;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
import art.arcane.iris.nativegen.NativeStructureReferenceRepair;
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
@@ -294,14 +295,14 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Engine current = engine();
try (GenerationSessionLease lease = requireGenerationLease(current, "modded_structure_locate");
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
Pair<BlockPos, Holder<Structure>> irisPlaced = nativeStructures.findNearestIrisStructure(
level, holders, pos, Math.max(1, radius), findUnexplored, current);
HolderSet<Structure> reachable = nativeStructures.filterReachableNativeStructures(
level, holders, current);
Pair<BlockPos, Holder<Structure>> nativeLocated = reachable.size() == 0
? null
: super.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
return NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated);
NativeStructureVanillaLocator.Candidate nativeCandidate =
reachable.size() == 0 ? null
: NativeStructureVanillaLocator.predict(
level, reachable, pos, radius, findUnexplored);
return nativeStructures.findNearestIrisStructure(
level, holders, pos, Math.max(0, radius), findUnexplored, current, nativeCandidate);
}
}
@@ -879,7 +880,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Engine current = engine();
try (GenerationSessionLease lease = requireGenerationLease(current, "modded_create_references");
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
super.createReferences(level, structureManager, chunk);
NativeStructureReferenceRepair.createReferences(
current, level, structureManager, chunk);
}
}
@@ -23,7 +23,7 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackValidationResult;
import art.arcane.iris.core.pack.PackValidator;
import art.arcane.iris.engine.object.IrisDimension;
@@ -74,7 +74,6 @@ public final class ModdedForcedDatapack {
// v2: custom biomes now inherit their vanilla derivative's biome tags, so every already-published pack has
// to regenerate once.
private static final String HASH_SALT = "iris-forced-datapack-v2";
private static final String GIT_DIRECTORY = ".git";
private static final long PACKS_HASH_TTL_NANOS = 2_000_000_000L;
private static final Object LOCK = new Object();
private static final AtomicBoolean LOADED = new AtomicBoolean(false);
@@ -147,13 +146,13 @@ public final class ModdedForcedDatapack {
return;
}
Path packsRoot = packsRoot();
File[] packs = packsRoot.toFile().listFiles(File::isDirectory);
if (packs == null || packs.length == 0) {
List<File> packs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot.toFile());
if (packs.isEmpty()) {
return;
}
LOGGER.error("===============================================================");
LOGGER.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID);
LOGGER.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.length, packsRoot);
LOGGER.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot);
LOGGER.error("Datapack source injection failed for this loader (mixin/event not applied), so world creation will fail and restarting will not fix it.");
LOGGER.error("===============================================================");
}
@@ -360,8 +359,8 @@ public final class ModdedForcedDatapack {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) {
// Studio packs can be git checkouts; .git churns constantly and never reaches the datapack.
return GIT_DIRECTORY.equals(directory.getFileName().toString())
return !directory.equals(root)
&& PackDirectoryResolver.isHiddenName(directory.getFileName().toString())
? FileVisitResult.SKIP_SUBTREE
: FileVisitResult.CONTINUE;
}
@@ -397,16 +396,10 @@ public final class ModdedForcedDatapack {
int packCount = 0;
KList<String> presetIds = new KList<>();
File root = packsRoot().toFile();
File[] packs = root.listFiles(File::isDirectory);
if (packs == null && root.exists()) {
throw new IOException("Iris could not read installed pack directory " + root.getAbsolutePath());
}
if (packs != null) {
Arrays.sort(packs, Comparator.comparing(File::getName));
for (File pack : packs) {
if (stagePack(pack, fixer, stagingDirectory, seenBiomes, presetIds)) {
packCount++;
}
List<File> packs = PackDirectoryResolver.listVisiblePackDirectoriesOrThrow(root);
for (File pack : packs) {
if (stagePack(pack, fixer, stagingDirectory, seenBiomes, presetIds)) {
packCount++;
}
}
@@ -431,8 +424,7 @@ public final class ModdedForcedDatapack {
KList<String> presetIds) throws IOException {
PackValidationResult validation;
try {
validation = PackValidator.validate(sourcePack);
PackValidationRegistry.publish(validation);
validation = PackValidator.validateForDatapackBootstrap(sourcePack);
} catch (Throwable validationFailure) {
LOGGER.error("Iris excluded pack '{}' from Create World because validation failed",
sourcePack.getName(), validationFailure);
@@ -21,17 +21,21 @@ package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.nativegen.NativeStructureLocatePersistence;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
import art.arcane.iris.nativegen.NativeStructureOwnershipRecovery;
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
import art.arcane.iris.nativegen.NativeStructureReferenceEnvelope;
import art.arcane.iris.nativegen.NativeStructureSurfaceFitter;
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
import art.arcane.iris.nativegen.NativeStructureVegetationClearer;
import art.arcane.iris.nativegen.NativeStructureVerticalPlacer;
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.math.RNG;
@@ -60,6 +64,7 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -85,42 +90,85 @@ final class ModdedNativeStructureStage {
Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(ServerLevel level,
HolderSet<Structure> holders,
BlockPos pos, int radius, boolean findUnexplored,
Engine current) {
if (findUnexplored) {
return null;
}
Engine current,
NativeStructureVanillaLocator.Candidate nativeCandidate) {
Pair<BlockPos, Holder<Structure>> nativeLocated =
nativeCandidate == null ? null : nativeCandidate.result();
Runnable nativeReference = () -> {
if (nativeCandidate != null) {
nativeCandidate.reference(level.structureManager());
}
};
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
BlockPos best = null;
Holder<Structure> bestHolder = null;
long bestDistance = Long.MAX_VALUE;
List<IrisNativeLocateSearch> searches = new ArrayList<>(holders.size());
NativeStructureLocatePersistence.ProbeBudget budget = NativeStructureLocatePersistence.probeBudget();
for (Holder<Structure> holder : holders) {
Identifier id = registry.getKey(holder.value());
if (id == null) {
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
}
String structureId = id.toString();
if (!IrisStructureLocator.isPlaced(current, structureId)) {
if (!IrisStructureLocator.hasNativePlacement(current, structureId)) {
continue;
}
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
current, structureId, pos.getX(), pos.getZ(), radius);
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
throw new IllegalStateException("Iris structure locate reached its safety limit for "
+ structureId + " within " + radius + " chunks");
}
if (!result.found()) {
continue;
}
long dx = (long) result.originX() - pos.getX();
long dz = (long) result.originZ() - pos.getZ();
long distance = dx * dx + dz * dz;
if (distance < bestDistance) {
bestDistance = distance;
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
bestHolder = holder;
}
NativeStructureLocatePersistence.Probe probe = NativeStructureLocatePersistence.probe(
level, holder.value(), findUnexplored, budget);
searches.add(new IrisNativeLocateSearch(
holder, structureId, NativeStructureLocatePersistence.search(
current, structureId, pos.getX(), pos.getZ(), radius, probe)));
}
return best == null ? null : Pair.of(best, bestHolder);
searches.sort(Comparator.comparing(IrisNativeLocateSearch::structureId));
for (int attempt = 0; attempt < NativeStructureLocatePersistence.MAX_SELECTED_CANDIDATE_RETRIES; attempt++) {
IrisNativeLocateSearch bestSearch = null;
IrisStructureLocator.LocateResult bestResult = null;
long bestDistance = Long.MAX_VALUE;
for (IrisNativeLocateSearch search : searches) {
IrisStructureLocator.LocateResult result = search.search().predict();
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
throw new IllegalStateException("Iris structure locate reached its safety limit for "
+ search.structureId() + " within " + radius + " placement rings");
}
if (!result.found()) {
continue;
}
long dx = (long) result.originX() - pos.getX();
long dz = (long) result.originZ() - pos.getZ();
long distance = dx * dx + dz * dz;
if (distance < bestDistance) {
bestDistance = distance;
bestSearch = search;
bestResult = result;
}
}
if (bestSearch == null) {
return NativeStructureLocateResults.selectAndReference(
pos, null, () -> { }, nativeLocated, nativeReference);
}
Pair<BlockPos, Holder<Structure>> predicted = Pair.of(
new BlockPos(bestResult.originX(), bestResult.baseY(), bestResult.originZ()),
bestSearch.holder());
if (NativeStructureLocateResults.nearest(pos, predicted, nativeLocated) != predicted) {
return NativeStructureLocateResults.selectAndReference(
pos, predicted, () -> { }, nativeLocated, nativeReference);
}
NativeStructureLocatePersistence.VerifiedStart verified =
bestSearch.search().verify(bestResult);
if (verified == null) {
bestSearch.search().reject(bestResult);
continue;
}
BlockPos located = new BlockPos(
bestResult.originX(), verified.ownership().locatorY(),
bestResult.originZ());
Pair<BlockPos, Holder<Structure>> irisLocated = Pair.of(located, bestSearch.holder());
IrisNativeLocateSearch selectedSearch = bestSearch;
NativeStructureLocatePersistence.VerifiedStart selectedStart = verified;
return NativeStructureLocateResults.selectAndReference(
pos, irisLocated, () -> selectedSearch.search().reference(selectedStart),
nativeLocated, nativeReference);
}
throw new IllegalStateException("Iris structure locate rejected too many selected candidates within "
+ radius + " placement rings");
}
HolderSet<Structure> filterReachableNativeStructures(ServerLevel level, HolderSet<Structure> holders,
@@ -193,10 +241,14 @@ final class ModdedNativeStructureStage {
decision.preserveSourceY(),
decision.yBand(),
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
start, structure, start.getReferences(), templateManager,
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()));
StructureStart wrapped = NativeStructureReferenceEnvelope.wrapForPublication(
start, structure, start.getReferences(),
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
structureId);
chunk.setStartForStructure(structure, wrapped);
if (!wrapped.isValid()) {
continue;
}
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"vertical adjustment", structureId, chunkPos.x(), chunkPos.z(), error);
@@ -227,7 +279,6 @@ final class ModdedNativeStructureStage {
Engine current = generator.engine();
List<NativePlacementGroup> placementGroups = new ArrayList<>();
List<StructureStart> heightmapStarts = new ArrayList<>();
List<StructureStart> nativeStarts = new ArrayList<>();
List<NativeStructureVegetationClearer.VegetationTarget> vegetationTargets = new ArrayList<>();
List<NativeStructureTerrainIntegrator.TerrainTarget> terrainTargets = new ArrayList<>();
for (int step = 0; step < steps; step++) {
@@ -245,10 +296,11 @@ final class ModdedNativeStructureStage {
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
List<NativePlacement> resolvedPlacements = new ArrayList<>(starts.size());
for (StructureStart start : starts) {
NativeStructureStartPlan plan = NativeStructurePlacementPlanner.matchingPlan(
current, structureId, start.getChunkPos().x(), start.getChunkPos().z());
IrisNativeStructureDecision decision = plan == null
? sourceDecision : NativeStructurePlacementPlanner.decisionFor(plan);
NativeStructureOwnershipRecord ownership =
NativeStructureOwnershipRecovery.resolve(
current, world.getLevel(), structureId, structure, start);
IrisNativeStructureDecision decision =
ownership == null ? sourceDecision : ownership.restoredDecision();
if (!decision.generate()) {
continue;
}
@@ -258,9 +310,6 @@ final class ModdedNativeStructureStage {
structureId, start,
NativeStructureTerrainIntegrator.resolveNativeTerrain(
start, decision.terrain())));
if (plan == null || !plan.placement().isUnderground()) {
nativeStarts.add(start);
}
boolean clearEntireFootprint = NativeStructureVegetationClearer
.shouldClearEntireVegetationFootprint(
structure.step(), decision.clearVegetation());
@@ -289,15 +338,6 @@ final class ModdedNativeStructureStage {
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, nativeStarts,
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, vegetationTargets);
@@ -306,6 +346,15 @@ final class ModdedNativeStructureStage {
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, terrainTargets,
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructurePostProcessor.prepareTerrain(
world, area, terrainTargets, this::resolvePaletteBlock);
@@ -413,8 +462,8 @@ final class ModdedNativeStructureStage {
ChunkPos chunkPos = chunk.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
int minY = chunk.getMinY();
int maxY = minY + chunk.getHeight() - 1;
int minY = chunk.getMinY() + 1;
int maxY = chunk.getMinY() + chunk.getHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@@ -436,6 +485,10 @@ final class ModdedNativeStructureStage {
List<NativePlacement> placements) {
}
private record IrisNativeLocateSearch(Holder<Structure> holder, String structureId,
NativeStructureLocatePersistence.Search search) {
}
private record StructureStepCache(Registry<Structure> registry, List<List<Structure>> structures) {
}
}
@@ -56,8 +56,8 @@ public final class ModdedPackInstaller {
synchronized (installLock) {
File packs = configDir.resolve("irisworldgen").resolve("packs").toFile();
try {
boolean installed = PackDownloader.isDefaultOverworld(pack)
? PackDownloader.downloadDefaultOverworld(packs, forceOverwrite, feedback) != null
PackDownloader.PackInstallResult result = PackDownloader.isDefaultOverworld(pack)
? PackDownloader.downloadDefaultOverworld(packs, forceOverwrite, feedback)
: PackDownloader.download(
packs,
"IrisDimensions/" + pack,
@@ -65,8 +65,9 @@ public final class ModdedPackInstaller {
forceOverwrite,
false,
pack,
feedback) != null;
if (installed) {
feedback);
boolean installed = result != null;
if (result != null && result.changed()) {
// Pack-install completion is one of the four forced-datapack regeneration triggers; every
// install call site already runs off the server thread, so regenerate inline here. A
// regeneration failure must never turn a successful install into a failed one.
@@ -76,6 +77,9 @@ public final class ModdedPackInstaller {
LOGGER.error("Iris installed pack '{}' but could not regenerate the forced datapack", pack, regenerationFailure);
}
}
if (result != null && result.restartRequired()) {
feedback.accept("Pack '" + pack + "' is installed on disk and requires a server restart before its active data changes.");
}
return installed;
} catch (IOException error) {
LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, branch, error);
@@ -28,6 +28,7 @@ import art.arcane.iris.spi.PlatformEntityType;
import art.arcane.iris.spi.PlatformItem;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.volmlib.util.data.UnresolvedKeyLog;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
@@ -40,6 +41,7 @@ import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.storage.loot.LootTable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -212,6 +214,20 @@ public final class ModdedRegistries implements PlatformRegistries {
return keys;
}
@Override
public List<String> lootTableKeys() {
List<String> keys = new ArrayList<>();
MinecraftServer instance = server.get();
if (instance == null) {
warnNotReady("loot table");
return keys;
}
HolderLookup.RegistryLookup<LootTable> registry = instance.reloadableRegistries().lookup()
.lookupOrThrow(Registries.LOOT_TABLE);
registry.listElementIds().forEach(key -> keys.add(key.identifier().toString()));
return keys;
}
@Override
public Map<String, List<PlatformBlockProperty>> blockStateProperties() {
Map<String, List<PlatformBlockProperty>> properties = new LinkedHashMap<>();
@@ -19,6 +19,7 @@
package art.arcane.iris.modded;
import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackDownloader;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.pack.PackValidationResult;
@@ -104,9 +105,9 @@ public final class ModdedStartup {
public static void validateAllPacks() {
File packsRoot = ModdedPackCommands.packsRoot();
File[] packDirs = packsRoot.listFiles(File::isDirectory);
List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
PackValidationRegistry.clear();
if (packDirs == null || packDirs.length == 0) {
if (packDirs.isEmpty()) {
LOGGER.info("Iris found no packs to validate under {}; install one with /iris download <pack>",
packsRoot.getAbsolutePath());
return;
@@ -146,8 +147,8 @@ public final class ModdedStartup {
if (pack == null || pack.isBlank()) {
throw new IllegalArgumentException("Pack name is required for world creation");
}
File packDir = new File(ModdedPackCommands.packsRoot(), pack);
if (!packDir.isDirectory()) {
File packDir = PackDirectoryResolver.resolveExisting(ModdedPackCommands.packsRoot(), pack);
if (packDir == null) {
throw new BrokenPackException(pack, List.of(
"Pack folder does not exist under " + ModdedPackCommands.packsRoot().getAbsolutePath() + "."));
}
@@ -19,8 +19,10 @@
package art.arcane.iris.modded;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.nativegen.NativeStructureFactory;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.spi.PlatformStructureHooks.JigsawSourceMetadata;
import art.arcane.iris.spi.PlatformWorld;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@@ -95,6 +97,67 @@ public final class ModdedStructureHooks implements PlatformStructureHooks {
return registryKeys(Registries.TEMPLATE_POOL);
}
@Override
public JigsawSourceMetadata jigsawSourceMetadata(String structureKey) {
MinecraftServer instance = requireServer("resolve live jigsaw metadata for registered structure '"
+ structureKey + "'");
try {
Identifier identifier = Identifier.tryParse(structureKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
}
Registry<Structure> registry = instance.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Structure structure = registry.getValue(identifier);
if (structure == null) {
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
}
if (!(structure instanceof JigsawStructure jigsaw)) {
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
}
return NativeStructureFactory.sourceMetadata(
instance.registryAccess(), instance.getStructureManager(), jigsaw);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve live jigsaw metadata for registered structure '"
+ structureKey + "' from the modded structure registry", error);
}
}
@Override
public int templatePoolHorizontalSpan(String templatePoolKey) {
MinecraftServer instance = requireServer("resolve the live horizontal span for registered template pool '"
+ templatePoolKey + "'");
try {
return NativeStructureFactory.templatePoolHorizontalSpan(
instance.registryAccess(), instance.getStructureManager(), templatePoolKey);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve the live horizontal span for registered "
+ "template pool '" + templatePoolKey + "' from the modded template-pool registry", error);
}
}
@Override
public int jigsawStartPoolHorizontalSpan(String structureKey, String templatePoolKey) {
MinecraftServer instance = requireServer("resolve the effective start-pool span for registered jigsaw '"
+ structureKey + "'");
try {
Identifier identifier = Identifier.tryParse(structureKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
}
Structure structure = instance.registryAccess().lookupOrThrow(Registries.STRUCTURE)
.getValue(identifier);
if (!(structure instanceof JigsawStructure jigsaw)) {
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
}
return NativeStructureFactory.jigsawStartPoolHorizontalSpan(
instance.registryAccess(), instance.getStructureManager(), jigsaw, templatePoolKey);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve the effective start-pool span for registered "
+ "jigsaw structure '" + structureKey + "' and pool '" + templatePoolKey
+ "' from the modded registries", error);
}
}
@Override
public List<String> structureSetKeys() {
return registryKeys(Registries.STRUCTURE_SET);
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
@@ -153,24 +154,18 @@ final class ModdedCommandSuggestions {
names.add("overworld");
try {
File packs = ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("packs").toFile();
File[] children = packs.listFiles();
if (children != null) {
for (File child : children) {
if (!child.isDirectory()) {
continue;
}
String packName = child.getName();
names.add(packName);
File dimensions = new File(child, "dimensions");
File[] dimensionFiles = dimensions.listFiles(
(File directory, String name) -> name.endsWith(".json"));
if (dimensionFiles == null) {
continue;
}
for (File dimensionFile : dimensionFiles) {
String fileName = dimensionFile.getName();
names.add(packName + ":" + fileName.substring(0, fileName.length() - 5));
}
for (File child : PackDirectoryResolver.listVisiblePackDirectories(packs)) {
String packName = child.getName();
names.add(packName);
File dimensions = new File(child, "dimensions");
File[] dimensionFiles = dimensions.listFiles(
(File directory, String name) -> name.endsWith(".json"));
if (dimensionFiles == null) {
continue;
}
for (File dimensionFile : dimensionFiles) {
String fileName = dimensionFile.getName();
names.add(packName + ":" + fileName.substring(0, fileName.length() - 5));
}
}
} catch (Throwable e) {
@@ -20,6 +20,7 @@ package art.arcane.iris.modded.command;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
@@ -42,8 +43,12 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
import art.arcane.iris.core.localization.IrisLanguage;
@@ -74,11 +79,11 @@ public final class ModdedDatapackCommands {
root.then(Commands.literal("ls")
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
root.then(message("ingest", "Modrinth datapack ingest requires the Bukkit plugin because its editable-resource import and manifest workflow use Bukkit tooling. Iris modded dimensions do run native vanilla and datapack structure placement; install the datapack in world/datapacks and restart to generate its registered structures."));
root.then(message("pull", "Modrinth datapack ingest requires the Bukkit plugin because its editable-resource import and manifest workflow use Bukkit tooling. Iris modded dimensions do run native vanilla and datapack structure placement; install the datapack in world/datapacks and restart to generate its registered structures."));
root.then(message("ingest", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions."));
root.then(message("pull", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions."));
root.then(message("remove", "Datapack removal manages the Bukkit ingest manifest. On modded servers delete the datapack folder from world/datapacks and restart."));
root.then(message("rm", "Datapack removal manages the Bukkit ingest manifest. On modded servers delete the datapack folder from world/datapacks and restart."));
root.then(message("remove", "Managed datapack removal is Bukkit-only. On modded servers disable the pack, delete its folder or zip from world/datapacks, and restart."));
root.then(message("rm", "Managed datapack removal is Bukkit-only. On modded servers disable the pack, delete its folder or zip from world/datapacks, and restart."));
return root;
}
@@ -215,27 +220,24 @@ public final class ModdedDatapackCommands {
MinecraftServer server = source.getServer();
LinkedHashSet<String> configured = new LinkedHashSet<>();
File packsRoot = ModdedPackCommands.packsRoot();
File[] packs = packsRoot.isDirectory() ? packsRoot.listFiles(File::isDirectory) : null;
if (packs != null) {
for (File pack : packs) {
if (!new File(pack, "dimensions").isDirectory()) {
continue;
}
try {
IrisData data = IrisData.get(pack);
for (IrisDimension dimension : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) {
if (dimension == null || dimension.getDatapackImports() == null) {
continue;
}
for (String url : dimension.getDatapackImports()) {
if (url != null && !url.isBlank()) {
configured.add(url.trim());
}
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsRoot)) {
if (!new File(pack, "dimensions").isDirectory()) {
continue;
}
try {
IrisData data = IrisData.get(pack);
for (IrisDimension dimension : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) {
if (dimension == null || dimension.getDatapackImports() == null) {
continue;
}
for (String url : dimension.getDatapackImports()) {
if (url != null && !url.isBlank()) {
configured.add(url.trim());
}
}
} catch (Throwable e) {
LOGGER.error("Iris datapack import scan failed for pack {}", pack.getName(), e);
}
} catch (Throwable e) {
LOGGER.error("Iris datapack import scan failed for pack {}", pack.getName(), e);
}
}
@@ -248,19 +250,45 @@ public final class ModdedDatapackCommands {
}
File datapacks = worldDatapacksFolder(server);
File[] installed = datapacks.isDirectory() ? datapacks.listFiles(File::isDirectory) : null;
File[] installed = datapacks.isDirectory()
? datapacks.listFiles(file -> file.isDirectory() || file.isFile() && file.getName().toLowerCase(Locale.ROOT).endsWith(".zip"))
: null;
Set<String> availableIds = new HashSet<>(server.getPackRepository().getAvailableIds());
Set<String> selectedIds = new HashSet<>(server.getPackRepository().getSelectedIds());
KList<String> names = new KList<>();
if (installed != null) {
for (File folder : installed) {
if (new File(folder, "pack.mcmeta").isFile()) {
names.add(folder.getName());
for (File installedPack : installed) {
String name = installedPack.getName();
String repositoryId = resolveRepositoryId(name, availableIds);
String state;
if (repositoryId == null) {
state = "unavailable";
} else if (selectedIds.contains(repositoryId)) {
state = "enabled";
} else {
state = "disabled";
}
names.add(name + " [" + state + "]");
}
}
Collections.sort(names);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_INSTALLED_WORLD_DATAPACKS, MessageArgument.untrusted("value", names.size())));
for (String name : names) {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_MESSAGE_2, MessageArgument.untrusted("name", name)));
}
return 1;
}
private static String resolveRepositoryId(String filename, Set<String> availableIds) {
String direct = "file/" + filename;
if (availableIds.contains(direct)) {
return direct;
}
for (String availableId : availableIds) {
if (availableId.equals(filename) || availableId.endsWith("/" + filename)) {
return availableId;
}
}
return null;
}
}
@@ -173,10 +173,16 @@ final class ModdedLocateCommands {
return 0;
}
if (decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
locateIrisStructure(source, level, engine, player, target.key());
if (!IrisStructureLocator.hasNativePlacement(engine, target.key())) {
locateIrisStructure(source, level, engine, player, target.key());
return 1;
}
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCHING_NATIVE_STRUCTURE_WITHIN_CHUNKS, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("NATIVESTRUCTURELOCATERADIUS", NATIVE_STRUCTURE_LOCATE_RADIUS)));
runNativeStructureLocate(source, level, player, target);
return 1;
}
if (target.availability() != NativeStructureAvailability.AVAILABLE) {
if (!IrisStructureLocator.hasNativePlacement(engine, target.key())
&& target.availability() != NativeStructureAvailability.AVAILABLE) {
IrisModdedCommands.fail(source, nativeUnavailableMessage(target.key(), target.availability()));
return 0;
}
@@ -110,14 +110,12 @@ public final class ModdedPackCommands {
List<File> targets = new ArrayList<>();
if (pack == null || pack.isBlank()) {
File[] dirs = packsRoot.listFiles(File::isDirectory);
if (dirs == null || dirs.length == 0) {
List<File> dirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
if (dirs.isEmpty()) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_PACKS_VALIDATE_UNDER, MessageArgument.untrusted("value", packsRoot.getAbsolutePath())));
return 0;
}
for (File dir : dirs) {
targets.add(dir);
}
targets.addAll(dirs);
} else {
File target = PackDirectoryResolver.resolveExisting(packsRoot, pack);
if (target == null) {