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:
Brian Neumann-Fopiano committed 2026-10-15 09:13:55 -04:00
1 parent 0ca33068f3
commit 523cc7e7be
3 files changed
+177 -8

No files matched your search

@@ -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();