mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-18 06:24:47 +00:00
Merged forced datapack tags
- Staged packs that write the same data/*/tags file now merge into the union of their values - Tag entries dedupe by value or id and replace stays false unless a contributor set it - Every non-tag file keeps strict conflict detection
This commit is contained in:
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
final class ModdedDatapackTagMerge {
|
||||
private static final String REPLACE = "replace";
|
||||
private static final String VALUES = "values";
|
||||
|
||||
private ModdedDatapackTagMerge() {
|
||||
}
|
||||
|
||||
static boolean isTag(Path relative) {
|
||||
return relative.getNameCount() >= 4
|
||||
&& "data".equals(relative.getName(0).toString())
|
||||
&& "tags".equals(relative.getName(2).toString())
|
||||
&& relative.getFileName().toString().endsWith(".json");
|
||||
}
|
||||
|
||||
static void merge(Path source, Path destination) throws IOException {
|
||||
JSONObject incoming = read(source);
|
||||
JSONObject existing = read(destination);
|
||||
Map<String, Object> values = new TreeMap<>();
|
||||
collect(existing, values, destination);
|
||||
collect(incoming, values, source);
|
||||
JSONArray merged = new JSONArray();
|
||||
for (Object value : values.values()) {
|
||||
merged.put(value);
|
||||
}
|
||||
JSONObject tag = new JSONObject();
|
||||
tag.put(REPLACE, existing.optBoolean(REPLACE, false) || incoming.optBoolean(REPLACE, false));
|
||||
tag.put(VALUES, merged);
|
||||
Files.writeString(destination, tag.toString(4), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static JSONObject read(Path tag) throws IOException {
|
||||
try {
|
||||
return new JSONObject(Files.readString(tag, StandardCharsets.UTF_8));
|
||||
} catch (RuntimeException invalid) {
|
||||
throw new IOException("Iris cannot merge the invalid datapack tag " + tag + ".", invalid);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collect(JSONObject tag, Map<String, Object> values, Path origin) throws IOException {
|
||||
JSONArray entries = tag.optJSONArray(VALUES);
|
||||
if (entries == null) {
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < entries.length(); index++) {
|
||||
Object entry = entries.get(index);
|
||||
String key = entry instanceof JSONObject object ? object.optString("id", "") : entry.toString();
|
||||
if (key.isBlank()) {
|
||||
throw new IOException("Iris cannot merge datapack tag " + origin + ": entry " + index + " has no id.");
|
||||
}
|
||||
values.putIfAbsent(key, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,6 +464,8 @@ public final class ModdedForcedDatapack {
|
||||
Files.createDirectories(destination);
|
||||
} else if (!Files.exists(destination)) {
|
||||
Files.copy(source, destination);
|
||||
} else if (Files.isRegularFile(destination) && ModdedDatapackTagMerge.isTag(relative)) {
|
||||
ModdedDatapackTagMerge.merge(source, destination);
|
||||
} else if (!Files.isRegularFile(destination) || Files.mismatch(source, destination) != -1L) {
|
||||
throw new IOException("Conflicting Iris datapack output at " + destination + ".");
|
||||
}
|
||||
|
||||
+55
@@ -260,6 +260,61 @@ public class ModdedForcedDatapackTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergesTagFilesFromDifferentPacksIntoTheUnionOfTheirValues() throws IOException {
|
||||
Path root = Files.createTempDirectory("iris-forced-pack-tag-merge");
|
||||
try {
|
||||
Path tag = Path.of("data/minecraft/tags/worldgen/biome/water_on_map_outlines.json");
|
||||
Path source = root.resolve("source");
|
||||
Path destination = root.resolve("destination");
|
||||
Files.createDirectories(source.resolve(tag).getParent());
|
||||
Files.createDirectories(destination.resolve(tag).getParent());
|
||||
Files.writeString(source.resolve(tag),
|
||||
"{\"replace\":false,\"values\":[\"iris:biomes/b\",\"iris:biomes/c\",{\"id\":\"mod:river\",\"required\":false}]}",
|
||||
StandardCharsets.UTF_8);
|
||||
Files.writeString(destination.resolve(tag),
|
||||
"{\"replace\":false,\"values\":[\"iris:biomes/a\",\"iris:biomes/b\"]}",
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
ModdedForcedDatapack.mergeDirectory(source, destination);
|
||||
|
||||
JSONObject merged = new JSONObject(Files.readString(destination.resolve(tag), StandardCharsets.UTF_8));
|
||||
assertFalse(merged.getBoolean("replace"));
|
||||
List<String> values = new ArrayList<>();
|
||||
for (int index = 0; index < merged.getJSONArray("values").length(); index++) {
|
||||
Object value = merged.getJSONArray("values").get(index);
|
||||
values.add(value instanceof JSONObject entry ? entry.getString("id") : value.toString());
|
||||
}
|
||||
assertEquals(List.of("iris:biomes/a", "iris:biomes/b", "iris:biomes/c", "mod:river"), values);
|
||||
} finally {
|
||||
deleteTree(root);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagMergeKeepsStrictConflictsForNonTagFilesInTheSameMerge() throws IOException {
|
||||
Path root = Files.createTempDirectory("iris-forced-pack-tag-strict");
|
||||
try {
|
||||
Path tag = Path.of("data/minecraft/tags/worldgen/biome/is_overworld.json");
|
||||
Path biome = Path.of("data/iris/worldgen/biome/shared.json");
|
||||
for (String side : List.of("source", "destination")) {
|
||||
Files.createDirectories(root.resolve(side).resolve(tag).getParent());
|
||||
Files.createDirectories(root.resolve(side).resolve(biome).getParent());
|
||||
Files.writeString(root.resolve(side).resolve(tag),
|
||||
"{\"replace\":false,\"values\":[\"iris:biomes/" + side + "\"]}", StandardCharsets.UTF_8);
|
||||
}
|
||||
Files.writeString(root.resolve("source").resolve(biome), "{\"temperature\":1}", StandardCharsets.UTF_8);
|
||||
Files.writeString(root.resolve("destination").resolve(biome), "{\"temperature\":2}", StandardCharsets.UTF_8);
|
||||
|
||||
assertThrows(
|
||||
IOException.class,
|
||||
() -> ModdedForcedDatapack.mergeDirectory(root.resolve("source"), root.resolve("destination"))
|
||||
);
|
||||
} finally {
|
||||
deleteTree(root);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> directoryEntries(Path root) throws IOException {
|
||||
try (Stream<Path> entries = Files.list(root)) {
|
||||
return entries.map((Path path) -> path.getFileName().toString())
|
||||
|
||||
Reference in New Issue
Block a user