mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
Vwop
This commit is contained in:
+10
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-19
@@ -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);
|
||||
|
||||
+102
-49
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -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);
|
||||
|
||||
+13
-18
@@ -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) {
|
||||
|
||||
+54
-26
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -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;
|
||||
}
|
||||
|
||||
+3
-5
@@ -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) {
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public class ModdedGenerationLeaseContractTest {
|
||||
String references = method(source, "public void createReferences(");
|
||||
assertTrue(references.contains("requireGenerationLease(current, \"modded_create_references\")"));
|
||||
assertTrue(references.contains("IrisContext.open(current, lease.sessionId(), null)"));
|
||||
assertTrue(references.contains("super.createReferences(level, structureManager, chunk);"));
|
||||
assertTrue(references.contains("NativeStructureReferenceRepair.createReferences("));
|
||||
assertFalse(references.contains("super.createReferences(level, structureManager, chunk);"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+7
@@ -80,6 +80,11 @@ public class ModdedStructureHooksTest {
|
||||
IllegalStateException.class, hooks::jigsawStructureKeys);
|
||||
IllegalStateException templatePoolKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::templatePoolKeys);
|
||||
IllegalStateException jigsawMetadata = assertThrows(
|
||||
IllegalStateException.class, () -> hooks.jigsawSourceMetadata("minecraft:village"));
|
||||
IllegalStateException templatePoolSpan = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> hooks.templatePoolHorizontalSpan("minecraft:village/plains/town_centers"));
|
||||
IllegalStateException structureSetKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::structureSetKeys);
|
||||
IllegalStateException structureBiomeKeys = assertThrows(
|
||||
@@ -88,6 +93,8 @@ public class ModdedStructureHooksTest {
|
||||
assertTrue(structureKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(jigsawStructureKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(templatePoolKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(jigsawMetadata.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(templatePoolSpan.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(structureSetKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(structureBiomeKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
}
|
||||
|
||||
+4
-3
@@ -56,7 +56,7 @@ public class NativeStructureFailureContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structureTerrainPreparationPrecedesVegetationAndPlacement() throws IOException {
|
||||
public void structureVegetationCleanupPrecedesTerrainPreparationAndPlacement() throws IOException {
|
||||
String source = moddedSource("ModdedNativeStructureStage.java");
|
||||
int placementStart = source.indexOf("void placeVanillaStructures");
|
||||
int placementEnd = source.indexOf("private static String nativeStructureBatchContext", placementStart);
|
||||
@@ -65,9 +65,9 @@ public class NativeStructureFailureContractTest {
|
||||
assertTrue(placement.contains("\"terrain integration\""));
|
||||
assertTrue(placement.contains("prepareSurfaceStructures"));
|
||||
assertTrue(placement.contains("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("prepareSurfaceStructures")
|
||||
< placement.indexOf("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("clearIntersectingVegetation")
|
||||
< placement.indexOf("prepareSurfaceStructures"));
|
||||
assertTrue(placement.indexOf("prepareSurfaceStructures")
|
||||
< placement.indexOf("for (NativePlacementGroup group"));
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ public class NativeStructureFailureContractTest {
|
||||
< placement.indexOf("prepareSurfaceStructures"));
|
||||
assertTrue(source.contains("generationEngine.getHeight(x, z, false) + runtimeMinY + 1"));
|
||||
assertTrue(source.contains("generationEngine.getHeight(x, z, true) + runtimeMinY + 1"));
|
||||
assertTrue(source.contains("int minY = chunk.getMinY() + 1;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.core.project.SchemaBuilder;
|
||||
import art.arcane.iris.engine.object.IrisDirection;
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class SchemaBuilderPlatformIsolationTest {
|
||||
@Test
|
||||
public void enumSchemaDoesNotResolveBukkitOnlyMethodSignatures() {
|
||||
assertThrows(ClassNotFoundException.class, () -> Class.forName("org.bukkit.block.BlockFace"));
|
||||
|
||||
JSONObject schema = new SchemaBuilder(DirectionModel.class, null).construct();
|
||||
JSONObject direction = schema.getJSONObject("properties").getJSONObject("direction");
|
||||
String definitionKey = direction.getString("$ref").substring("#/definitions/".length());
|
||||
JSONArray values = schema.getJSONObject("definitions").getJSONObject(definitionKey).getJSONArray("oneOf");
|
||||
List<String> names = new ArrayList<>(values.length());
|
||||
for (int index = 0; index < values.length(); index++) {
|
||||
names.add(values.getJSONObject(index).getString("const"));
|
||||
}
|
||||
|
||||
assertEquals(List.of(
|
||||
"UP_POSITIVE_Y",
|
||||
"DOWN_NEGATIVE_Y",
|
||||
"NORTH_NEGATIVE_Z",
|
||||
"SOUTH_POSITIVE_Z",
|
||||
"EAST_POSITIVE_X",
|
||||
"WEST_NEGATIVE_X"), names);
|
||||
assertEquals("#/definitions/" + definitionKey,
|
||||
schema.getJSONObject("properties").getJSONObject("directions")
|
||||
.getJSONObject("items").getString("$ref"));
|
||||
}
|
||||
|
||||
@Desc("Direction model.")
|
||||
public static class DirectionModel {
|
||||
@Desc("Direction.")
|
||||
private IrisDirection direction = IrisDirection.NORTH_NEGATIVE_Z;
|
||||
|
||||
@Desc("Directions.")
|
||||
@ArrayType(type = IrisDirection.class)
|
||||
private KList<IrisDirection> directions = new KList<>();
|
||||
}
|
||||
}
|
||||
+56
-7
@@ -1,15 +1,49 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.nativegen.NativeStructureLocateResults;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisModdedStructureCommandTest {
|
||||
@Test
|
||||
public void mixedUnexploredLocateReferencesOnlyTheSelectedProvider() {
|
||||
BlockPos origin = BlockPos.ZERO;
|
||||
Pair<BlockPos, String> irisNear = Pair.of(new BlockPos(4, 70, 0), "iris");
|
||||
Pair<BlockPos, String> nativeFar = Pair.of(new BlockPos(8, 70, 0), "native");
|
||||
AtomicInteger irisReferences = new AtomicInteger();
|
||||
AtomicInteger nativeReferences = new AtomicInteger();
|
||||
|
||||
Pair<BlockPos, String> irisSelected = NativeStructureLocateResults.selectAndReference(
|
||||
origin,
|
||||
irisNear, () -> irisReferences.incrementAndGet(),
|
||||
nativeFar, () -> nativeReferences.incrementAndGet());
|
||||
|
||||
assertSame(irisNear, irisSelected);
|
||||
assertEquals(1, irisReferences.get());
|
||||
assertEquals(0, nativeReferences.get());
|
||||
|
||||
Pair<BlockPos, String> nativeNear = Pair.of(new BlockPos(2, 70, 0), "native");
|
||||
Pair<BlockPos, String> nativeSelected = NativeStructureLocateResults.selectAndReference(
|
||||
origin,
|
||||
irisNear, () -> irisReferences.incrementAndGet(),
|
||||
nativeNear, () -> nativeReferences.incrementAndGet());
|
||||
|
||||
assertSame(nativeNear, nativeSelected);
|
||||
assertEquals(1, irisReferences.get());
|
||||
assertEquals(1, nativeReferences.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gotoStructureSupportsIrisAndNativeRegistryTargets() throws IOException {
|
||||
String source = source("ModdedLocateCommands.java");
|
||||
@@ -40,21 +74,34 @@ public class IrisModdedStructureCommandTest {
|
||||
int methodStart = source.indexOf("Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(");
|
||||
int methodEnd = source.indexOf("HolderSet<Structure> filterReachableNativeStructures(", methodStart);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
int unexploredGuard = method.indexOf("if (findUnexplored)");
|
||||
int registryLookup = method.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
|
||||
int placedCheck = method.indexOf(
|
||||
"if (!IrisStructureLocator.isPlaced(current, structureId))");
|
||||
int irisLocate = method.indexOf("IrisStructureLocator.locate(", placedCheck);
|
||||
"if (!IrisStructureLocator.hasNativePlacement(current, structureId))");
|
||||
int irisLocate = method.indexOf("NativeStructureLocatePersistence.search(", placedCheck);
|
||||
int nearestSelection = method.indexOf(
|
||||
"NativeStructureLocateResults.nearest(pos, predicted, nativeLocated)", irisLocate);
|
||||
int selectedVerification = method.indexOf("bestSearch.search().verify(bestResult)", nearestSelection);
|
||||
int selectedReference = method.indexOf(
|
||||
"selectedSearch.search().reference(selectedStart)", selectedVerification);
|
||||
|
||||
assertTrue(unexploredGuard >= 0);
|
||||
assertTrue(registryLookup > unexploredGuard);
|
||||
assertTrue(registryLookup >= 0);
|
||||
assertTrue(placedCheck > registryLookup);
|
||||
assertTrue(irisLocate > placedCheck);
|
||||
assertTrue(nearestSelection > irisLocate);
|
||||
assertTrue(selectedVerification > nearestSelection);
|
||||
assertTrue(selectedReference > selectedVerification);
|
||||
assertTrue(method.contains("NativeStructureLocatePersistence.probe("));
|
||||
assertTrue(method.contains("findUnexplored"));
|
||||
assertTrue(method.contains("LocateStatus.SEARCH_LIMIT_REACHED"));
|
||||
assertTrue(method.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
assertTrue(method.contains("verified.ownership().locatorY()"));
|
||||
assertTrue(method.contains("selectedSearch.search().reference(selectedStart)"));
|
||||
assertTrue(method.contains("NativeStructureLocateResults.selectAndReference("));
|
||||
assertFalse(method.contains("NativeStructureLocateCapability"));
|
||||
assertTrue(source.contains("structureBiomeSource.isStructureReachable(holder)"));
|
||||
assertFalse(source.contains("isPaperUnavailable"));
|
||||
String generator = moddedSource("IrisModdedChunkGenerator.java");
|
||||
assertTrue(generator.contains("NativeStructureVanillaLocator.predict("));
|
||||
assertFalse(generator.contains("super.findNearestMapStructure(level, reachable"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,7 +115,7 @@ public class IrisModdedStructureCommandTest {
|
||||
int policyResolution = method.indexOf("NativeStructureGenerationPolicy.resolve(engine, target.key(), false)", genericIrisLookup);
|
||||
int replacementCheck = method.indexOf(
|
||||
"decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS", policyResolution);
|
||||
int replacementLocate = method.indexOf("locateIrisStructure(source, level, engine, player, target.key())",
|
||||
int replacementLocate = method.indexOf("runNativeStructureLocate(source, level, player, target)",
|
||||
replacementCheck);
|
||||
|
||||
assertTrue(nativeResolution >= 0);
|
||||
@@ -76,6 +123,8 @@ public class IrisModdedStructureCommandTest {
|
||||
assertTrue(policyResolution > genericIrisLookup);
|
||||
assertTrue(replacementCheck > policyResolution);
|
||||
assertTrue(replacementLocate > replacementCheck);
|
||||
assertTrue(method.contains("!IrisStructureLocator.hasNativePlacement(engine, target.key())"));
|
||||
assertTrue(method.contains("&& target.availability() != NativeStructureAvailability.AVAILABLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipBundle;
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipStore;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructure;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureReferenceRepairTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistedEnvelopeRepairsReloadedMonumentReferenceOutsideLiveBounds() {
|
||||
long seed = 119723L;
|
||||
ChunkPos origin = new ChunkPos(-3, 6);
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart generated = monumentStart(structure, origin, seed);
|
||||
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
|
||||
generated, 0, 80, -64, 320);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24);
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
structure,
|
||||
0,
|
||||
terrain);
|
||||
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
|
||||
"minecraft:monument",
|
||||
wrapped,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
|
||||
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
|
||||
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
origin, seed, new PiecesContainer(wrapped.getPieces()));
|
||||
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
|
||||
ChunkPos target = outsideLiveBounds(ownership, reloaded);
|
||||
|
||||
assertNotNull(target);
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
target, "minecraft:monument", reloaded, ownership));
|
||||
assertFalse(NativeStructureReferenceRepair.requiresReference(
|
||||
target, "minecraft:monument", reloaded, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deniedNaturalStartsCannotEnterCollisionArbitration() {
|
||||
IrisNativeStructureDecision enabled = new IrisNativeStructureDecision(
|
||||
NativeStructureGenerationStatus.GENERATE_NATIVE,
|
||||
0, null, false, false, null, new IrisStructureTerrain());
|
||||
IrisNativeStructureDecision disabled = new IrisNativeStructureDecision(
|
||||
NativeStructureGenerationStatus.DISABLED_BY_PACK,
|
||||
0, null, false, false, null, new IrisStructureTerrain());
|
||||
IrisNativeStructureDecision replaced = new IrisNativeStructureDecision(
|
||||
NativeStructureGenerationStatus.REPLACED_BY_IRIS,
|
||||
0, null, false, false, null, new IrisStructureTerrain());
|
||||
|
||||
assertTrue(NativeStructureReferenceRepair.naturalDecisionAllows(enabled));
|
||||
assertFalse(NativeStructureReferenceRepair.naturalDecisionAllows(disabled));
|
||||
assertFalse(NativeStructureReferenceRepair.naturalDecisionAllows(replaced));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistedManualOwnershipSurvivesDisabledNaturalPolicyDuringReferenceScans()
|
||||
throws Exception {
|
||||
String structureKey = "minecraft:monument";
|
||||
long seed = 582119L;
|
||||
ChunkPos origin = new ChunkPos(2, -4);
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart generated = monumentStart(structure, origin, seed);
|
||||
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
|
||||
generated, 0, 80, -64, 320);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24);
|
||||
StructureStart start = NativeStructureReferenceEnvelope.wrap(
|
||||
generated, structure, 0, terrain);
|
||||
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
|
||||
structureKey,
|
||||
start,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
|
||||
NativeStructureReferenceEnvelope.referenceBounds(start, structure, terrain));
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
dimension.getImportedStructures().getDisabled().add(structureKey);
|
||||
IrisData data = allocateWithoutConstructor(IrisData.class);
|
||||
Engine engine = engine(dimension, data);
|
||||
IrisNativeStructureDecision currentDecision = NativeStructureGenerationPolicy.resolve(
|
||||
engine, structureKey, false);
|
||||
|
||||
assertFalse(currentDecision.generate());
|
||||
assertFalse(NativeStructureReferenceRepair.naturalDecisionAllows(currentDecision));
|
||||
|
||||
ProtoChunk originChunk = emptyChunk(origin);
|
||||
originChunk.setStartForStructure(structure, start);
|
||||
ProtoChunk emptyScannedChunk = emptyChunk(new ChunkPos(Integer.MIN_VALUE, Integer.MIN_VALUE));
|
||||
Registry<Structure> registry = structureRegistry(structure, structureKey);
|
||||
ServerLevel serverLevel = allocateWithoutConstructor(ServerLevel.class);
|
||||
StructureManager structureManager = new StructureManager(null, null, null);
|
||||
ChunkPos coveredTarget = new ChunkPos(
|
||||
ownership.referenceMinChunkX(), ownership.referenceMinChunkZ());
|
||||
ChunkPos uncoveredTarget = outsideReferenceEnvelopeWithinScan(ownership);
|
||||
|
||||
assertNotNull(uncoveredTarget);
|
||||
|
||||
installOwnership(engine, ownership);
|
||||
try {
|
||||
WorldGenLevel level = worldGenLevel(
|
||||
registry, serverLevel, originChunk, emptyScannedChunk);
|
||||
ProtoChunk coveredChunk = emptyChunk(coveredTarget);
|
||||
NativeStructureReferenceRepair.createReferences(
|
||||
engine, level, structureManager, coveredChunk);
|
||||
|
||||
assertSame(start, originChunk.getStartForStructure(structure));
|
||||
assertTrue(originChunk.getStartForStructure(structure).isValid());
|
||||
assertTrue(coveredChunk.getReferencesForStructure(structure).contains(origin.pack()));
|
||||
|
||||
ProtoChunk uncoveredChunk = emptyChunk(uncoveredTarget);
|
||||
NativeStructureReferenceRepair.createReferences(
|
||||
engine, level, structureManager, uncoveredChunk);
|
||||
|
||||
assertSame(start, originChunk.getStartForStructure(structure));
|
||||
assertTrue(originChunk.getStartForStructure(structure).isValid());
|
||||
assertFalse(uncoveredChunk.getReferencesForStructure(structure).contains(origin.pack()));
|
||||
} finally {
|
||||
NativeStructureOwnershipStore.close(engine);
|
||||
}
|
||||
}
|
||||
|
||||
private static StructureStart monumentStart(OceanMonumentStructure structure,
|
||||
ChunkPos origin, long seed) {
|
||||
WorldgenRandom random = new WorldgenRandom(
|
||||
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
|
||||
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
|
||||
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
|
||||
random,
|
||||
origin.getMinBlockX() - 29,
|
||||
origin.getMinBlockZ() - 29,
|
||||
orientation
|
||||
);
|
||||
return new StructureStart(
|
||||
structure,
|
||||
origin,
|
||||
0,
|
||||
new PiecesContainer(List.of(building))
|
||||
);
|
||||
}
|
||||
|
||||
private static NativeStructureStartPlan plan(ChunkPos origin, int baseY) {
|
||||
IrisNativeStructure source = new IrisNativeStructure()
|
||||
.setStructure("minecraft:monument")
|
||||
.setWeight(1);
|
||||
IrisStructurePlacement placement = new IrisStructurePlacement()
|
||||
.setPlacementId("monument-reference-test")
|
||||
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
|
||||
return new NativeStructureStartPlan(
|
||||
placement,
|
||||
source,
|
||||
origin.x(),
|
||||
origin.z(),
|
||||
baseY
|
||||
);
|
||||
}
|
||||
|
||||
private static ChunkPos outsideLiveBounds(NativeStructureOwnershipRecord ownership,
|
||||
StructureStart start) {
|
||||
for (int chunkX = ownership.referenceMinChunkX();
|
||||
chunkX <= ownership.referenceMaxChunkX(); chunkX++) {
|
||||
for (int chunkZ = ownership.referenceMinChunkZ();
|
||||
chunkZ <= ownership.referenceMaxChunkZ(); chunkZ++) {
|
||||
ChunkPos candidate = new ChunkPos(chunkX, chunkZ);
|
||||
if (!start.getBoundingBox().intersects(
|
||||
candidate.getMinBlockX(), candidate.getMinBlockZ(),
|
||||
candidate.getMaxBlockX(), candidate.getMaxBlockZ())) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ChunkPos outsideReferenceEnvelopeWithinScan(
|
||||
NativeStructureOwnershipRecord ownership) {
|
||||
int originX = ownership.originChunkX();
|
||||
int originZ = ownership.originChunkZ();
|
||||
List<ChunkPos> candidates = List.of(
|
||||
new ChunkPos(ownership.referenceMinChunkX() - 1, originZ),
|
||||
new ChunkPos(ownership.referenceMaxChunkX() + 1, originZ),
|
||||
new ChunkPos(originX, ownership.referenceMinChunkZ() - 1),
|
||||
new ChunkPos(originX, ownership.referenceMaxChunkZ() + 1));
|
||||
for (ChunkPos candidate : candidates) {
|
||||
if (!ownership.covers(candidate.x(), candidate.z())
|
||||
&& Math.abs(candidate.x() - originX)
|
||||
<= NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS
|
||||
&& Math.abs(candidate.z() - originZ)
|
||||
<= NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ProtoChunk emptyChunk(ChunkPos position) {
|
||||
return new ProtoChunk(
|
||||
position,
|
||||
UpgradeData.EMPTY,
|
||||
LevelHeightAccessor.create(0, 0),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Registry<Structure> structureRegistry(
|
||||
Structure structure, String structureKey) {
|
||||
Identifier identifier = Identifier.parse(structureKey);
|
||||
InvocationHandler handler = (Object proxy, Method method, Object[] arguments) -> {
|
||||
if (method.getName().equals("getKey")) {
|
||||
return arguments != null && arguments.length == 1
|
||||
&& arguments[0] == structure ? identifier : null;
|
||||
}
|
||||
return defaultProxyValue(proxy, method, arguments);
|
||||
};
|
||||
return (Registry<Structure>) Proxy.newProxyInstance(
|
||||
Registry.class.getClassLoader(),
|
||||
new Class<?>[]{Registry.class},
|
||||
handler);
|
||||
}
|
||||
|
||||
private static WorldGenLevel worldGenLevel(
|
||||
Registry<Structure> registry,
|
||||
ServerLevel serverLevel,
|
||||
ChunkAccess originChunk,
|
||||
ChunkAccess emptyChunk) {
|
||||
RegistryAccess registryAccess = registryAccess(registry);
|
||||
ChunkPos origin = originChunk.getPos();
|
||||
InvocationHandler handler = (Object proxy, Method method, Object[] arguments) -> {
|
||||
return switch (method.getName()) {
|
||||
case "registryAccess" -> registryAccess;
|
||||
case "getLevel" -> serverLevel;
|
||||
case "getChunk" -> arguments != null
|
||||
&& arguments.length >= 2
|
||||
&& ((Integer) arguments[0]) == origin.x()
|
||||
&& ((Integer) arguments[1]) == origin.z()
|
||||
? originChunk : emptyChunk;
|
||||
default -> defaultProxyValue(proxy, method, arguments);
|
||||
};
|
||||
};
|
||||
return (WorldGenLevel) Proxy.newProxyInstance(
|
||||
WorldGenLevel.class.getClassLoader(),
|
||||
new Class<?>[]{WorldGenLevel.class},
|
||||
handler);
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess(Registry<Structure> registry) {
|
||||
InvocationHandler handler = (Object proxy, Method method, Object[] arguments) -> {
|
||||
return switch (method.getName()) {
|
||||
case "lookupOrThrow" -> registry;
|
||||
case "lookup" -> Optional.of(registry);
|
||||
case "registries" -> Stream.empty();
|
||||
default -> defaultProxyValue(proxy, method, arguments);
|
||||
};
|
||||
};
|
||||
return (RegistryAccess) Proxy.newProxyInstance(
|
||||
RegistryAccess.class.getClassLoader(),
|
||||
new Class<?>[]{RegistryAccess.class},
|
||||
handler);
|
||||
}
|
||||
|
||||
private static Engine engine(IrisDimension dimension, IrisData data) throws Exception {
|
||||
TestEngine engine = allocateWithoutConstructor(TestEngine.class);
|
||||
engine.dimension = dimension;
|
||||
engine.data = data;
|
||||
return engine;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void installOwnership(
|
||||
Engine engine, NativeStructureOwnershipRecord ownership) throws Exception {
|
||||
NativeStructureOwnershipBundle bundle =
|
||||
NativeStructureOwnershipBundle.empty().with(ownership);
|
||||
Class<?> storageType = Class.forName(
|
||||
"art.arcane.iris.engine.framework.NativeStructureOwnershipStore$Storage");
|
||||
InvocationHandler storageHandler =
|
||||
(Object proxy, Method method, Object[] arguments) -> {
|
||||
return switch (method.getName()) {
|
||||
case "read" -> arguments != null
|
||||
&& arguments.length == 2
|
||||
&& ((Integer) arguments[0]) == ownership.originChunkX()
|
||||
&& ((Integer) arguments[1]) == ownership.originChunkZ()
|
||||
? bundle : null;
|
||||
case "write", "remove" -> null;
|
||||
default -> defaultProxyValue(proxy, method, arguments);
|
||||
};
|
||||
};
|
||||
Object storage = Proxy.newProxyInstance(
|
||||
storageType.getClassLoader(),
|
||||
new Class<?>[]{storageType},
|
||||
storageHandler);
|
||||
Class<?> stateType = Class.forName(
|
||||
"art.arcane.iris.engine.framework.NativeStructureOwnershipStore$State");
|
||||
Constructor<?> constructor = stateType.getDeclaredConstructor(Engine.class, storageType);
|
||||
constructor.setAccessible(true);
|
||||
Object state = constructor.newInstance(engine, storage);
|
||||
Field statesField = NativeStructureOwnershipStore.class.getDeclaredField("STATES");
|
||||
statesField.setAccessible(true);
|
||||
Cache<Engine, Object> states = (Cache<Engine, Object>) statesField.get(null);
|
||||
states.put(engine, state);
|
||||
}
|
||||
|
||||
private static <T> T allocateWithoutConstructor(Class<T> type) throws Exception {
|
||||
Class<?> unsafeType = Class.forName("sun.misc.Unsafe");
|
||||
Field unsafeField = unsafeType.getDeclaredField("theUnsafe");
|
||||
unsafeField.setAccessible(true);
|
||||
Object unsafe = unsafeField.get(null);
|
||||
Method allocateInstance = unsafeType.getMethod("allocateInstance", Class.class);
|
||||
return type.cast(allocateInstance.invoke(unsafe, type));
|
||||
}
|
||||
|
||||
private static Object defaultProxyValue(
|
||||
Object proxy, Method method, Object[] arguments) {
|
||||
return switch (method.getName()) {
|
||||
case "equals" -> arguments != null && arguments.length == 1
|
||||
&& proxy == arguments[0];
|
||||
case "hashCode" -> System.identityHashCode(proxy);
|
||||
case "toString" -> proxy.getClass().getInterfaces()[0].getSimpleName() + "Proxy";
|
||||
default -> primitiveDefault(method.getReturnType());
|
||||
};
|
||||
}
|
||||
|
||||
private static Object primitiveDefault(Class<?> returnType) {
|
||||
if (!returnType.isPrimitive() || returnType == void.class) {
|
||||
return null;
|
||||
}
|
||||
if (returnType == boolean.class) {
|
||||
return false;
|
||||
}
|
||||
if (returnType == char.class) {
|
||||
return '\0';
|
||||
}
|
||||
if (returnType == byte.class) {
|
||||
return (byte) 0;
|
||||
}
|
||||
if (returnType == short.class) {
|
||||
return (short) 0;
|
||||
}
|
||||
if (returnType == int.class) {
|
||||
return 0;
|
||||
}
|
||||
if (returnType == long.class) {
|
||||
return 0L;
|
||||
}
|
||||
if (returnType == float.class) {
|
||||
return 0.0F;
|
||||
}
|
||||
return 0.0D;
|
||||
}
|
||||
|
||||
private static final class TestEngine extends IrisEngine {
|
||||
private IrisDimension dimension;
|
||||
private IrisData data;
|
||||
|
||||
private TestEngine() {
|
||||
super(null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisDimension getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user