diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bc3504a..51eaa8fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,9 +126,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/OreSpawn-4.1.0.112021.jar - build/libs/OreSpawn-4.1.0.112021-sources.jar - build/libs/OreSpawn-4.1.0.112021-javadoc.jar + build/libs/OreSpawn-4.1.1.112021.jar + build/libs/OreSpawn-4.1.1.112021-sources.jar + build/libs/OreSpawn-4.1.1.112021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 01f4590d..6b2a0255 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +Version 4.1.1.112021 + +* Keep existing provider settings when importing older ore files for the first + time, instead of replacing them with the older settings. +* Describe preserved settings as retained rather than verified in the upgrade + report. Invalid files are still checked by the normal provider loader. + Version 4.1.0.112021 * Clarify dimension-wide material controls and replace light-equivalent custom diff --git a/README.md b/README.md index 280a28f9..fca2795c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It gives mods and modpacks one place to configure ores, deposit shapes, optional rock strata and geomes, provider-owned underground fluid deposits, biome palettes and world materials, flat bedrock, and bounded ore retrogen. -This branch builds target-qualified version `4.1.0.112021`: the OreSpawn 4.1.0 +This branch builds target-qualified version `4.1.1.112021`: the OreSpawn 4.1.1 feature set for Minecraft 1.12.2 and Forge. It adds stable generation identity, non-loading geology sampling, per-resource background control, existing-world provider merge policy, an Ore Sources arbiter for equivalent provider ores, a diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index e32f78b6..cd81eab4 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -81,6 +81,12 @@ legacy-ID behaviour. Migration is non-destructive. OreSpawn writes `config/orespawn-worldgen.json` only when that target does not already exist and retains every source file. +An existing `config/-orespawn.json` override is also kept unchanged, +even when legacy files are imported for the first time. The upgrade report +records that the existing provider settings were retained, not validated. +An unfinished or invalid override is left for the normal provider validator; +migration does not replace it with defaults or older settings. + When `config/mineralogy-geomes.json` exists, OreSpawn imports the Mineralogy 6 profile directly, updates its schema marker, and records `migrated_from`. diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 053016f9..a94f573a 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -52,7 +52,7 @@ Examples: | Minecraft | Loader | Target | Example full OreSpawn version | | --- | --- | ---: | --- | | 1.10.2 | Forge | `110021` | `4.0.6.110021` | -| 1.12.2 | Forge | `112021` | `4.1.0.112021` | +| 1.12.2 | Forge | `112021` | `4.1.1.112021` | | 1.13.2 | Forge | `113021` | `4.0.6.113021` | | 1.20.6 | Forge | `120061` | `4.0.6.120061` | | 1.21.11 | Forge | `121111` | `4.0.6.121111` | diff --git a/gradle.properties b/gradle.properties index 25e182a3..5aab1e5a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,7 +30,7 @@ mod_vendor=Minecraft Mod Development mod_license=LGPL-2.1 mod_license_name=GNU Lesser General Public License, Version 2.1 mod_license_url=https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt -mod_version=4.1.0.112021 +mod_version=4.1.1.112021 mod_group=zone.moddev.mc.orespawn mod_authors=SkyBlade1978, dshadowwolf, the MMD Team mod_description=Configurable, provider-driven terrain, ore, and deposit generation. diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index c7a4c30a..c4e6949b 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -81,7 +81,7 @@ tasks.register('verifyReleaseConfiguration') { description = 'Validates the target-qualified release, API, schemas, reports, and publishing identity.' doLast { - if (project.mod_version != '4.1.0.112021' + if (project.mod_version != '4.1.1.112021' || project.mod_group != expectedMavenGroup) { throw new GradleException("Unexpected OreSpawn release version: ${project.mod_version}") } @@ -108,8 +108,8 @@ tasks.register('verifyReleaseConfiguration') { 'src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java', 'README.md', 'CHANGELOG.txt' ].each { path -> - if (!file(path).getText('UTF-8').contains('4.1.0.112021')) { - throw new GradleException("Authoritative release location does not contain 4.1.0.112021: ${path}") + if (!file(path).getText('UTF-8').contains('4.1.1.112021')) { + throw new GradleException("Authoritative release location does not contain 4.1.1.112021: ${path}") } } if (!file('docs/API.md').getText('UTF-8').contains('orespawn@[4.0.6,5.0.0)')) { @@ -173,7 +173,7 @@ tasks.register('verifyReleaseArtifacts') { } ['src/test/', 'src/biomeIntegrationTest/', 'src/migrationIntegrationTest/', 'src/clientIntegrationTest/', 'src/os1AbiFixture/', 'src/os3AbiFixture/', - 'agent-notes/', 'surfaceprobe', 'migrationprobe', 'clientprobe', + 'agent-notes/', 'AGENTS.md', 'AGENT.md', 'surfaceprobe', 'migrationprobe', 'clientprobe', 'codec-conflict/', 'LegacyApiConsumer', 'OreSourceEditorProbeBridge', 'org/junit/', 'org/mockito/', 'net/bytebuddy/'].each { forbidden -> if (candidateZip.entries().any { it.name.contains(forbidden) }) { diff --git a/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java b/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java index 09f5edaa..43e68f56 100644 --- a/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java +++ b/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java @@ -335,7 +335,7 @@ private static void migrateConfigDirectory(Path configDirectory) { } for (Map.Entry provider : providers.entrySet()) { try { - writeAtomicIfChanged(configDirectory.resolve(provider.getKey() + "-orespawn.json"), provider.getValue()); + writeProviderIfAbsent(configDirectory.resolve(provider.getKey() + "-orespawn.json"), provider.getValue()); } catch (IOException failure) { REPORT.add("provider_write_failed=" + provider.getKey() + ":" + failure.getClass().getSimpleName()); LOGGER.error("Could not write migrated OS3 provider {}", provider.getKey(), failure); @@ -792,6 +792,16 @@ private static int metadata(String block, String state) { return 0; } + static void writeProviderIfAbsent(Path destination, JsonObject value) throws IOException { + // An existing OS4 file is a pack's choice, even on its first legacy import. + if (Files.isRegularFile(destination)) { + REPORT.add("provider_preserved=" + destination.getFileName()); + return; + } + + writeAtomicIfChanged(destination, value); + } + private static void writeAtomicIfChanged(Path destination, JsonObject value) throws IOException { byte[] data = (GSON.toJson(value) + System.lineSeparator()).getBytes(StandardCharsets.UTF_8); if (Files.isRegularFile(destination) && Arrays.equals(Files.readAllBytes(destination), data)) { @@ -833,7 +843,8 @@ private static void writeHumanUpgradeReport(Path destination) throws IOException String lower = row.toLowerCase(java.util.Locale.ROOT); if (lower.startsWith("config_source=") || lower.startsWith("resource_loaded=") || lower.startsWith("config_registered=")) sources.add(row); - if ((lower.startsWith("provider_written=") || lower.startsWith("provider_unchanged=")) + if ((lower.startsWith("provider_written=") || lower.startsWith("provider_unchanged=") + || lower.startsWith("provider_preserved=")) && !lower.contains("migration-report")) providers.add(row); if (lower.contains("_failed=") || lower.contains("_rejected=") || lower.contains("_ignored=") || lower.contains("_unresolved=") @@ -842,12 +853,12 @@ private static void writeHumanUpgradeReport(Path destination) throws IOException } } List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.112021 Upgrade Report"); + lines.add("OreSpawn 4.1.1.112021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Legacy OreSpawn configuration was consumed and translated for OS4."); lines.add("- Legacy sources read: " + sources.size()); - lines.add("- OS4 provider files written or verified: " + providers.size()); + lines.add("- OS4 provider files written or retained: " + providers.size()); lines.add("- Unique items requiring review: " + warnings.size()); lines.add(""); lines.add(warnings.isEmpty() diff --git a/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java b/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java index eee1108e..023e01b4 100644 --- a/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java +++ b/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java @@ -52,7 +52,7 @@ public class OreSpawn { public static final String MODID = "orespawn"; public static final String NAME = "OreSpawn"; - public static final String VERSION = "4.1.0.112021"; + public static final String VERSION = "4.1.1.112021"; private static final Logger LOGGER = LogManager.getLogger(); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java index eb227465..368a2c3a 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java @@ -152,7 +152,7 @@ private static void writeUpgradeReport(Path worldRoot, Path configDirectory, Path report = worldRoot.resolve("serverconfig/orespawn-upgrade-report.txt"); List missing = missingBlocks(igneous, metamorphic, sedimentary); List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.112021 Upgrade Report"); + lines.add("OreSpawn 4.1.1.112021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Existing Mineralogy " + identity.version + " world detected."); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java index 6a68d6ec..ff4d7588 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java @@ -65,7 +65,7 @@ private static void writeInitialUpgradeReport(Path configDirectory, boolean forceRetrogen, boolean flatBedrock, boolean retrogenBedrock, int bedrockLayers) throws IOException { String newline = System.lineSeparator(); - String text = "OreSpawn 4.1.0.112021 Upgrade Report" + newline + String text = "OreSpawn 4.1.1.112021 Upgrade Report" + newline + "================================" + newline + newline + "RESULT: Legacy OreSpawn settings were imported into the OS4 profile." + newline + "- Manage vanilla ores: " + manageVanilla + newline diff --git a/src/test/java/com/mcmoddev/orespawn/compat/LegacyOs3BridgeTest.java b/src/test/java/com/mcmoddev/orespawn/compat/LegacyOs3BridgeTest.java index a20315ed..09abc86e 100644 --- a/src/test/java/com/mcmoddev/orespawn/compat/LegacyOs3BridgeTest.java +++ b/src/test/java/com/mcmoddev/orespawn/compat/LegacyOs3BridgeTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -43,6 +44,99 @@ static void bootstrapMinecraft() { Forge14TestBootstrap.registerVanilla(); } + @Test + void keepsAnExistingProviderOverrideDuringLegacyImport() throws Exception { + Path destination = temporary.resolve("basemetals-orespawn.json"); + String existing = "{ \"schema_version\": 5, \"provider_modid\": \"basemetals\", \"provider_revision\": 1 }\n"; + Files.write(destination, existing.getBytes(StandardCharsets.UTF_8)); + + LegacyOs3Bridge.writeProviderIfAbsent(destination, new JsonObject()); + + assertEquals(existing, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + assertFalse(Files.exists(temporary.resolve("basemetals-orespawn.json.os3-backup"))); + assertTrue(LegacyOs3Bridge.reportForTests().contains("provider_preserved=basemetals-orespawn.json")); + } + + @Test + void leavesAMalformedOverrideForTheNormalProviderValidator() throws Exception { + Path destination = temporary.resolve("basemetals-orespawn.json"); + String existing = "{ unfinished user edit"; + Files.write(destination, existing.getBytes(StandardCharsets.UTF_8)); + + LegacyOs3Bridge.writeProviderIfAbsent(destination, new JsonObject()); + + assertEquals(existing, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + } + + @Test + void writesAnImportedProviderOnlyWhenNoOverrideExists() throws Exception { + Path destination = temporary.resolve("basemetals-orespawn.json"); + JsonObject imported = new JsonObject(); + imported.addProperty("provider_modid", "basemetals"); + imported.addProperty("provider_revision", 1); + + LegacyOs3Bridge.writeProviderIfAbsent(destination, imported); + String first = new String(Files.readAllBytes(destination), StandardCharsets.UTF_8); + assertEquals(imported, new JsonParser().parse(first)); + + imported.addProperty("provider_revision", 9); + LegacyOs3Bridge.writeProviderIfAbsent(destination, imported); + assertEquals(first, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + } + + @Test + void keepsAnExistingProviderDuringTheFirstImportAndReload() throws Exception { + LegacyOs3Bridge.resetProgrammaticForTests("overridefixture"); + Path legacy = temporary.resolve("orespawn3"); + Files.createDirectories(legacy); + Files.write(legacy.resolve("overridefixture.json"), + "{\"spawns\":{}}".getBytes(StandardCharsets.UTF_8)); + Path destination = temporary.resolve("orespawn-orespawn.json"); + String existing = "{ \"schema_version\": 5, \"provider_modid\": \"orespawn\", \"provider_revision\": 9 }\n"; + Files.write(destination, existing.getBytes(StandardCharsets.UTF_8)); + + migrateLegacyDirectory(temporary); + + assertEquals(existing, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + assertFalse(Files.exists(temporary.resolve("orespawn-orespawn.json.os3-backup"))); + Path report = temporary.resolve("orespawn-upgrade-report.txt"); + String firstReport = new String(Files.readAllBytes(report), StandardCharsets.UTF_8); + assertTrue(firstReport.contains("provider_preserved=orespawn-orespawn.json")); + + migrateLegacyDirectory(temporary); + + assertEquals(existing, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + assertEquals(firstReport, new String(Files.readAllBytes(report), StandardCharsets.UTF_8)); + } + + @Test + void describesAnUnvalidatedOverrideAsRetainedRatherThanVerified() throws Exception { + LegacyOs3Bridge.resetProgrammaticForTests("reportfixture"); + Path legacy = temporary.resolve("orespawn3"); + Files.createDirectories(legacy); + Files.write(legacy.resolve("reportfixture.json"), + "{\"spawns\":{}}".getBytes(StandardCharsets.UTF_8)); + Path destination = temporary.resolve("orespawn-orespawn.json"); + String existing = "{ unfinished user edit"; + Files.write(destination, existing.getBytes(StandardCharsets.UTF_8)); + + migrateLegacyDirectory(temporary); + + assertEquals(existing, new String(Files.readAllBytes(destination), StandardCharsets.UTF_8)); + String report = new String(Files.readAllBytes( + temporary.resolve("orespawn-upgrade-report.txt")), StandardCharsets.UTF_8); + assertTrue(report.contains("OS4 provider files written or retained: 1")); + assertFalse(report.contains("written or verified")); + assertTrue(report.contains("provider_preserved=orespawn-orespawn.json")); + } + + private static void migrateLegacyDirectory(Path directory) throws Exception { + Method migration = LegacyOs3Bridge.class.getDeclaredMethod("migrateConfigDirectory", Path.class); + migration.setAccessible(true); + migration.invoke(null, directory); + } + + @Test void translatesAllOs3PatternsSelectorsWeightsMetadataAndFlags() throws Exception { Path legacy = temporary.resolve("orespawn3"); diff --git a/src/test/java/zone/moddev/mc/orespawn/client/OreSpawnModDirectoryModelTest.java b/src/test/java/zone/moddev/mc/orespawn/client/OreSpawnModDirectoryModelTest.java index d3db4454..da7a64e9 100644 --- a/src/test/java/zone/moddev/mc/orespawn/client/OreSpawnModDirectoryModelTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/client/OreSpawnModDirectoryModelTest.java @@ -30,7 +30,7 @@ void discoversLoadedNativeLegacyUiOnlyInactiveAndRejectedIntegrations() { "directory_ui_only", parent -> new GuiScreen() { }); List loaded = Arrays.asList( - new LoadedMod("orespawn", "MMD OreSpawn", "4.1.0.112021"), + new LoadedMod("orespawn", "MMD OreSpawn", "4.1.1.112021"), new LoadedMod("directory_realistic", "Realistic Deposits", "0.1.0.110021"), new LoadedMod("directory_base", "Base Metals", "2.5.0-beta5"), new LoadedMod("directory_mineralogy", "Mineralogy", "6.0.2.110021"), diff --git a/src/test/java/zone/moddev/mc/orespawn/documentation/DocumentationExporterTest.java b/src/test/java/zone/moddev/mc/orespawn/documentation/DocumentationExporterTest.java index b57a3a7b..659de75f 100644 --- a/src/test/java/zone/moddev/mc/orespawn/documentation/DocumentationExporterTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/documentation/DocumentationExporterTest.java @@ -1,6 +1,7 @@ package zone.moddev.mc.orespawn.documentation; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.charset.StandardCharsets; @@ -38,6 +39,7 @@ void exportsCompleteGuideAndDoesNotOverwriteExistingFiles() throws Exception { "runtime export differs for " + relative); } assertTrue(Files.isRegularFile(temporaryDirectory.resolve("README.md"))); + assertFalse(Files.exists(temporaryDirectory.resolve("AGENTS.md"))); assertTrue(Files.isRegularFile(temporaryDirectory.resolve("DEVELOPER_GUIDE.md"))); assertTrue(Files.isRegularFile(temporaryDirectory.resolve("BIOMES.md"))); assertTrue(Files.isRegularFile(temporaryDirectory.resolve("examples/examplemod-orespawn.json")));