mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-19 23:14:10 +00:00
Capped adoptions per template
- Each pack biome now serves as the template for at most five adopted land biomes and three adopted sea, shore, or cave biomes - A mod biome whose best template is full falls back to the next best template under the cap - A mod biome that finds every compatible template full is skipped with a reason naming the cap instead of being dropped
This commit is contained in:
@@ -19,17 +19,19 @@
|
||||
package art.arcane.iris.pack.ingest;
|
||||
|
||||
enum BiomeRole {
|
||||
LAND("landBiomes", "land"),
|
||||
SEA("seaBiomes", "sea"),
|
||||
SHORE("shoreBiomes", "shore"),
|
||||
CAVE("caveBiomes", "cave");
|
||||
LAND("landBiomes", "land", 5),
|
||||
SEA("seaBiomes", "sea", 3),
|
||||
SHORE("shoreBiomes", "shore", 3),
|
||||
CAVE("caveBiomes", "cave", 3);
|
||||
|
||||
private final String poolField;
|
||||
private final String label;
|
||||
private final int templateCap;
|
||||
|
||||
BiomeRole(String poolField, String label) {
|
||||
BiomeRole(String poolField, String label, int templateCap) {
|
||||
this.poolField = poolField;
|
||||
this.label = label;
|
||||
this.templateCap = templateCap;
|
||||
}
|
||||
|
||||
String poolField() {
|
||||
@@ -39,4 +41,8 @@ enum BiomeRole {
|
||||
String label() {
|
||||
return label;
|
||||
}
|
||||
|
||||
int templateCap() {
|
||||
return templateCap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@ final class ModBiomeAdopter {
|
||||
boolean ownsVegetation = ownsVegetation(biome, namespace);
|
||||
Placement placement = place(role, IngestClimate.of(biome), BiomeCategories.of(biome.tags()), ownsVegetation);
|
||||
if (placement == null) {
|
||||
items.add(new ModIngestItem(namespace, biome.key(), "biome", ModIngestTier.SKIPPED, "",
|
||||
"no region in this dimension accepts a " + role.label() + " biome"));
|
||||
items.add(new ModIngestItem(namespace, biome.key(), "biome", ModIngestTier.SKIPPED, "", unplacedReason(role)));
|
||||
continue;
|
||||
}
|
||||
IngestPackModel.RegionProfile region = placement.region();
|
||||
@@ -114,10 +113,14 @@ final class ModBiomeAdopter {
|
||||
+ (regionClimate == null ? 0D : REGION_CLIMATE_WEIGHT * climate.distance(regionClimate))
|
||||
+ REGION_CATEGORY_WEIGHT * (1D - regionAffinity(region, role, categories));
|
||||
for (String template : region.pool(role)) {
|
||||
int uses = templateUses.getOrDefault(template, 0);
|
||||
if (uses >= role.templateCap()) {
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
+ REUSE_WEIGHT * uses
|
||||
+ (ownsVegetation ? BARE_SURFACE_PENALTY * (1D - model.plantableShare(template)) : 0D)
|
||||
+ regionPenalty;
|
||||
if (best == null || score < bestScore) {
|
||||
@@ -129,6 +132,15 @@ final class ModBiomeAdopter {
|
||||
return best;
|
||||
}
|
||||
|
||||
private String unplacedReason(BiomeRole role) {
|
||||
for (IngestPackModel.RegionProfile region : model.regions()) {
|
||||
if (!excludedRegions.contains(region.key()) && !region.pool(role).isEmpty()) {
|
||||
return "every " + role.label() + " template already hosts " + role.templateCap() + " adopted biomes";
|
||||
}
|
||||
}
|
||||
return "no region in this dimension accepts a " + role.label() + " biome";
|
||||
}
|
||||
|
||||
private IngestClimate regionClimate(IngestPackModel.RegionProfile region, BiomeRole role, Set<String> categories) {
|
||||
if (role != BiomeRole.LAND || categories.isEmpty()) {
|
||||
return region.climate();
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IngestTemplateCapTest {
|
||||
private static final String MOD = "modcaps";
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void caveTemplatesHostAtMostThreeModBiomesAndOverflowMovesToTheNextBestTemplate() throws Exception {
|
||||
ModIngestReport report = ingest(7, 0);
|
||||
|
||||
Map<String, Integer> templates = templateUses(report);
|
||||
assertEquals(Integer.valueOf(3), templates.get("caves/lush"));
|
||||
for (Map.Entry<String, Integer> template : templates.entrySet()) {
|
||||
assertTrue(template.toString(), template.getValue() <= 3);
|
||||
}
|
||||
for (int index = 0; index < 7; index++) {
|
||||
assertEquals(ModIngestTier.ADOPTED, ModIngestFixture.item(report, MOD + ":grotto_" + index).tier());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void landTemplatesHostAtMostFiveModBiomesAndOverflowMovesToTheNextBestTemplate() throws Exception {
|
||||
ModIngestReport report = ingest(0, 7);
|
||||
|
||||
Map<String, Integer> templates = templateUses(report);
|
||||
assertEquals(Integer.valueOf(5), templates.get("wet/marsh"));
|
||||
assertEquals(Integer.valueOf(2), templates.get("dry/plains"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modBiomeIsSkippedWithAReasonWhenEveryCompatibleTemplateIsFull() throws Exception {
|
||||
ModIngestReport report = ingest(10, 0);
|
||||
|
||||
int adopted = 0;
|
||||
List<ModIngestItem> skipped = new ArrayList<>();
|
||||
for (int index = 0; index < 10; index++) {
|
||||
ModIngestItem item = ModIngestFixture.item(report, MOD + ":grotto_" + index);
|
||||
if (item.tier() == ModIngestTier.ADOPTED) {
|
||||
adopted++;
|
||||
} else {
|
||||
skipped.add(item);
|
||||
}
|
||||
}
|
||||
assertEquals(9, adopted);
|
||||
assertEquals(1, skipped.size());
|
||||
assertEquals(ModIngestTier.SKIPPED, skipped.getFirst().tier());
|
||||
assertTrue(skipped.getFirst().reason(), skipped.getFirst().reason().contains("already hosts 3 adopted biomes"));
|
||||
}
|
||||
|
||||
private ModIngestReport ingest(int caves, int marshes) throws IOException {
|
||||
Path root = folder.newFolder().toPath();
|
||||
Path source = root.resolve("source");
|
||||
ModIngestFixture.write(source, "dimensions/overworld.json", """
|
||||
{"name": "Overworld", "environment": "NORMAL", "regions": ["wet", "dry"], "dimensionHeight": {"min": -64, "max": 320}}
|
||||
""");
|
||||
ModIngestFixture.write(source, "regions/wet.json", """
|
||||
{"name": "Wet", "landBiomes": ["wet/marsh"], "caveBiomes": ["caves/lush"]}
|
||||
""");
|
||||
ModIngestFixture.write(source, "regions/dry.json", """
|
||||
{"name": "Dry", "landBiomes": ["dry/plains"], "caveBiomes": ["caves/drip", "caves/sand"]}
|
||||
""");
|
||||
biome(source, "wet/marsh", "minecraft:swamp");
|
||||
biome(source, "dry/plains", "minecraft:plains");
|
||||
biome(source, "caves/lush", "minecraft:lush_caves");
|
||||
biome(source, "caves/drip", "minecraft:dripstone_caves");
|
||||
biome(source, "caves/sand", "minecraft:dripstone_caves");
|
||||
Path target = root.resolve("packs").resolve("capped");
|
||||
return ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(caves, marshes), Set.of(MOD), Set.of(), false));
|
||||
}
|
||||
|
||||
private static Map<String, Integer> templateUses(ModIngestReport report) {
|
||||
Map<String, Integer> uses = new TreeMap<>();
|
||||
for (ModIngestItem item : report.items()) {
|
||||
if (item.kind().equals("biome") && item.tier() == ModIngestTier.ADOPTED) {
|
||||
String reason = item.reason();
|
||||
uses.merge(reason.substring(reason.indexOf("from template ") + "from template ".length()), 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
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(int caves, int marshes) {
|
||||
List<CatalogBiome> biomes = new ArrayList<>();
|
||||
biomes.add(biome("minecraft:swamp", 0.8F, 0.9F, List.of("c:is_swamp")));
|
||||
biomes.add(biome("minecraft:plains", 0.8F, 0.4F, List.of("c:is_plains")));
|
||||
biomes.add(biome("minecraft:lush_caves", 0.5F, 0.5F, List.of("c:is_cave", "c:is_lush")));
|
||||
biomes.add(biome("minecraft:dripstone_caves", 2.0F, 0.0F, List.of("c:is_cave")));
|
||||
for (int index = 0; index < caves; index++) {
|
||||
biomes.add(biome(MOD + ":grotto_" + index, 0.5F, 0.5F, List.of("c:is_cave", "c:is_lush")));
|
||||
}
|
||||
for (int index = 0; index < marshes; index++) {
|
||||
biomes.add(biome(MOD + ":marsh_" + index, 0.8F, 0.9F, List.of("c:is_swamp")));
|
||||
}
|
||||
return new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1000L, "fingerprint-caps",
|
||||
List.of(new CatalogNamespace(MOD, "Mod Caps", "1.0.0")), biomes, List.of(), List.of(),
|
||||
List.of(new CatalogDimensionType("minecraft:overworld", -64, 384, 384, null)), List.of(), Map.of());
|
||||
}
|
||||
|
||||
private static CatalogBiome biome(String key, float temperature, float downfall, List<String> categoryTags) {
|
||||
List<List<String>> steps = new ArrayList<>(11);
|
||||
for (int index = 0; index < 11; index++) {
|
||||
steps.add(List.of());
|
||||
}
|
||||
List<String> tags = new ArrayList<>(categoryTags);
|
||||
tags.add("minecraft:is_overworld");
|
||||
return new CatalogBiome(key, temperature, downfall, true, tags, steps, List.of(), null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user