mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-18 22:44:09 +00:00
Scored ingest biome placement
- Mod biomes pick their template across all regions by climate distance, biome category agreement from minecraft and c tags, a reuse penalty that spreads biomes over equally good templates, and the region's overall climate - Regions much rarer than the dimension's median region are treated as themed and only receive a mod biome when nothing else fits - Templates derived from void biomes no longer act as climate references - /iris ingest mods accepts excludeRegions=<a,b> to keep mod biomes out of named regions; unknown region names are refused
This commit is contained in:
+29
-4
@@ -26,21 +26,25 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
record ModIngestArguments(String sourcePack, String targetPack, String dimension, Set<String> namespaces, boolean replace) {
|
||||
static final List<String> OPTION_SUGGESTIONS = List.of("dimension=", "namespaces=", "replace=true");
|
||||
record ModIngestArguments(String sourcePack, String targetPack, String dimension, Set<String> namespaces,
|
||||
Set<String> excludedRegions, boolean replace) {
|
||||
static final List<String> OPTION_SUGGESTIONS = List.of("dimension=", "namespaces=", "excludeRegions=", "replace=true");
|
||||
private static final Pattern PACK = Pattern.compile("[A-Za-z0-9_.-]+");
|
||||
private static final Pattern TARGET_PACK = Pattern.compile("[A-Za-z0-9_-]+");
|
||||
private static final Pattern DIMENSION = Pattern.compile("[A-Za-z0-9_/.-]+");
|
||||
private static final Pattern NAMESPACE = Pattern.compile("[a-z0-9_.-]+");
|
||||
private static final Pattern REGION = Pattern.compile("[A-Za-z0-9_/.-]+");
|
||||
private static final String DIMENSION_KEY = "dimension";
|
||||
private static final String NAMESPACES_KEY = "namespaces";
|
||||
private static final String REPLACE_KEY = "replace";
|
||||
private static final String EXCLUDE_REGIONS_KEY = "excludeRegions";
|
||||
|
||||
ModIngestArguments {
|
||||
Objects.requireNonNull(sourcePack, "sourcePack");
|
||||
Objects.requireNonNull(targetPack, "targetPack");
|
||||
Objects.requireNonNull(dimension, "dimension");
|
||||
namespaces = Set.copyOf(Objects.requireNonNull(namespaces, "namespaces"));
|
||||
excludedRegions = Set.copyOf(Objects.requireNonNull(excludedRegions, "excludedRegions"));
|
||||
}
|
||||
|
||||
static ModIngestArguments parse(String raw) {
|
||||
@@ -62,7 +66,8 @@ record ModIngestArguments(String sourcePack, String targetPack, String dimension
|
||||
continue;
|
||||
}
|
||||
String key = token.substring(0, equals);
|
||||
if (!key.equals(DIMENSION_KEY) && !key.equals(NAMESPACES_KEY) && !key.equals(REPLACE_KEY)) {
|
||||
if (!key.equals(DIMENSION_KEY) && !key.equals(NAMESPACES_KEY) && !key.equals(REPLACE_KEY)
|
||||
&& !key.equals(EXCLUDE_REGIONS_KEY)) {
|
||||
throw new IllegalArgumentException("unknown option '" + key + "'");
|
||||
}
|
||||
if (options.putIfAbsent(key, token.substring(equals + 1)) != null) {
|
||||
@@ -88,7 +93,7 @@ record ModIngestArguments(String sourcePack, String targetPack, String dimension
|
||||
throw new IllegalArgumentException("invalid dimension key '" + dimension + "'");
|
||||
}
|
||||
return new ModIngestArguments(source, target, dimension, namespaces(options.get(NAMESPACES_KEY)),
|
||||
replace(options.get(REPLACE_KEY)));
|
||||
regions(options.get(EXCLUDE_REGIONS_KEY)), replace(options.get(REPLACE_KEY)));
|
||||
}
|
||||
|
||||
private static void requirePack(String pack) {
|
||||
@@ -117,6 +122,26 @@ record ModIngestArguments(String sourcePack, String targetPack, String dimension
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
private static Set<String> regions(String raw) {
|
||||
if (raw == null) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<String> regions = new LinkedHashSet<>();
|
||||
for (String part : raw.split(",")) {
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!REGION.matcher(part).matches() || part.contains("..")) {
|
||||
throw new IllegalArgumentException("invalid region '" + part + "'");
|
||||
}
|
||||
regions.add(part);
|
||||
}
|
||||
if (regions.isEmpty()) {
|
||||
throw new IllegalArgumentException("excludeRegions must not be empty");
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
private static boolean replace(String raw) {
|
||||
if (raw == null || raw.equals("false")) {
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ final class ModdedCatalogCommands {
|
||||
}
|
||||
Path targetPack = packsRoot.toPath().toAbsolutePath().normalize().resolve(arguments.targetPack());
|
||||
ModIngestRequest request = new ModIngestRequest(sourcePack.toPath(), targetPack, arguments.dimension(),
|
||||
catalog.get(), arguments.namespaces(), arguments.replace());
|
||||
catalog.get(), arguments.namespaces(), arguments.excludedRegions(), arguments.replace());
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_STARTED,
|
||||
MessageArgument.untrusted("source", arguments.sourcePack()),
|
||||
MessageArgument.untrusted("target", arguments.targetPack()),
|
||||
|
||||
+1
-1
@@ -169,7 +169,7 @@ final class ModdedCommandHelp {
|
||||
));
|
||||
SECTIONS.put("pk", SECTIONS.get("pack"));
|
||||
SECTIONS.put("ingest", List.of(
|
||||
Entry.command("mods", "<source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]", ModdedCatalogMessages.HELP_INGEST_MODS)
|
||||
Entry.command("mods", "<source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]", ModdedCatalogMessages.HELP_INGEST_MODS)
|
||||
));
|
||||
SECTIONS.put("world", List.of(
|
||||
Entry.command("enable", "<dimension> <pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"),
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ public final class ModdedCatalogMessages {
|
||||
public static final TextKey INGEST_INVALID = TextKey.of(
|
||||
"iris.modded.catalog.ingest_invalid",
|
||||
"Invalid arguments: {reason}. Usage: /iris ingest mods <source> <target> [dimension=<key>] "
|
||||
+ "[namespaces=<a,b>] [replace=true]"
|
||||
+ "[namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
);
|
||||
public static final TextKey INGEST_PACK_MISSING = TextKey.of(
|
||||
"iris.modded.catalog.ingest_pack_missing",
|
||||
|
||||
+5
-1
@@ -36,15 +36,17 @@ public class ModIngestArgumentsTest {
|
||||
assertEquals("modded_overworld", arguments.targetPack());
|
||||
assertEquals("overworld", arguments.dimension());
|
||||
assertEquals(Set.of(), arguments.namespaces());
|
||||
assertEquals(Set.of(), arguments.excludedRegions());
|
||||
assertFalse(arguments.replace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyedOptionalsAreParsedInAnyOrder() {
|
||||
ModIngestArguments arguments = ModIngestArguments.parse(
|
||||
" overworld modded replace=true namespaces=biomesoplenty,terralith dimension=overworld_alt ");
|
||||
" overworld modded replace=true namespaces=biomesoplenty,terralith excludeRegions=prismatics,estranged dimension=overworld_alt ");
|
||||
|
||||
assertEquals("overworld_alt", arguments.dimension());
|
||||
assertEquals(Set.of("prismatics", "estranged"), arguments.excludedRegions());
|
||||
assertEquals(Set.of("biomesoplenty", "terralith"), arguments.namespaces());
|
||||
assertTrue(arguments.replace());
|
||||
}
|
||||
@@ -72,6 +74,8 @@ public class ModIngestArgumentsTest {
|
||||
assertEquals("invalid dimension key 'a..b'", reason("overworld modded dimension=a..b"));
|
||||
assertEquals("invalid namespace 'Bad'", reason("overworld modded namespaces=ok,Bad"));
|
||||
assertEquals("namespaces must not be empty", reason("overworld modded namespaces=,"));
|
||||
assertEquals("invalid region 'a/../b'", reason("overworld modded excludeRegions=a/../b"));
|
||||
assertEquals("excludeRegions must not be empty", reason("overworld modded excludeRegions=,"));
|
||||
assertEquals("source and target must be different packs", reason("overworld overworld"));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class ModdedCatalogCommandsTest {
|
||||
assertTrue(ModdedCommandHelp.documents("ingest", "mods"));
|
||||
List<String> lines = ModdedCommandHelp.consoleLines("ingest");
|
||||
assertTrue(lines.stream().anyMatch((String line) ->
|
||||
line.startsWith("/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true] - ")));
|
||||
line.startsWith("/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true] - ")));
|
||||
}
|
||||
|
||||
private static CommandNode<CommandSourceStack> child(CommandNode<CommandSourceStack> parent, String name) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 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.pack.ingest;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
final class BiomeCategories {
|
||||
private static final Map<String, Set<String>> CATEGORY_TAGS = Map.ofEntries(
|
||||
Map.entry("forest", Set.of("is_forest", "is_birch_forest", "is_dark_forest", "is_flower_forest", "is_tree/deciduous")),
|
||||
Map.entry("taiga", Set.of("is_taiga", "is_tree/coniferous", "is_old_growth")),
|
||||
Map.entry("jungle", Set.of("is_jungle", "is_tree/jungle")),
|
||||
Map.entry("savanna", Set.of("is_savanna", "is_tree/savanna")),
|
||||
Map.entry("badlands", Set.of("is_badlands")),
|
||||
Map.entry("desert", Set.of("is_desert", "is_sandy")),
|
||||
Map.entry("mountain", Set.of("is_mountain", "is_mountain/peak", "is_mountain/slope", "is_peak", "is_slope", "is_hill", "is_windswept", "is_plateau")),
|
||||
Map.entry("snowy", Set.of("is_snowy", "is_icy")),
|
||||
Map.entry("swamp", Set.of("is_swamp")),
|
||||
Map.entry("mushroom", Set.of("is_mushroom")),
|
||||
Map.entry("plains", Set.of("is_plains", "is_meadow")),
|
||||
Map.entry("beach", Set.of("is_beach", "is_stony_shores")),
|
||||
Map.entry("ocean", Set.of("is_ocean", "is_deep_ocean")),
|
||||
Map.entry("cave", Set.of("is_cave", "is_underground")));
|
||||
|
||||
private static final double UNKNOWN_DISTANCE = 0.5D;
|
||||
private static final String VOID_TAG = "is_void";
|
||||
|
||||
private BiomeCategories() {
|
||||
}
|
||||
|
||||
static Set<String> of(List<String> tags) {
|
||||
Set<String> paths = ModBiomeClassifier.conventionTags(tags);
|
||||
Set<String> categories = new TreeSet<>();
|
||||
for (Map.Entry<String, Set<String>> category : CATEGORY_TAGS.entrySet()) {
|
||||
for (String tag : category.getValue()) {
|
||||
if (paths.contains(tag)) {
|
||||
categories.add(category.getKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
static boolean isVoid(List<String> tags) {
|
||||
return ModBiomeClassifier.conventionTags(tags).contains(VOID_TAG);
|
||||
}
|
||||
|
||||
static double distance(Set<String> first, Set<String> second) {
|
||||
if (first.isEmpty() || second.isEmpty()) {
|
||||
return UNKNOWN_DISTANCE;
|
||||
}
|
||||
Set<String> union = new HashSet<>(first);
|
||||
union.addAll(second);
|
||||
int shared = 0;
|
||||
for (String category : first) {
|
||||
if (second.contains(category)) {
|
||||
shared++;
|
||||
}
|
||||
}
|
||||
return (union.size() - shared) / (double) union.size();
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
final class IngestPackModel {
|
||||
private static final int DEFAULT_MIN_Y = -64;
|
||||
@@ -111,8 +112,21 @@ final class IngestPackModel {
|
||||
}
|
||||
|
||||
IngestClimate biomeClimate(String biomeKey) {
|
||||
String derivative = reachableBiomes.get(biomeKey);
|
||||
return derivative == null ? null : index.biome(derivative).map(IngestClimate::of).orElse(null);
|
||||
return climateReference(index, reachableBiomes.get(biomeKey)).map(IngestClimate::of).orElse(null);
|
||||
}
|
||||
|
||||
Set<String> biomeCategories(String biomeKey) {
|
||||
return climateReference(index, reachableBiomes.get(biomeKey)).map((CatalogBiome biome) -> BiomeCategories.of(biome.tags())).orElse(Set.of());
|
||||
}
|
||||
|
||||
private static Optional<CatalogBiome> climateReference(WorldgenCatalogIndex index, String derivative) {
|
||||
if (derivative == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Optional<CatalogBiome> biome = index.biome(derivative);
|
||||
return biome.isPresent() && !BiomeCategories.isVoid(biome.get().tags()) && !biome.get().key().equals(DEFAULT_DERIVATIVE)
|
||||
? biome
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
VanillaHeights.VerticalBounds verticalBounds() {
|
||||
@@ -138,10 +152,10 @@ final class IngestPackModel {
|
||||
}
|
||||
List<IngestClimate> landClimates = new ArrayList<>();
|
||||
for (String biome : pools.get(BiomeRole.LAND)) {
|
||||
Optional<CatalogBiome> catalogBiome = index.biome(reachable.get(biome));
|
||||
Optional<CatalogBiome> catalogBiome = climateReference(index, reachable.get(biome));
|
||||
catalogBiome.ifPresent(value -> landClimates.add(IngestClimate.of(value)));
|
||||
}
|
||||
return new RegionProfile(key, IngestClimate.average(landClimates), pools);
|
||||
return new RegionProfile(key, IngestClimate.average(landClimates), (int) number(region.get("rarity"), 1D), pools);
|
||||
}
|
||||
|
||||
private static void collectReachable(IngestPack pack, List<JsonObject> regions, Map<String, String> reachable) throws IOException {
|
||||
@@ -192,7 +206,7 @@ final class IngestPackModel {
|
||||
}
|
||||
}
|
||||
|
||||
record RegionProfile(String key, IngestClimate climate, Map<BiomeRole, List<String>> pools) {
|
||||
record RegionProfile(String key, IngestClimate climate, int rarity, Map<BiomeRole, List<String>> pools) {
|
||||
List<String> pool(BiomeRole role) {
|
||||
return pools.get(role);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -36,15 +40,25 @@ final class ModBiomeAdopter {
|
||||
static final String INGEST_BIOME_FOLDER = "ingest/";
|
||||
private static final List<String> OVERRIDE_FIELDS = List.of("customDerivitives", "vanillaDerivative", "biomeScatter", "biomeSkyScatter", "children");
|
||||
private static final int VEGETAL_STEP = IrisDecorationStep.VEGETAL_DECORATION.ordinal();
|
||||
private static final double CATEGORY_WEIGHT = 1D;
|
||||
private static final double REGION_CLIMATE_WEIGHT = 0.25D;
|
||||
private static final double REUSE_WEIGHT = 0.05D;
|
||||
private static final double THEMED_REGION_PENALTY = 0.6D;
|
||||
private static final int THEMED_RARITY_FACTOR = 5;
|
||||
|
||||
private final IngestPackModel model;
|
||||
private final ModBiomeClassifier classifier;
|
||||
private final Set<String> decoratorFeatures;
|
||||
private final Set<String> excludedRegions;
|
||||
private final Set<String> themedRegions;
|
||||
private final Map<String, Integer> templateUses = new HashMap<>();
|
||||
|
||||
ModBiomeAdopter(IngestPackModel model, WorldgenCatalogIndex index, Set<String> decoratorFeatures) {
|
||||
ModBiomeAdopter(IngestPackModel model, WorldgenCatalogIndex index, Set<String> decoratorFeatures, Set<String> excludedRegions) {
|
||||
this.model = model;
|
||||
this.classifier = new ModBiomeClassifier(model.environment(), index.catalog());
|
||||
this.decoratorFeatures = decoratorFeatures;
|
||||
this.excludedRegions = Set.copyOf(excludedRegions);
|
||||
this.themedRegions = themedRegions(model.regions());
|
||||
}
|
||||
|
||||
Map<String, AdoptedBiome> adopt(List<CatalogBiome> modBiomes, List<ModIngestItem> items) throws IOException {
|
||||
@@ -57,14 +71,15 @@ final class ModBiomeAdopter {
|
||||
continue;
|
||||
}
|
||||
BiomeRole role = classification.role();
|
||||
IngestClimate climate = IngestClimate.of(biome);
|
||||
IngestPackModel.RegionProfile region = nearestRegion(role, climate);
|
||||
if (region == null) {
|
||||
Placement placement = place(role, IngestClimate.of(biome), BiomeCategories.of(biome.tags()));
|
||||
if (placement == null) {
|
||||
items.add(new ModIngestItem(namespace, biome.key(), "biome", ModIngestTier.SKIPPED, "",
|
||||
"no region in this dimension has a " + role.label() + " biome pool"));
|
||||
"no region in this dimension accepts a " + role.label() + " biome"));
|
||||
continue;
|
||||
}
|
||||
String template = nearestTemplate(region, role, climate);
|
||||
IngestPackModel.RegionProfile region = placement.region();
|
||||
String template = placement.template();
|
||||
templateUses.merge(template, 1, Integer::sum);
|
||||
String irisKey = irisKey(biome.key());
|
||||
JsonObject json = model.pack().require(IngestPack.biomePath(template)).deepCopy();
|
||||
transform(json, biome, ownsVegetation(biome, namespace));
|
||||
@@ -83,34 +98,47 @@ final class ModBiomeAdopter {
|
||||
return INGEST_BIOME_FOLDER + namespace + "/" + modBiomeKey.substring(colon + 1);
|
||||
}
|
||||
|
||||
private IngestPackModel.RegionProfile nearestRegion(BiomeRole role, IngestClimate climate) {
|
||||
IngestPackModel.RegionProfile best = null;
|
||||
double bestDistance = Double.POSITIVE_INFINITY;
|
||||
private Placement place(BiomeRole role, IngestClimate climate, Set<String> categories) {
|
||||
Placement best = null;
|
||||
double bestScore = Double.POSITIVE_INFINITY;
|
||||
for (IngestPackModel.RegionProfile region : model.regions()) {
|
||||
if (region.pool(role).isEmpty()) {
|
||||
if (excludedRegions.contains(region.key())) {
|
||||
continue;
|
||||
}
|
||||
double distance = climate.distance(region.climate());
|
||||
if (best == null || distance < bestDistance) {
|
||||
best = region;
|
||||
bestDistance = distance;
|
||||
double regionPenalty = (themedRegions.contains(region.key()) ? THEMED_REGION_PENALTY : 0D)
|
||||
+ (region.climate() == null ? 0D : REGION_CLIMATE_WEIGHT * climate.distance(region.climate()));
|
||||
for (String template : region.pool(role)) {
|
||||
IngestClimate templateClimate = model.biomeClimate(template);
|
||||
double score = climate.distance(templateClimate == null ? region.climate() : templateClimate)
|
||||
+ CATEGORY_WEIGHT * BiomeCategories.distance(categories, model.biomeCategories(template))
|
||||
+ REUSE_WEIGHT * templateUses.getOrDefault(template, 0)
|
||||
+ regionPenalty;
|
||||
if (best == null || score < bestScore) {
|
||||
best = new Placement(region, template);
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private String nearestTemplate(IngestPackModel.RegionProfile region, BiomeRole role, IngestClimate climate) {
|
||||
String best = null;
|
||||
double bestDistance = Double.POSITIVE_INFINITY;
|
||||
for (String candidate : region.pool(role)) {
|
||||
IngestClimate candidateClimate = model.biomeClimate(candidate);
|
||||
double distance = climate.distance(candidateClimate == null ? region.climate() : candidateClimate);
|
||||
if (best == null || distance < bestDistance) {
|
||||
best = candidate;
|
||||
bestDistance = distance;
|
||||
private static Set<String> themedRegions(List<IngestPackModel.RegionProfile> regions) {
|
||||
if (regions.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
List<Integer> rarities = new ArrayList<>(regions.size());
|
||||
for (IngestPackModel.RegionProfile region : regions) {
|
||||
rarities.add(region.rarity());
|
||||
}
|
||||
rarities.sort(Comparator.naturalOrder());
|
||||
int median = Math.max(1, rarities.get(rarities.size() / 2));
|
||||
Set<String> themed = new HashSet<>();
|
||||
for (IngestPackModel.RegionProfile region : regions) {
|
||||
if (region.rarity() >= median * THEMED_RARITY_FACTOR) {
|
||||
themed.add(region.key());
|
||||
}
|
||||
}
|
||||
return best;
|
||||
return themed;
|
||||
}
|
||||
|
||||
private void transform(JsonObject json, CatalogBiome biome, boolean ownsVegetation) throws IOException {
|
||||
@@ -170,4 +198,7 @@ final class ModBiomeAdopter {
|
||||
|
||||
record AdoptedBiome(String modKey, String irisKey, BiomeRole role) {
|
||||
}
|
||||
|
||||
private record Placement(IngestPackModel.RegionProfile region, String template) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ final class ModBiomeClassifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Set<String> conventionTags(List<String> tags) {
|
||||
static Set<String> conventionTags(List<String> tags) {
|
||||
Set<String> paths = new HashSet<>();
|
||||
for (String tag : tags) {
|
||||
String key = VanillaJson.namespaced(tag);
|
||||
|
||||
@@ -25,12 +25,14 @@ import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public record ModIngestRequest(Path sourcePack, Path targetPack, String dimensionKey, WorldgenCatalog catalog, Set<String> namespaces, boolean replaceTarget) {
|
||||
public record ModIngestRequest(Path sourcePack, Path targetPack, String dimensionKey, WorldgenCatalog catalog, Set<String> namespaces,
|
||||
Set<String> excludedRegions, boolean replaceTarget) {
|
||||
public ModIngestRequest {
|
||||
Objects.requireNonNull(sourcePack, "sourcePack");
|
||||
Objects.requireNonNull(targetPack, "targetPack");
|
||||
Objects.requireNonNull(dimensionKey, "dimensionKey");
|
||||
Objects.requireNonNull(catalog, "catalog");
|
||||
namespaces = Set.copyOf(Objects.requireNonNull(namespaces, "namespaces"));
|
||||
excludedRegions = Set.copyOf(Objects.requireNonNull(excludedRegions, "excludedRegions"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,9 @@ public final class ModIngester {
|
||||
IngestPackModel model = IngestPackModel.load(pack, targetKey, index);
|
||||
ModFeatureIngester features = new ModFeatureIngester(model, index, namespaces);
|
||||
List<ModIngestItem> biomeItems = new ArrayList<>();
|
||||
Map<String, ModBiomeAdopter.AdoptedBiome> adopted = new ModBiomeAdopter(model, index, features.decoratorKeys())
|
||||
.adopt(modBiomes(index, namespaces), biomeItems);
|
||||
requireKnownRegions(model, request.excludedRegions());
|
||||
Map<String, ModBiomeAdopter.AdoptedBiome> adopted = new ModBiomeAdopter(model, index, features.decoratorKeys(),
|
||||
request.excludedRegions()).adopt(modBiomes(index, namespaces), biomeItems);
|
||||
List<ModIngestItem> featureItems = features.apply(adopted);
|
||||
JsonObject dimension = pack.edit(model.dimensionPath());
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, features.nativeKeys(),
|
||||
@@ -101,6 +102,16 @@ public final class ModIngester {
|
||||
return new ModIngestReport(target, items, IngestManifest.summary(targetKey, pack, reported, items));
|
||||
}
|
||||
|
||||
private static void requireKnownRegions(IngestPackModel model, Set<String> excludedRegions) {
|
||||
Set<String> unknown = new TreeSet<>(excludedRegions);
|
||||
for (IngestPackModel.RegionProfile region : model.regions()) {
|
||||
unknown.remove(region.key());
|
||||
}
|
||||
if (!unknown.isEmpty()) {
|
||||
throw new IllegalArgumentException("Excluded regions " + unknown + " are not regions of dimension " + model.dimensionPath() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> reportedNamespaces(Set<String> namespaces, List<ModIngestItem> items) {
|
||||
Set<String> reported = new TreeSet<>();
|
||||
for (ModIngestItem item : items) {
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Dimensionstypen: {value}; Level-Stems: {value2}"
|
||||
refreshing = "Worldgen-Katalog wird erfasst..."
|
||||
refreshed = "Worldgen-Katalog in {duration} erfasst: {path}"
|
||||
refresh_failed = "Erfassung des Worldgen-Katalogs fehlgeschlagen: {reason}"
|
||||
ingest_invalid = "Ungültige Argumente: {reason}. Verwendung: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Ungültige Argumente: {reason}. Verwendung: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Pack '{pack}' wurde unter {path} nicht gefunden."
|
||||
ingest_started = "Übernehme Mod-Worldgen aus '{source}' in '{target}' (Dimension {dimension})..."
|
||||
ingest_failed = "Mod-Übernahme fehlgeschlagen: {reason}"
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Tipos de dimensión: {value}; level stems: {value2}"
|
||||
refreshing = "Capturando el catálogo de generación de mundo..."
|
||||
refreshed = "Catálogo de generación capturado en {duration}: {path}"
|
||||
refresh_failed = "Error al capturar el catálogo de generación: {reason}"
|
||||
ingest_invalid = "Argumentos no válidos: {reason}. Uso: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Argumentos no válidos: {reason}. Uso: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "No se encontró el pack '{pack}' en {path}."
|
||||
ingest_started = "Incorporando la generación de mods de '{source}' a '{target}' (dimensión {dimension})..."
|
||||
ingest_failed = "La incorporación de mods falló: {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "Ulottuvuustyypit: {value}; level stemit: {value2}"
|
||||
refreshing = "Kerätään maailmangeneroinnin luetteloa..."
|
||||
refreshed = "Maailmangeneroinnin luettelo kerätty ajassa {duration}: {path}"
|
||||
refresh_failed = "Maailmangeneroinnin luettelon kerääminen epäonnistui: {reason}"
|
||||
ingest_invalid = "Virheelliset argumentit: {reason}. Käyttö: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Virheelliset argumentit: {reason}. Käyttö: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Pakettia '{pack}' ei löytynyt kohteesta {path}."
|
||||
ingest_started = "Tuodaan modien maailmangenerointi paketista '{source}' pakettiin '{target}' (ulottuvuus {dimension})..."
|
||||
ingest_failed = "Modien tuonti epäonnistui: {reason}"
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Types de dimension : {value} ; level stems : {value2}"
|
||||
refreshing = "Capture du catalogue de génération..."
|
||||
refreshed = "Catalogue de génération capturé en {duration} : {path}"
|
||||
refresh_failed = "Échec de la capture du catalogue de génération : {reason}"
|
||||
ingest_invalid = "Arguments invalides : {reason}. Utilisation : /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Arguments invalides : {reason}. Utilisation : /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Le pack '{pack}' est introuvable dans {path}."
|
||||
ingest_started = "Intégration de la génération des mods de '{source}' vers '{target}' (dimension {dimension})..."
|
||||
ingest_failed = "L'intégration des mods a échoué : {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "סוגי ממדים: {value}; level stems: {value2}"
|
||||
refreshing = "אוסף את קטלוג יצירת העולם..."
|
||||
refreshed = "קטלוג יצירת העולם נאסף תוך {duration}: {path}"
|
||||
refresh_failed = "איסוף קטלוג יצירת העולם נכשל: {reason}"
|
||||
ingest_invalid = "ארגומנטים לא תקינים: {reason}. שימוש: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "ארגומנטים לא תקינים: {reason}. שימוש: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "החבילה '{pack}' לא נמצאה תחת {path}."
|
||||
ingest_started = "מייבא את יצירת העולם של המודים מ-'{source}' אל '{target}' (ממד {dimension})..."
|
||||
ingest_failed = "ייבוא המודים נכשל: {reason}"
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Tipi di dimensione: {value}; level stem: {value2}"
|
||||
refreshing = "Acquisizione del catalogo di generazione..."
|
||||
refreshed = "Catalogo di generazione acquisito in {duration}: {path}"
|
||||
refresh_failed = "Acquisizione del catalogo di generazione non riuscita: {reason}"
|
||||
ingest_invalid = "Argomenti non validi: {reason}. Uso: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Argomenti non validi: {reason}. Uso: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Il pack '{pack}' non è stato trovato in {path}."
|
||||
ingest_started = "Importazione della generazione delle mod da '{source}' a '{target}' (dimensione {dimension})..."
|
||||
ingest_failed = "Importazione delle mod non riuscita: {reason}"
|
||||
|
||||
@@ -475,7 +475,7 @@ detail_dimensions = "ディメンションタイプ: {value}、レベルステ
|
||||
refreshing = "ワールド生成カタログを取得しています..."
|
||||
refreshed = "ワールド生成カタログを {duration} で取得しました: {path}"
|
||||
refresh_failed = "ワールド生成カタログの取得に失敗しました: {reason}"
|
||||
ingest_invalid = "引数が無効です: {reason}。使い方: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "引数が無効です: {reason}。使い方: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "パック '{pack}' が {path} に見つかりません。"
|
||||
ingest_started = "'{source}' から '{target}' へ Mod のワールド生成を取り込んでいます (ディメンション {dimension})..."
|
||||
ingest_failed = "Mod の取り込みに失敗しました: {reason}"
|
||||
|
||||
@@ -475,7 +475,7 @@ detail_dimensions = "차원 유형: {value}; 레벨 스템: {value2}"
|
||||
refreshing = "월드 생성 카탈로그를 수집하는 중..."
|
||||
refreshed = "월드 생성 카탈로그를 {duration} 만에 수집했습니다: {path}"
|
||||
refresh_failed = "월드 생성 카탈로그 수집에 실패했습니다: {reason}"
|
||||
ingest_invalid = "잘못된 인수: {reason}. 사용법: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "잘못된 인수: {reason}. 사용법: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "{path}에서 팩 '{pack}'을(를) 찾을 수 없습니다."
|
||||
ingest_started = "'{source}'에서 '{target}'(으)로 모드 월드 생성을 가져오는 중 (차원 {dimension})..."
|
||||
ingest_failed = "모드 가져오기에 실패했습니다: {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "Dimensijų tipai: {value}; lygio stiebai: {value2}"
|
||||
refreshing = "Renkamas pasaulio generavimo katalogas..."
|
||||
refreshed = "Pasaulio generavimo katalogas surinktas per {duration}: {path}"
|
||||
refresh_failed = "Nepavyko surinkti pasaulio generavimo katalogo: {reason}"
|
||||
ingest_invalid = "Netinkami argumentai: {reason}. Naudojimas: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Netinkami argumentai: {reason}. Naudojimas: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Paketas '{pack}' nerastas {path}."
|
||||
ingest_started = "Perkeliamas modų pasaulio generavimas iš '{source}' į '{target}' (dimensija {dimension})..."
|
||||
ingest_failed = "Modų perkėlimas nepavyko: {reason}"
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Dimensietypen: {value}; level stems: {value2}"
|
||||
refreshing = "Wereldgeneratiecatalogus wordt vastgelegd..."
|
||||
refreshed = "Wereldgeneratiecatalogus vastgelegd in {duration}: {path}"
|
||||
refresh_failed = "Vastleggen van de wereldgeneratiecatalogus mislukt: {reason}"
|
||||
ingest_invalid = "Ongeldige argumenten: {reason}. Gebruik: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Ongeldige argumenten: {reason}. Gebruik: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Pack '{pack}' is niet gevonden in {path}."
|
||||
ingest_started = "Wereldgeneratie van mods wordt overgenomen van '{source}' naar '{target}' (dimensie {dimension})..."
|
||||
ingest_failed = "Overnemen van mods mislukt: {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "Typy wymiarów: {value}; level stemy: {value2}"
|
||||
refreshing = "Zbieranie katalogu generowania świata..."
|
||||
refreshed = "Katalog generowania świata zebrany w {duration}: {path}"
|
||||
refresh_failed = "Nie udało się zebrać katalogu generowania świata: {reason}"
|
||||
ingest_invalid = "Nieprawidłowe argumenty: {reason}. Użycie: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Nieprawidłowe argumenty: {reason}. Użycie: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Nie znaleziono paczki '{pack}' w {path}."
|
||||
ingest_started = "Przenoszenie generowania świata modów z '{source}' do '{target}' (wymiar {dimension})..."
|
||||
ingest_failed = "Przenoszenie modów nie powiodło się: {reason}"
|
||||
|
||||
@@ -472,7 +472,7 @@ detail_dimensions = "Tipos de dimensão: {value}; level stems: {value2}"
|
||||
refreshing = "A capturar o catálogo de geração de mundo..."
|
||||
refreshed = "Catálogo de geração capturado em {duration}: {path}"
|
||||
refresh_failed = "Falha ao capturar o catálogo de geração: {reason}"
|
||||
ingest_invalid = "Argumentos inválidos: {reason}. Utilização: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Argumentos inválidos: {reason}. Utilização: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "O pack '{pack}' não foi encontrado em {path}."
|
||||
ingest_started = "A importar a geração de mods de '{source}' para '{target}' (dimensão {dimension})..."
|
||||
ingest_failed = "A importação de mods falhou: {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "Типы измерений: {value}; level stem: {value2}"
|
||||
refreshing = "Сбор каталога генерации мира..."
|
||||
refreshed = "Каталог генерации мира собран за {duration}: {path}"
|
||||
refresh_failed = "Не удалось собрать каталог генерации мира: {reason}"
|
||||
ingest_invalid = "Неверные аргументы: {reason}. Использование: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Неверные аргументы: {reason}. Использование: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Пак '{pack}' не найден в {path}."
|
||||
ingest_started = "Перенос генерации мира модов из '{source}' в '{target}' (измерение {dimension})..."
|
||||
ingest_failed = "Не удалось перенести моды: {reason}"
|
||||
|
||||
@@ -477,7 +477,7 @@ detail_dimensions = "Boyut türleri: {value}; level stem'ler: {value2}"
|
||||
refreshing = "Dünya oluşturma kataloğu toplanıyor..."
|
||||
refreshed = "Dünya oluşturma kataloğu {duration} içinde toplandı: {path}"
|
||||
refresh_failed = "Dünya oluşturma kataloğu toplanamadı: {reason}"
|
||||
ingest_invalid = "Geçersiz argümanlar: {reason}. Kullanım: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Geçersiz argümanlar: {reason}. Kullanım: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "'{pack}' paketi {path} altında bulunamadı."
|
||||
ingest_started = "Mod dünya oluşturması '{source}' paketinden '{target}' paketine aktarılıyor (boyut {dimension})..."
|
||||
ingest_failed = "Mod aktarımı başarısız oldu: {reason}"
|
||||
|
||||
@@ -475,7 +475,7 @@ detail_dimensions = "Loại chiều: {value}; level stem: {value2}"
|
||||
refreshing = "Đang thu thập danh mục tạo thế giới..."
|
||||
refreshed = "Đã thu thập danh mục tạo thế giới trong {duration}: {path}"
|
||||
refresh_failed = "Thu thập danh mục tạo thế giới thất bại: {reason}"
|
||||
ingest_invalid = "Đối số không hợp lệ: {reason}. Cách dùng: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "Đối số không hợp lệ: {reason}. Cách dùng: /iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "Không tìm thấy gói '{pack}' trong {path}."
|
||||
ingest_started = "Đang đưa phần tạo thế giới của mod từ '{source}' vào '{target}' (chiều {dimension})..."
|
||||
ingest_failed = "Đưa mod vào thất bại: {reason}"
|
||||
|
||||
@@ -475,7 +475,7 @@ detail_dimensions = "维度类型:{value};level stem:{value2}"
|
||||
refreshing = "正在采集世界生成目录..."
|
||||
refreshed = "世界生成目录已在 {duration} 内采集完成:{path}"
|
||||
refresh_failed = "采集世界生成目录失败:{reason}"
|
||||
ingest_invalid = "参数无效:{reason}。用法:/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "参数无效:{reason}。用法:/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "在 {path} 下未找到包 '{pack}'。"
|
||||
ingest_started = "正在将模组世界生成从 '{source}' 导入 '{target}'(维度 {dimension})..."
|
||||
ingest_failed = "模组导入失败:{reason}"
|
||||
|
||||
@@ -475,7 +475,7 @@ detail_dimensions = "維度類型:{value};level stem:{value2}"
|
||||
refreshing = "正在擷取世界生成目錄..."
|
||||
refreshed = "世界生成目錄已在 {duration} 內擷取完成:{path}"
|
||||
refresh_failed = "擷取世界生成目錄失敗:{reason}"
|
||||
ingest_invalid = "參數無效:{reason}。用法:/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [replace=true]"
|
||||
ingest_invalid = "參數無效:{reason}。用法:/iris ingest mods <source> <target> [dimension=<key>] [namespaces=<a,b>] [excludeRegions=<a,b>] [replace=true]"
|
||||
ingest_pack_missing = "在 {path} 下找不到包 '{pack}'。"
|
||||
ingest_started = "正在將模組世界生成從 '{source}' 匯入 '{target}'(維度 {dimension})..."
|
||||
ingest_failed = "模組匯入失敗:{reason}"
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 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.pack.ingest;
|
||||
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogDimensionType;
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogNamespace;
|
||||
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IngestBiomePlacementTest {
|
||||
private static final String MOD = "modpack";
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void modBiomesAvoidThemedRegionsAndFollowTheirCategory() throws Exception {
|
||||
ModIngestReport report = ingest(Set.of());
|
||||
|
||||
assertEquals("temperate", region(report, "lavender_fields"));
|
||||
assertEquals("temperate", region(report, "redwood_forest"));
|
||||
assertTrue(reason(report, "redwood_forest").contains("temperate/forest"));
|
||||
assertEquals("hot", region(report, "brushland"));
|
||||
assertTrue(reason(report, "brushland").contains("hot/savanna"));
|
||||
for (int index = 0; index < 6; index++) {
|
||||
assertEquals("temperate", region(report, "prairie_" + index));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repeatedClimatesSpreadOverEquallyGoodTemplates() throws Exception {
|
||||
ModIngestReport report = ingest(Set.of());
|
||||
|
||||
Set<String> templates = new TreeSet<>();
|
||||
for (int index = 0; index < 6; index++) {
|
||||
String reason = reason(report, "prairie_" + index);
|
||||
templates.add(reason.substring(reason.indexOf("from template ") + "from template ".length()));
|
||||
}
|
||||
assertEquals(Set.of("temperate/meadow", "temperate/plains"), templates);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludedRegionsNeverReceiveModBiomes() throws Exception {
|
||||
ModIngestReport report = ingest(Set.of("temperate"));
|
||||
|
||||
for (ModIngestItem item : report.items()) {
|
||||
if (item.kind().equals("biome") && item.tier() == ModIngestTier.ADOPTED) {
|
||||
assertFalse(item.reason(), item.reason().contains("in region temperate "));
|
||||
}
|
||||
}
|
||||
assertThrows(IllegalArgumentException.class, () -> ingest(Set.of("missing")));
|
||||
}
|
||||
|
||||
private ModIngestReport ingest(Set<String> excludedRegions) throws IOException {
|
||||
Path root = folder.newFolder().toPath();
|
||||
Path source = root.resolve("source");
|
||||
ModIngestFixture.write(source, "dimensions/overworld.json", """
|
||||
{"name": "Overworld", "environment": "NORMAL", "regions": ["temperate", "hot", "prismatic"], "dimensionHeight": {"min": -64, "max": 320}}
|
||||
""");
|
||||
ModIngestFixture.write(source, "regions/temperate.json", """
|
||||
{"name": "Temperate", "landBiomes": ["temperate/plains", "temperate/meadow", "temperate/forest"], "seaBiomes": ["temperate/ocean"]}
|
||||
""");
|
||||
ModIngestFixture.write(source, "regions/hot.json", """
|
||||
{"name": "Hot", "landBiomes": ["hot/desert", "hot/savanna"]}
|
||||
""");
|
||||
ModIngestFixture.write(source, "regions/prismatic.json", """
|
||||
{"name": "Prismatic", "rarity": 20, "landBiomes": ["prismatic/white", "prismatic/red", "prismatic/blue"]}
|
||||
""");
|
||||
biome(source, "temperate/plains", "minecraft:plains");
|
||||
biome(source, "temperate/meadow", "minecraft:plains");
|
||||
biome(source, "temperate/forest", "minecraft:forest");
|
||||
biome(source, "temperate/ocean", "minecraft:ocean");
|
||||
biome(source, "hot/desert", "minecraft:desert");
|
||||
biome(source, "hot/savanna", "minecraft:savanna");
|
||||
biome(source, "prismatic/white", "minecraft:plains");
|
||||
biome(source, "prismatic/red", "minecraft:plains");
|
||||
biome(source, "prismatic/blue", "minecraft:plains");
|
||||
Path target = root.resolve("packs").resolve("placed");
|
||||
return ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(MOD), excludedRegions, false));
|
||||
}
|
||||
|
||||
private static void biome(Path pack, String key, String derivative) throws IOException {
|
||||
ModIngestFixture.write(pack, "biomes/" + key + ".json", "{\"name\": \"" + key + "\", \"derivative\": \"" + derivative + "\"}");
|
||||
}
|
||||
|
||||
private static WorldgenCatalog catalog() {
|
||||
List<CatalogBiome> biomes = new ArrayList<>();
|
||||
biomes.add(biome("minecraft:plains", 0.8F, 0.4F, "c:is_plains"));
|
||||
biomes.add(biome("minecraft:forest", 0.7F, 0.8F, "minecraft:is_forest"));
|
||||
biomes.add(biome("minecraft:desert", 2.0F, 0.0F, "c:is_desert"));
|
||||
biomes.add(biome("minecraft:savanna", 1.2F, 0.4F, "minecraft:is_savanna"));
|
||||
biomes.add(biome("minecraft:ocean", 0.5F, 0.5F, "minecraft:is_ocean"));
|
||||
biomes.add(biome(MOD + ":lavender_fields", 0.8F, 0.5F, "c:is_plains"));
|
||||
biomes.add(biome(MOD + ":redwood_forest", 0.8F, 0.6F, "minecraft:is_forest"));
|
||||
biomes.add(biome(MOD + ":brushland", 1.3F, 0.3F, "minecraft:is_savanna"));
|
||||
for (int index = 0; index < 6; index++) {
|
||||
biomes.add(biome(MOD + ":prairie_" + index, 0.8F, 0.4F, "c:is_plains"));
|
||||
}
|
||||
return new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1000L, "fingerprint-placement",
|
||||
List.of(new CatalogNamespace(MOD, "Mod Pack", "1.0.0")), biomes, List.of(), List.of(),
|
||||
List.of(new CatalogDimensionType("minecraft:overworld", -64, 384, 384, null)), List.of());
|
||||
}
|
||||
|
||||
private static CatalogBiome biome(String key, float temperature, float downfall, String categoryTag) {
|
||||
List<List<String>> steps = new ArrayList<>(11);
|
||||
for (int index = 0; index < 11; index++) {
|
||||
steps.add(List.of());
|
||||
}
|
||||
return new CatalogBiome(key, temperature, downfall, true, List.of("minecraft:is_overworld", categoryTag), steps, List.of(), null);
|
||||
}
|
||||
|
||||
private static String region(ModIngestReport report, String biome) {
|
||||
String reason = reason(report, biome);
|
||||
int start = reason.indexOf("in region ") + "in region ".length();
|
||||
return reason.substring(start, reason.indexOf(' ', start));
|
||||
}
|
||||
|
||||
private static String reason(ModIngestReport report, String biome) {
|
||||
ModIngestItem item = ModIngestFixture.item(report, MOD + ":" + biome);
|
||||
assertEquals(ModIngestTier.ADOPTED, item.tier());
|
||||
return item.reason();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class IngestHeightBoundsTest {
|
||||
}
|
||||
""".formatted(heightFields));
|
||||
Path target = root.resolve("packs").resolve("modded");
|
||||
ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog, Set.of(ModIngestFixture.MOD), false));
|
||||
ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog, Set.of(ModIngestFixture.MOD), Set.of(), false));
|
||||
JsonObject dimension = ModIngestFixture.readJson(target.resolve("dimensions/modded.json"));
|
||||
JsonObject stone = ModIngestFixture.findByPaletteBlock(dimension.getAsJsonArray("deposits"), "examplemod:tin_ore");
|
||||
assertNotNull(stone);
|
||||
|
||||
@@ -45,7 +45,7 @@ public class IngestReservedNamespaceTest {
|
||||
ModIngestFixture.write(root.resolve("packs").resolve("underworld"), "dimensions/underworld.json", "{\"name\": \"Underworld\"}");
|
||||
Path target = root.resolve("packs").resolve("modded");
|
||||
|
||||
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(), false));
|
||||
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(), Set.of(), false));
|
||||
|
||||
assertNull(ModIngestFixture.item(report, "irisworldgen:packs/6f76/dimensions/6f76/biomes/dunes"));
|
||||
assertNull(ModIngestFixture.item(report, "overworld:dunes"));
|
||||
@@ -63,7 +63,7 @@ public class IngestReservedNamespaceTest {
|
||||
|
||||
for (String namespace : List.of("irisworldgen", "overworld")) {
|
||||
assertThrows(namespace, IllegalArgumentException.class, () -> ModIngester.ingest(
|
||||
new ModIngestRequest(source, target, "overworld", catalog(), Set.of(namespace), false)));
|
||||
new ModIngestRequest(source, target, "overworld", catalog(), Set.of(namespace), Set.of(), false)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ final class ModIngestFixture {
|
||||
}
|
||||
|
||||
static ModIngestRequest request(Path source, Path target, Set<String> namespaces, boolean replace) {
|
||||
return new ModIngestRequest(source, target, "overworld", catalog(), namespaces, replace);
|
||||
return new ModIngestRequest(source, target, "overworld", catalog(), namespaces, Set.of(), replace);
|
||||
}
|
||||
|
||||
static JsonObject readJson(Path file) throws IOException {
|
||||
|
||||
@@ -158,7 +158,7 @@ public class ModIngesterPackTest {
|
||||
Map<String, String> first = snapshot(target);
|
||||
|
||||
assertThrows(IOException.class, () -> ModIngester.ingest(new ModIngestRequest(
|
||||
source, target, "missing", ModIngestFixture.catalog(), Set.of(ModIngestFixture.MOD), true)));
|
||||
source, target, "missing", ModIngestFixture.catalog(), Set.of(ModIngestFixture.MOD), Set.of(), true)));
|
||||
|
||||
assertEquals(first, snapshot(target));
|
||||
assertEquals(List.of("modded"), directoryNames(root.resolve("packs")));
|
||||
@@ -184,7 +184,7 @@ public class ModIngesterPackTest {
|
||||
ModIngester.ingest(ModIngestFixture.request(source, target, Set.of(ModIngestFixture.MOD), false));
|
||||
|
||||
IOException failure = assertThrows(IOException.class, () -> ModIngester.ingest(new ModIngestRequest(
|
||||
target, root.resolve("packs").resolve("again"), "modded", ModIngestFixture.catalog(), Set.of(), false)));
|
||||
target, root.resolve("packs").resolve("again"), "modded", ModIngestFixture.catalog(), Set.of(), Set.of(), false)));
|
||||
|
||||
assertTrue(failure.getMessage(), failure.getMessage().contains("original pack"));
|
||||
assertFalse(Files.exists(root.resolve("packs").resolve("again")));
|
||||
@@ -218,7 +218,7 @@ public class ModIngesterPackTest {
|
||||
fixture.capturedAtEpochMillis(), fixture.contentFingerprint(), namespaces, fixture.biomes(),
|
||||
fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems());
|
||||
|
||||
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog, Set.of(), false));
|
||||
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog, Set.of(), Set.of(), false));
|
||||
|
||||
assertEquals(List.of("examplemod", "othermod"),
|
||||
ModIngestFixture.strings(ModIngestFixture.readJson(target.resolve("ingest/manifest.json")).get("namespaces")));
|
||||
|
||||
Reference in New Issue
Block a user