mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
Refused unstageable takeovers
- Datapack staging refuses a takeover it cannot stage when the level has applied takeover state or Iris generation history, instead of silently leaving the mod generator in place - A level's retained generation history now stages its takeover even when the installed pack is gone - Takeover pack names and dimension keys are resolved to the installed folder and file names before staging, reconciliation, and recording, and the takeover command stores that form - Reconciliation refuses to switch an applied level to an uninstalled pack before moving any data, and leaves a never-applied level untouched until its pack is installed
This commit is contained in:
1 parent
cf0587c112
commit
038833e614
7 files changed
+361
-71
No files matched your search
+85
-5
@@ -18,13 +18,16 @@
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -36,6 +39,8 @@ public final class ModdedDimensionTakeovers {
|
||||
private static final String GENERATOR_TYPE = "irisworldgen:iris";
|
||||
private static final String PLACEHOLDER_BIOME_SOURCE = "minecraft:fixed";
|
||||
private static final String PLACEHOLDER_BIOME = "minecraft:plains";
|
||||
private static final String DIMENSIONS = "dimensions";
|
||||
private static final String JSON = ".json";
|
||||
|
||||
private ModdedDimensionTakeovers() {
|
||||
}
|
||||
@@ -80,13 +85,12 @@ public final class ModdedDimensionTakeovers {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void writeLevelStems(Path datapackRoot, Map<String, String> configured,
|
||||
Map<String, String> stagedDimensionTypes) throws IOException {
|
||||
public static void writeLevelStems(Path datapackRoot, Map<String, String> configured, StagedTypes staged,
|
||||
Path worldRoot) throws IOException {
|
||||
for (ModdedDimensionTakeover takeover : parseAll(configured)) {
|
||||
String typeRef = stagedDimensionTypes.get(takeover.pack() + ":" + takeover.packDimension());
|
||||
String typeRef = staged.typeRef(takeover, worldRoot);
|
||||
if (typeRef == null) {
|
||||
ModdedIrisLog.error("Iris skipped the dimension takeover of {}: pack '{}' dimension '{}' is not installed or failed validation, so the dimension keeps its own generator",
|
||||
takeover.levelKey(), takeover.pack(), takeover.packDimension());
|
||||
skipUnstaged(takeover, staged, worldRoot);
|
||||
continue;
|
||||
}
|
||||
writeLevelStem(datapackRoot, takeover, typeRef);
|
||||
@@ -95,6 +99,40 @@ public final class ModdedDimensionTakeovers {
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> configured() throws IOException {
|
||||
return canonical(ModdedModConfig.get().dimensionTakeovers(), IrisPlatforms.get().packsFolderNoCreate().toPath());
|
||||
}
|
||||
|
||||
static Map<String, String> canonical(Map<String, String> configured, Path packsRoot) throws IOException {
|
||||
Map<String, String> canonical = new TreeMap<>();
|
||||
for (ModdedDimensionTakeover takeover : parseAll(configured)) {
|
||||
canonical.put(takeover.levelKey(), canonical(takeover, packsRoot).packRef());
|
||||
}
|
||||
return Map.copyOf(canonical);
|
||||
}
|
||||
|
||||
public static ModdedDimensionTakeover canonical(ModdedDimensionTakeover takeover, Path packsRoot) throws IOException {
|
||||
Path pack = packsRoot.resolve(takeover.pack());
|
||||
if (!Files.isDirectory(pack)) {
|
||||
return takeover;
|
||||
}
|
||||
String packName = sameIgnoringCase(pack.toRealPath().getFileName().toString(), takeover.pack());
|
||||
Path dimensions = pack.resolve(DIMENSIONS);
|
||||
Path dimensionFile = dimensions.resolve(takeover.packDimension() + JSON);
|
||||
String dimension = takeover.packDimension();
|
||||
if (Files.isRegularFile(dimensionFile)) {
|
||||
String relative = dimensions.toRealPath().relativize(dimensionFile.toRealPath()).toString()
|
||||
.replace(File.separatorChar, '/');
|
||||
dimension = sameIgnoringCase(relative.substring(0, relative.length() - JSON.length()), takeover.packDimension());
|
||||
}
|
||||
return new ModdedDimensionTakeover(takeover.levelKey(), packName, dimension);
|
||||
}
|
||||
|
||||
public static boolean installed(ModdedDimensionTakeover takeover, Path packsRoot) {
|
||||
return Files.isRegularFile(packsRoot.resolve(takeover.pack()).resolve(DIMENSIONS)
|
||||
.resolve(takeover.packDimension() + JSON));
|
||||
}
|
||||
|
||||
public static Set<String> changedLevels(Map<String, String> applied, Map<String, String> configured) {
|
||||
Set<String> levels = new TreeSet<>(applied.keySet());
|
||||
levels.addAll(configured.keySet());
|
||||
@@ -107,6 +145,32 @@ public final class ModdedDimensionTakeovers {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static void skipUnstaged(ModdedDimensionTakeover takeover, StagedTypes staged, Path worldRoot) throws IOException {
|
||||
String reason = "pack '" + takeover.pack() + "' dimension '" + takeover.packDimension()
|
||||
+ "' is not installed or failed validation" + caseHint(takeover, staged);
|
||||
if (worldRoot != null && ModdedTakeoverReconciler.hasTakeoverRecord(worldRoot, takeover.levelKey())) {
|
||||
throw new IOException("Iris refused to stage the dimension takeover of " + takeover.levelKey() + ": " + reason
|
||||
+ ", and the level already has an applied takeover or Iris generation history. Restore the pack, or run /iris world takeover "
|
||||
+ takeover.levelKey() + " off to hand the level back to its own generator.");
|
||||
}
|
||||
ModdedIrisLog.error("Iris skipped the dimension takeover of {}: {}, so the dimension keeps its own generator and the takeover stays unapplied",
|
||||
takeover.levelKey(), reason);
|
||||
}
|
||||
|
||||
private static String caseHint(ModdedDimensionTakeover takeover, StagedTypes staged) {
|
||||
String wanted = takeover.pack() + ":" + takeover.packDimension();
|
||||
for (String installed : staged.installed().keySet()) {
|
||||
if (installed.equalsIgnoreCase(wanted)) {
|
||||
return "; pack names and dimension keys are case-sensitive, the installed dimension is " + installed;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String sameIgnoringCase(String actual, String configured) {
|
||||
return actual.equalsIgnoreCase(configured) ? actual : configured;
|
||||
}
|
||||
|
||||
public static String fingerprint(Map<String, String> configured) {
|
||||
StringBuilder canonical = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : new TreeMap<>(configured).entrySet()) {
|
||||
@@ -114,4 +178,20 @@ public final class ModdedDimensionTakeovers {
|
||||
}
|
||||
return canonical.toString();
|
||||
}
|
||||
|
||||
public record StagedTypes(Map<String, String> installed, Map<Path, String> retained) {
|
||||
public static StagedTypes create() {
|
||||
return new StagedTypes(new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
String typeRef(ModdedDimensionTakeover takeover, Path worldRoot) {
|
||||
if (worldRoot != null) {
|
||||
String retainedType = retained.get(takeover.storageFolder(worldRoot).toAbsolutePath().normalize());
|
||||
if (retainedType != null) {
|
||||
return retainedType;
|
||||
}
|
||||
}
|
||||
return installed.get(takeover.pack() + ":" + takeover.packDimension());
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-13
@@ -324,7 +324,7 @@ public final class ModdedForcedDatapack {
|
||||
false
|
||||
);
|
||||
digest.update(inputFingerprint.getBytes(StandardCharsets.UTF_8));
|
||||
digest.update(ModdedDimensionTakeovers.fingerprint(ModdedModConfig.get().dimensionTakeovers())
|
||||
digest.update(ModdedDimensionTakeovers.fingerprint(ModdedDimensionTakeovers.configured())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return HexFormat.of().formatHex(digest.digest());
|
||||
}
|
||||
@@ -335,7 +335,7 @@ public final class ModdedForcedDatapack {
|
||||
|
||||
int packCount = 0;
|
||||
KList<String> presetIds = new KList<>();
|
||||
Map<String, String> stagedDimensionTypes = new LinkedHashMap<>();
|
||||
ModdedDimensionTakeovers.StagedTypes stagedDimensionTypes = ModdedDimensionTakeovers.StagedTypes.create();
|
||||
Path worldRoot = worldRootOrNull();
|
||||
Set<PackSelection> legacySelections = unadoptedSelections(worldRoot);
|
||||
List<File> packRoots = compilationPackRoots(worldRoot);
|
||||
@@ -347,7 +347,7 @@ public final class ModdedForcedDatapack {
|
||||
}
|
||||
if (!ModdedEngineBootstrap.loader().clientEnvironment()) {
|
||||
ModdedDimensionTakeovers.writeLevelStems(stagingDirectory,
|
||||
ModdedModConfig.get().dimensionTakeovers(), stagedDimensionTypes);
|
||||
ModdedDimensionTakeovers.configured(), stagedDimensionTypes, worldRoot);
|
||||
}
|
||||
|
||||
IrisDatapackCompiler.installRetainedRegistrySources(packRoots, List.of(stagingDirectory.toFile()), fixer);
|
||||
@@ -370,7 +370,7 @@ public final class ModdedForcedDatapack {
|
||||
private static boolean stagePack(PackStageContext context, IDataFixer fixer, Path stagingDirectory,
|
||||
Map<String, KSet<String>> seenBiomes,
|
||||
KList<String> presetIds,
|
||||
Map<String, String> stagedDimensionTypes) throws IOException {
|
||||
ModdedDimensionTakeovers.StagedTypes stagedDimensionTypes) throws IOException {
|
||||
File sourcePack = context.packRoot().toFile();
|
||||
PackValidationResult validation;
|
||||
try {
|
||||
@@ -400,7 +400,7 @@ public final class ModdedForcedDatapack {
|
||||
stagingDirectory.getParent(), PACK_FOLDER + ".pack-" + sourcePack.getName() + "-");
|
||||
Map<String, KSet<String>> packBiomes = new LinkedHashMap<>();
|
||||
KList<String> packPresetIds = new KList<>();
|
||||
Map<String, String> packDimensionTypes = new LinkedHashMap<>();
|
||||
ModdedDimensionTakeovers.StagedTypes packDimensionTypes = ModdedDimensionTakeovers.StagedTypes.create();
|
||||
KList<File> packFolders = new KList<>();
|
||||
packFolders.add(packStagingDirectory.toFile());
|
||||
boolean installed;
|
||||
@@ -423,7 +423,8 @@ public final class ModdedForcedDatapack {
|
||||
mergeDirectory(packStagingDirectory, stagingDirectory);
|
||||
mergeBiomes(seenBiomes, packBiomes);
|
||||
presetIds.addAll(packPresetIds);
|
||||
stagedDimensionTypes.putAll(packDimensionTypes);
|
||||
stagedDimensionTypes.installed().putAll(packDimensionTypes.installed());
|
||||
stagedDimensionTypes.retained().putAll(packDimensionTypes.retained());
|
||||
return true;
|
||||
} finally {
|
||||
try {
|
||||
@@ -483,7 +484,7 @@ public final class ModdedForcedDatapack {
|
||||
private static boolean installPack(PackStageContext context, IDataFixer fixer, KList<File> folders,
|
||||
Map<String, KSet<String>> seenBiomes,
|
||||
KList<String> presetIds,
|
||||
Map<String, String> dimensionTypes) throws IOException {
|
||||
ModdedDimensionTakeovers.StagedTypes dimensionTypes) throws IOException {
|
||||
File packFolder = context.packRoot().toFile();
|
||||
String packName = context.packName();
|
||||
File dimensionsDirectory = new File(packFolder, "dimensions");
|
||||
@@ -531,8 +532,11 @@ public final class ModdedForcedDatapack {
|
||||
context
|
||||
);
|
||||
}
|
||||
if (context.retainedDimensionRoot() != null) {
|
||||
dimensionTypes.retained().put(context.retainedDimensionRoot(), typeRef);
|
||||
}
|
||||
if (context.emitPresets()) {
|
||||
dimensionTypes.put(packName + ":" + dimensionKey, typeRef);
|
||||
dimensionTypes.installed().put(packName + ":" + dimensionKey, typeRef);
|
||||
if (!dimension.hasBorrowedDimensionType()) {
|
||||
String presetRef = ModdedWorldgenIds.presetRef(packName, dimensionKey);
|
||||
writeWorldPreset(folders, packName, dimensionKey, presetRef);
|
||||
@@ -830,13 +834,13 @@ public final class ModdedForcedDatapack {
|
||||
GenerationEpoch.DimensionContract contract = retained.epoch().dimensionContract();
|
||||
Optional<ModdedWorldgenIds.ScopedDimension> scoped = ModdedWorldgenIds.scopedDimensionType(contract.dimensionTypeKey());
|
||||
if (scoped.isEmpty()) {
|
||||
return PackStageContext.retained(normalizedRoot, retainedPackName(retained.epoch(), normalizedRoot), retained.epoch());
|
||||
return PackStageContext.retained(normalizedRoot, retainedPackName(retained.epoch(), normalizedRoot), retained);
|
||||
}
|
||||
ModdedWorldgenIds.ScopedDimension identity = scoped.get();
|
||||
if (!identity.dimension().equals(contract.dimensionKey())) {
|
||||
throw new IOException("Retained Iris dimension type identity does not match its epoch contract.");
|
||||
}
|
||||
return PackStageContext.retained(normalizedRoot, identity.pack(), retained.epoch());
|
||||
return PackStageContext.retained(normalizedRoot, identity.pack(), retained);
|
||||
}
|
||||
|
||||
String packName = normalizedRoot.getFileName().toString();
|
||||
@@ -918,7 +922,8 @@ public final class ModdedForcedDatapack {
|
||||
boolean emitLegacyDimensionTypeAlias,
|
||||
boolean required,
|
||||
Set<String> legacyDimensions,
|
||||
GenerationRegistryContract retainedContract
|
||||
GenerationRegistryContract retainedContract,
|
||||
Path retainedDimensionRoot
|
||||
) implements IrisCustomBiomeAliasResolver {
|
||||
private static PackStageContext installed(
|
||||
Path packRoot,
|
||||
@@ -934,6 +939,7 @@ public final class ModdedForcedDatapack {
|
||||
true,
|
||||
false,
|
||||
Set.copyOf(legacyDimensions),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
@@ -941,8 +947,9 @@ public final class ModdedForcedDatapack {
|
||||
private static PackStageContext retained(
|
||||
Path packRoot,
|
||||
String packName,
|
||||
GenerationEpoch epoch
|
||||
RetainedPack retained
|
||||
) {
|
||||
GenerationEpoch epoch = retained.epoch();
|
||||
return new PackStageContext(
|
||||
packRoot,
|
||||
packName,
|
||||
@@ -952,7 +959,8 @@ public final class ModdedForcedDatapack {
|
||||
true,
|
||||
true,
|
||||
Set.of(),
|
||||
epoch.registryContract()
|
||||
epoch.registryContract(),
|
||||
retained.history().paths().dimensionRoot().toAbsolutePath().normalize()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+60
-28
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.world.history.GenerationEpoch;
|
||||
import art.arcane.iris.world.history.GenerationHistory;
|
||||
import art.arcane.iris.world.history.GenerationHistoryPaths;
|
||||
@@ -42,6 +43,7 @@ import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class ModdedTakeoverReconciler {
|
||||
private static final String STATE_FILE = "data/irisworldgen/dimension-takeovers.json";
|
||||
@@ -53,7 +55,7 @@ public final class ModdedTakeoverReconciler {
|
||||
public static void reconcileEarly() {
|
||||
try {
|
||||
ModdedModConfig config = ModdedModConfig.get();
|
||||
Map<String, String> configured = config.dimensionTakeovers();
|
||||
Map<String, String> configured = ModdedDimensionTakeovers.configured();
|
||||
Path worldRoot = MainWorldService.configuredWorldRootIfPresent();
|
||||
if (worldRoot == null) {
|
||||
if (!configured.isEmpty()) {
|
||||
@@ -63,17 +65,17 @@ public final class ModdedTakeoverReconciler {
|
||||
return;
|
||||
}
|
||||
Path recoveryParent = ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen");
|
||||
Result result = reconcile(worldRoot, configured, config.takeoverProblems(), recoveryParent);
|
||||
Path packsRoot = IrisPlatforms.get().packsFolderNoCreate().toPath();
|
||||
Result result = reconcile(new Request(worldRoot, configured, config.takeoverProblems(),
|
||||
(ModdedDimensionTakeover takeover) -> ModdedDimensionTakeovers.installed(takeover, packsRoot), recoveryParent));
|
||||
if (!result.adoptedLevels().isEmpty()) {
|
||||
ModdedIrisLog.info("Iris dimension takeover recorded for {}: their existing Iris generation history already uses the configured pack",
|
||||
result.adoptedLevels());
|
||||
}
|
||||
Set<String> quarantined = new TreeSet<>(result.changedLevels());
|
||||
quarantined.removeAll(result.adoptedLevels());
|
||||
if (quarantined.isEmpty()) {
|
||||
if (result.quarantinedLevels().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ModdedIrisLog.warn("Iris dimension takeover changed for {}: {}", quarantined,
|
||||
ModdedIrisLog.warn("Iris dimension takeover changed for {}: {}", result.quarantinedLevels(),
|
||||
result.recovery() == null
|
||||
? "no existing chunk data to quarantine"
|
||||
: "moved their chunk, entity, POI, and Iris Mantle data to " + result.recovery()
|
||||
@@ -89,9 +91,8 @@ public final class ModdedTakeoverReconciler {
|
||||
if (worldRoot.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> configured = ModdedModConfig.get().dimensionTakeovers();
|
||||
try {
|
||||
recordApplied(worldRoot.get(), configured,
|
||||
recordApplied(worldRoot.get(), ModdedDimensionTakeovers.configured(),
|
||||
(String levelKey, String packRef) -> generatesFrom(server, levelKey, packRef));
|
||||
} catch (IOException | RuntimeException failure) {
|
||||
ModdedIrisLog.error("Iris is stopping the server because it could not record the applied dimension takeovers under {}; an unrecorded takeover would be quarantined on the next start",
|
||||
@@ -118,42 +119,69 @@ public final class ModdedTakeoverReconciler {
|
||||
return Map.copyOf(recorded);
|
||||
}
|
||||
|
||||
static Result reconcile(Path worldRoot, Map<String, String> configured, List<String> configProblems,
|
||||
Path recoveryParent) throws IOException {
|
||||
requireReadableConfig(worldRoot, configProblems);
|
||||
static Result reconcile(Request request) throws IOException {
|
||||
Path worldRoot = request.worldRoot();
|
||||
requireReadableConfig(worldRoot, request.configProblems());
|
||||
Map<String, String> applied = readApplied(worldRoot);
|
||||
Set<String> changed = ModdedDimensionTakeovers.changedLevels(applied, configured);
|
||||
if (changed.isEmpty()) {
|
||||
return new Result(changed, Set.of(), null);
|
||||
}
|
||||
Set<String> changed = ModdedDimensionTakeovers.changedLevels(applied, request.configured());
|
||||
Map<String, String> state = new TreeMap<>(applied);
|
||||
Set<String> quarantined = new TreeSet<>();
|
||||
Set<String> adopted = new TreeSet<>();
|
||||
List<Path> sources = new ArrayList<>();
|
||||
for (String level : changed) {
|
||||
Path storage = ModdedDimensionTakeover.storageFolder(worldRoot, level);
|
||||
String packRef = configured.get(level);
|
||||
if (packRef != null && historyMatches(storage, ModdedDimensionTakeover.parse(level, packRef))) {
|
||||
state.put(level, packRef);
|
||||
adopted.add(level);
|
||||
continue;
|
||||
}
|
||||
String packRef = request.configured().get(level);
|
||||
if (packRef == null) {
|
||||
state.remove(level);
|
||||
quarantined.add(level);
|
||||
continue;
|
||||
}
|
||||
ModdedDimensionTakeover takeover = ModdedDimensionTakeover.parse(level, packRef);
|
||||
Path storage = takeover.storageFolder(worldRoot);
|
||||
if (historyMatches(storage, takeover)) {
|
||||
state.put(level, packRef);
|
||||
adopted.add(level);
|
||||
} else if (request.installed().test(takeover)) {
|
||||
quarantined.add(level);
|
||||
} else {
|
||||
refuseOrSkipUninstalled(worldRoot, applied, takeover);
|
||||
}
|
||||
}
|
||||
List<Path> sources = new ArrayList<>();
|
||||
for (String level : quarantined) {
|
||||
Path storage = ModdedDimensionTakeover.storageFolder(worldRoot, level);
|
||||
for (String folder : ModdedDimensionStorage.chunkDataFolders()) {
|
||||
sources.add(storage.resolve(folder));
|
||||
}
|
||||
}
|
||||
Path recovery = recoveryParent.resolve("takeover-recovery-" + UUID.randomUUID());
|
||||
List<Path> moved = ModdedWorldQuarantine.move(worldRoot, sources, recovery);
|
||||
Path recovery = request.recoveryParent().resolve("takeover-recovery-" + UUID.randomUUID());
|
||||
List<Path> moved = sources.isEmpty() ? List.of() : ModdedWorldQuarantine.move(worldRoot, sources, recovery);
|
||||
if (!state.equals(applied)) {
|
||||
writeApplied(worldRoot, state);
|
||||
}
|
||||
return new Result(changed, Set.copyOf(adopted), moved.isEmpty() ? null : recovery);
|
||||
return new Result(Set.copyOf(quarantined), Set.copyOf(adopted), moved.isEmpty() ? null : recovery);
|
||||
}
|
||||
|
||||
static boolean hasTakeoverRecord(Path worldRoot, String levelKey) throws IOException {
|
||||
return readApplied(worldRoot).containsKey(levelKey) || hasGenerationHistory(
|
||||
ModdedDimensionTakeover.storageFolder(worldRoot, levelKey));
|
||||
}
|
||||
|
||||
private static void refuseOrSkipUninstalled(Path worldRoot, Map<String, String> applied,
|
||||
ModdedDimensionTakeover takeover) throws IOException {
|
||||
if (applied.containsKey(takeover.levelKey()) || hasGenerationHistory(takeover.storageFolder(worldRoot))) {
|
||||
throw new IOException("Iris refused to switch the dimension takeover of " + takeover.levelKey() + " to "
|
||||
+ takeover.packRef() + ": pack '" + takeover.pack() + "' dimension '" + takeover.packDimension()
|
||||
+ "' is not installed, and the level already has an applied takeover or Iris generation history. Install the pack or restore the previous takeover.");
|
||||
}
|
||||
ModdedIrisLog.error("Iris left {} untouched: its takeover pack '{}' dimension '{}' is not installed",
|
||||
takeover.levelKey(), takeover.pack(), takeover.packDimension());
|
||||
}
|
||||
|
||||
private static boolean hasGenerationHistory(Path storage) {
|
||||
return Files.exists(GenerationHistoryPaths.forDimension(storage).generationRoot(), LinkOption.NOFOLLOW_LINKS);
|
||||
}
|
||||
|
||||
static boolean historyMatches(Path storage, ModdedDimensionTakeover takeover) throws IOException {
|
||||
if (!Files.exists(GenerationHistoryPaths.forDimension(storage).generationRoot(), LinkOption.NOFOLLOW_LINKS)) {
|
||||
if (!hasGenerationHistory(storage)) {
|
||||
return false;
|
||||
}
|
||||
GenerationEpoch epoch = GenerationHistory.inspectPacks(storage).manifest().activeEpoch();
|
||||
@@ -225,6 +253,10 @@ public final class ModdedTakeoverReconciler {
|
||||
}
|
||||
}
|
||||
|
||||
record Result(Set<String> changedLevels, Set<String> adoptedLevels, Path recovery) {
|
||||
record Request(Path worldRoot, Map<String, String> configured, List<String> configProblems,
|
||||
Predicate<ModdedDimensionTakeover> installed, Path recoveryParent) {
|
||||
}
|
||||
|
||||
record Result(Set<String> quarantinedLevels, Set<String> adoptedLevels, Path recovery) {
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -27,6 +27,7 @@ import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldGenerators;
|
||||
import art.arcane.iris.modded.MainWorldService;
|
||||
import art.arcane.iris.modded.ModdedDimensionTakeover;
|
||||
import art.arcane.iris.modded.ModdedDimensionTakeovers;
|
||||
import art.arcane.iris.modded.ModdedForcedDatapack;
|
||||
import art.arcane.iris.modded.ModdedDimensionManager;
|
||||
import art.arcane.iris.modded.ModdedEngineBootstrap;
|
||||
@@ -46,6 +47,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -320,12 +322,20 @@ public final class ModdedWorldCommands {
|
||||
if (blockIfPackBroken(source, takeover.levelKey(), pack) || !loadPackDimension(source, pack, takeover.packDimension())) {
|
||||
return 0;
|
||||
}
|
||||
ModdedModConfig.setDimensionTakeover(takeover);
|
||||
ModdedForcedDatapack.scheduleRegeneration("dimension takeover of " + takeover.levelKey());
|
||||
ModdedDimensionTakeover canonical;
|
||||
try {
|
||||
canonical = ModdedDimensionTakeovers.canonical(takeover, ModdedPackCommands.packsRoot().toPath());
|
||||
} catch (IOException unreadable) {
|
||||
ModdedIrisLog.error("Iris could not resolve the takeover pack {} for {}", takeover.packRef(), takeover.levelKey(), unreadable);
|
||||
IrisModdedCommands.fail(source, IrisLanguage.errorDetail(unreadable));
|
||||
return 0;
|
||||
}
|
||||
ModdedModConfig.setDimensionTakeover(canonical);
|
||||
ModdedForcedDatapack.scheduleRegeneration("dimension takeover of " + canonical.levelKey());
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_TAKEOVER_SET,
|
||||
MessageArgument.untrusted("dimensionId", takeover.levelKey()),
|
||||
MessageArgument.untrusted("pack", pack),
|
||||
MessageArgument.untrusted("packDimension", takeover.packDimension())));
|
||||
MessageArgument.untrusted("dimensionId", canonical.levelKey()),
|
||||
MessageArgument.untrusted("pack", canonical.pack()),
|
||||
MessageArgument.untrusted("packDimension", canonical.packDimension())));
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_TAKEOVER_RESTART_REQUIRED));
|
||||
return 1;
|
||||
}
|
||||
|
||||
+113
@@ -30,6 +30,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -130,4 +131,116 @@ public class ModdedDimensionTakeoversTest {
|
||||
assertEquals("", ModdedDimensionTakeovers.fingerprint(Map.of()));
|
||||
assertTrue(ModdedDimensionTakeovers.fingerprint(forward).contains("iristest:b=overworld:sky"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installedPackTypeIsStagedForTheTakeover() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-stage-installed");
|
||||
try {
|
||||
Path datapack = temporary.resolve("datapack");
|
||||
ModdedDimensionTakeovers.StagedTypes staged = ModdedDimensionTakeovers.StagedTypes.create();
|
||||
staged.installed().put("skypack:sky", "irisworldgen:packs/x/dimensions/y/dimension_type");
|
||||
|
||||
ModdedDimensionTakeovers.writeLevelStems(datapack, Map.of("aether:the_aether", "skypack:sky"), staged,
|
||||
temporary.resolve("world"));
|
||||
|
||||
JSONObject stem = new JSONObject(Files.readString(datapack.resolve("data/aether/dimension/the_aether.json")));
|
||||
assertEquals("irisworldgen:packs/x/dimensions/y/dimension_type", stem.getString("type"));
|
||||
assertEquals("skypack:sky", stem.getJSONObject("generator").getString("dimension"));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retainedHistoryOfTheLevelStagesTheTakeoverWithoutTheInstalledPack() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-stage-retained");
|
||||
try {
|
||||
Path datapack = temporary.resolve("datapack");
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
ModdedDimensionTakeovers.StagedTypes staged = ModdedDimensionTakeovers.StagedTypes.create();
|
||||
staged.retained().put(worldRoot.resolve("dimensions/aether/the_aether").toAbsolutePath().normalize(),
|
||||
"aether:the_aether");
|
||||
|
||||
ModdedDimensionTakeovers.writeLevelStems(datapack, Map.of("aether:the_aether", "skypack:sky"), staged, worldRoot);
|
||||
|
||||
JSONObject stem = new JSONObject(Files.readString(datapack.resolve("data/aether/dimension/the_aether.json")));
|
||||
assertEquals("aether:the_aether", stem.getString("type"));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unstageableTakeoverWithoutAnyRecordIsSkipped() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-stage-skip");
|
||||
try {
|
||||
Path datapack = temporary.resolve("datapack");
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
Files.createDirectories(worldRoot);
|
||||
|
||||
ModdedDimensionTakeovers.writeLevelStems(datapack, Map.of("aether:the_aether", "skypack:sky"),
|
||||
ModdedDimensionTakeovers.StagedTypes.create(), worldRoot);
|
||||
ModdedDimensionTakeovers.writeLevelStems(datapack, Map.of("aether:the_aether", "skypack:sky"),
|
||||
ModdedDimensionTakeovers.StagedTypes.create(), null);
|
||||
|
||||
assertFalse(Files.exists(datapack.resolve("data/aether/dimension/the_aether.json")));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unstageableTakeoverOfAnAppliedLevelRefusesStaging() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-stage-applied");
|
||||
try {
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
ModdedTakeoverReconciler.writeApplied(worldRoot, Map.of("aether:the_aether", "skypack:sky"));
|
||||
|
||||
IOException refused = assertThrows(IOException.class, () -> ModdedDimensionTakeovers.writeLevelStems(
|
||||
temporary.resolve("datapack"), Map.of("aether:the_aether", "skypack:sky"),
|
||||
ModdedDimensionTakeovers.StagedTypes.create(), worldRoot));
|
||||
|
||||
assertTrue(refused.getMessage(), refused.getMessage().contains("aether:the_aether"));
|
||||
assertTrue(refused.getMessage(), refused.getMessage().contains("skypack"));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unstageableTakeoverOfALevelWithIrisHistoryRefusesStaging() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-stage-history");
|
||||
try {
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
Files.createDirectories(worldRoot.resolve("dimensions/aether/the_aether/iris/generation"));
|
||||
|
||||
assertThrows(IOException.class, () -> ModdedDimensionTakeovers.writeLevelStems(
|
||||
temporary.resolve("datapack"), Map.of("aether:the_aether", "skypack:sky"),
|
||||
ModdedDimensionTakeovers.StagedTypes.create(), worldRoot));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canonicalTakeoverFollowsTheInstalledPackFolderCase() throws IOException {
|
||||
Path packsRoot = Files.createTempDirectory("iris-takeover-canonical");
|
||||
try {
|
||||
Files.createDirectories(packsRoot.resolve("skypack/dimensions"));
|
||||
Files.writeString(packsRoot.resolve("skypack/dimensions/sky.json"), "{}");
|
||||
boolean caseInsensitive = Files.isDirectory(packsRoot.resolve("SKYPACK"));
|
||||
|
||||
Map<String, String> canonical = ModdedDimensionTakeovers.canonical(
|
||||
Map.of("aether:the_aether", "SkyPack:Sky", "aether:other", "missing:sky"), packsRoot);
|
||||
|
||||
assertEquals(caseInsensitive ? "skypack:sky" : "SkyPack:Sky", canonical.get("aether:the_aether"));
|
||||
assertEquals("missing:sky", canonical.get("aether:other"));
|
||||
assertEquals("skypack:sky", ModdedDimensionTakeovers.canonical(
|
||||
Map.of("aether:the_aether", "skypack:sky"), packsRoot).get("aether:the_aether"));
|
||||
assertTrue(ModdedDimensionTakeovers.installed(ModdedDimensionTakeover.parse("aether:the_aether", "skypack:sky"), packsRoot));
|
||||
assertFalse(ModdedDimensionTakeovers.installed(ModdedDimensionTakeover.parse("aether:the_aether", "skypack:void"), packsRoot));
|
||||
} finally {
|
||||
ModdedTakeoverReconcilerTest.deleteTree(packsRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -111,10 +111,10 @@ public class ModdedForcedDatapackTest {
|
||||
Map.of(key, saved));
|
||||
Class<?> stageType = Class.forName(ModdedForcedDatapack.class.getName() + "$PackStageContext");
|
||||
Constructor<?> constructor = stageType.getDeclaredConstructor(Path.class, String.class, String.class,
|
||||
String.class, boolean.class, boolean.class, boolean.class, Set.class, GenerationRegistryContract.class);
|
||||
String.class, boolean.class, boolean.class, boolean.class, Set.class, GenerationRegistryContract.class, Path.class);
|
||||
constructor.setAccessible(true);
|
||||
IrisCustomBiomeAliasResolver resolver = (IrisCustomBiomeAliasResolver) constructor.newInstance(
|
||||
Path.of("retained"), "test", "test", "iris:test_type", false, false, true, Set.of(), contract);
|
||||
Path.of("retained"), "test", "test", "iris:test_type", false, false, true, Set.of(), contract, Path.of("dimension"));
|
||||
assertEquals(source, resolver.generatedSource(key.registryKey(), key.resourceKey(), "{}", fixer));
|
||||
assertThrows(IOException.class, () -> resolver.generatedSource(
|
||||
key.registryKey(), "iris:missing_type", "{}", fixer));
|
||||
|
||||
+65
-18
@@ -67,10 +67,10 @@ public class ModdedTakeoverReconcilerTest {
|
||||
touch(worldRoot.resolve("dimensions/minecraft/overworld/region/r.0.0.mca"));
|
||||
touch(worldRoot.resolve("players/data/player.dat"));
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of("iristest:skylands", "overworld"), List.of(), recoveryParent);
|
||||
|
||||
assertEquals(Set.of("iristest:skylands"), result.changedLevels());
|
||||
assertEquals(Set.of("iristest:skylands"), result.quarantinedLevels());
|
||||
for (String folder : List.of("region", "entities", "poi", "mantle-hydrology", "iris")) {
|
||||
assertFalse(folder, Files.exists(skylands.resolve(folder)));
|
||||
assertTrue(folder, Files.isRegularFile(result.recovery()
|
||||
@@ -95,17 +95,17 @@ public class ModdedTakeoverReconcilerTest {
|
||||
ModdedTakeoverReconciler.writeApplied(worldRoot, Map.of("iristest:skylands", "overworld"));
|
||||
touch(region);
|
||||
|
||||
ModdedTakeoverReconciler.Result unchanged = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result unchanged = reconcile(
|
||||
worldRoot, Map.of("iristest:skylands", "overworld"), List.of(), recoveryParent);
|
||||
|
||||
assertEquals(Set.of(), unchanged.changedLevels());
|
||||
assertEquals(Set.of(), unchanged.quarantinedLevels());
|
||||
assertNull(unchanged.recovery());
|
||||
assertTrue(Files.isRegularFile(region));
|
||||
|
||||
ModdedTakeoverReconciler.Result removed = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result removed = reconcile(
|
||||
worldRoot, Map.of(), List.of(), recoveryParent);
|
||||
|
||||
assertEquals(Set.of("iristest:skylands"), removed.changedLevels());
|
||||
assertEquals(Set.of("iristest:skylands"), removed.quarantinedLevels());
|
||||
assertFalse(Files.exists(region));
|
||||
assertTrue(Files.isRegularFile(removed.recovery().resolve("dimensions/iristest/skylands/region/r.0.0.mca")));
|
||||
assertEquals(Map.of(), ModdedTakeoverReconciler.readApplied(worldRoot));
|
||||
@@ -122,10 +122,10 @@ public class ModdedTakeoverReconcilerTest {
|
||||
try {
|
||||
Files.createDirectories(worldRoot);
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of("aether:the_aether", "overworld"), List.of(), recoveryParent);
|
||||
|
||||
assertEquals(Set.of("aether:the_aether"), result.changedLevels());
|
||||
assertEquals(Set.of("aether:the_aether"), result.quarantinedLevels());
|
||||
assertNull(result.recovery());
|
||||
assertFalse(Files.exists(recoveryParent));
|
||||
assertEquals(Map.of(), ModdedTakeoverReconciler.readApplied(worldRoot));
|
||||
@@ -145,7 +145,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
touch(region);
|
||||
List<String> problems = List.of("Iris could not read /srv/config/irisworldgen/modded.json: Unterminated object");
|
||||
|
||||
IOException refused = assertThrows(IOException.class, () -> ModdedTakeoverReconciler.reconcile(
|
||||
IOException refused = assertThrows(IOException.class, () -> reconcile(
|
||||
worldRoot, Map.of(), problems, recoveryParent));
|
||||
|
||||
assertTrue(refused.getMessage(), refused.getMessage().contains("modded.json"));
|
||||
@@ -164,7 +164,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
try {
|
||||
ModdedTakeoverReconciler.writeApplied(worldRoot, Map.of());
|
||||
|
||||
assertThrows(IOException.class, () -> ModdedTakeoverReconciler.reconcile(
|
||||
assertThrows(IOException.class, () -> reconcile(
|
||||
worldRoot, Map.of(), List.of("invalid takeover entry"), temporary.resolve("config")));
|
||||
} finally {
|
||||
deleteTree(temporary);
|
||||
@@ -178,10 +178,10 @@ public class ModdedTakeoverReconcilerTest {
|
||||
try {
|
||||
Files.createDirectories(worldRoot);
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of(), List.of("invalid takeover entry"), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of(), result.changedLevels());
|
||||
assertEquals(Set.of(), result.quarantinedLevels());
|
||||
} finally {
|
||||
deleteTree(temporary);
|
||||
}
|
||||
@@ -196,10 +196,10 @@ public class ModdedTakeoverReconcilerTest {
|
||||
ModdedTakeoverReconciler.writeApplied(worldRoot, Map.of("iristest:skylands", "overworld"));
|
||||
touch(region);
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of("iristest:skylands", "skypack"), List.of(), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of("iristest:skylands"), result.changedLevels());
|
||||
assertEquals(Set.of("iristest:skylands"), result.quarantinedLevels());
|
||||
assertFalse(Files.exists(region));
|
||||
assertEquals(Map.of("iristest:skylands", "overworld"), ModdedTakeoverReconciler.readApplied(worldRoot));
|
||||
} finally {
|
||||
@@ -216,7 +216,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
createHistory(temporary, skylands, ModdedWorldgenIds.dimensionTypeRef("skypack", "sky"), "sky", List.of());
|
||||
touch(skylands.resolve("region/r.0.0.mca"));
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of("iristest:skylands", "skypack:sky"), List.of(), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of("iristest:skylands"), result.adoptedLevels());
|
||||
@@ -237,7 +237,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
createHistory(temporary, skylands, ModdedWorldgenIds.dimensionTypeRef("otherpack", "sky"), "sky", List.of());
|
||||
touch(skylands.resolve("region/r.0.0.mca"));
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result result = reconcile(
|
||||
worldRoot, Map.of("iristest:skylands", "skypack:sky"), List.of(), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of(), result.adoptedLevels());
|
||||
@@ -259,7 +259,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
List.of(ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow")));
|
||||
touch(aether.resolve("region/r.0.0.mca"));
|
||||
|
||||
ModdedTakeoverReconciler.Result other = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result other = reconcile(
|
||||
worldRoot, Map.of("aether:the_aether", "otherpack:sky"), List.of(), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of(), other.adoptedLevels());
|
||||
@@ -267,7 +267,7 @@ public class ModdedTakeoverReconcilerTest {
|
||||
|
||||
createHistory(temporary, aether, "aether:the_aether", "sky",
|
||||
List.of(ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow")));
|
||||
ModdedTakeoverReconciler.Result matching = ModdedTakeoverReconciler.reconcile(
|
||||
ModdedTakeoverReconciler.Result matching = reconcile(
|
||||
worldRoot, Map.of("aether:the_aether", "skypack:sky"), List.of(), temporary.resolve("config"));
|
||||
|
||||
assertEquals(Set.of("aether:the_aether"), matching.adoptedLevels());
|
||||
@@ -310,6 +310,53 @@ public class ModdedTakeoverReconcilerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchingAnAppliedLevelToAnUninstalledPackRefusesBeforeMovingAnything() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-uninstalled-applied");
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
try {
|
||||
Path region = worldRoot.resolve("dimensions/iristest/skylands/region/r.0.0.mca");
|
||||
ModdedTakeoverReconciler.writeApplied(worldRoot, Map.of("iristest:skylands", "overworld"));
|
||||
touch(region);
|
||||
|
||||
IOException refused = assertThrows(IOException.class, () -> ModdedTakeoverReconciler.reconcile(
|
||||
new ModdedTakeoverReconciler.Request(worldRoot, Map.of("iristest:skylands", "missing"), List.of(),
|
||||
(ModdedDimensionTakeover takeover) -> false, temporary.resolve("config"))));
|
||||
|
||||
assertTrue(refused.getMessage(), refused.getMessage().contains("missing"));
|
||||
assertTrue(Files.isRegularFile(region));
|
||||
assertEquals(Map.of("iristest:skylands", "overworld"), ModdedTakeoverReconciler.readApplied(worldRoot));
|
||||
} finally {
|
||||
deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newTakeoverOfAnUninstalledPackLeavesTheLevelAlone() throws IOException {
|
||||
Path temporary = Files.createTempDirectory("iris-takeover-uninstalled-new");
|
||||
Path worldRoot = temporary.resolve("world");
|
||||
try {
|
||||
Path region = worldRoot.resolve("dimensions/aether/the_aether/region/r.0.0.mca");
|
||||
touch(region);
|
||||
|
||||
ModdedTakeoverReconciler.Result result = ModdedTakeoverReconciler.reconcile(
|
||||
new ModdedTakeoverReconciler.Request(worldRoot, Map.of("aether:the_aether", "missing"), List.of(),
|
||||
(ModdedDimensionTakeover takeover) -> false, temporary.resolve("config")));
|
||||
|
||||
assertEquals(Set.of(), result.quarantinedLevels());
|
||||
assertTrue(Files.isRegularFile(region));
|
||||
assertEquals(Map.of(), ModdedTakeoverReconciler.readApplied(worldRoot));
|
||||
} finally {
|
||||
deleteTree(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModdedTakeoverReconciler.Result reconcile(Path worldRoot, Map<String, String> configured,
|
||||
List<String> problems, Path recoveryParent) throws IOException {
|
||||
return ModdedTakeoverReconciler.reconcile(new ModdedTakeoverReconciler.Request(
|
||||
worldRoot, configured, problems, (ModdedDimensionTakeover takeover) -> true, recoveryParent));
|
||||
}
|
||||
|
||||
private static void createHistory(Path temporary, Path dimensionRoot, String dimensionTypeKey, String dimensionKey,
|
||||
List<String> biomeKeys) throws IOException {
|
||||
Path source = Files.createTempDirectory(temporary, "pack");
|
||||
|
||||
Reference in new issue
Block a user