mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-28 23:10:32 +00:00
Ordered converted ore hosts
- The worldgen catalog (format 2) records the members of every block tag referenced by a captured placed feature, so ore targets using mod block tags convert instead of passing through - Ore conversion supports noise_based_count and noise_threshold_count placement counts - Converted ores that produce another converted ore's host block are written ahead of it, so an ore whose host comes from a sibling ore finds that host during the deposit stage
This commit is contained in:
1 parent
d5796b275e
commit
965e19801d
20 files changed
+445
-45
No files matched your search
@@ -27,6 +27,8 @@ import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -37,6 +39,8 @@ import java.util.TreeSet;
|
||||
|
||||
final class ModFeatureIngester {
|
||||
private static final String VANILLA_NAMESPACE = "minecraft";
|
||||
private static final String REPLACEABLE_BLOCKS = "replaceableBlocks";
|
||||
private static final String PALETTE = "palette";
|
||||
|
||||
private final IngestPackModel model;
|
||||
private final WorldgenCatalogIndex index;
|
||||
@@ -96,7 +100,7 @@ final class ModFeatureIngester {
|
||||
universe.addAll(model.reachableBiomes().values());
|
||||
Set<String> caveKeys = caveKeys(adopted);
|
||||
List<ModIngestItem> items = new ArrayList<>(plans.size());
|
||||
for (FeaturePlan plan : plans.values()) {
|
||||
for (FeaturePlan plan : hostProducersFirst(plans.values())) {
|
||||
String namespace = WorldgenCatalogIndex.namespaceOf(plan.key());
|
||||
Set<String> hosts = new TreeSet<>(plan.declaring());
|
||||
hosts.retainAll(universe);
|
||||
@@ -128,6 +132,57 @@ final class ModFeatureIngester {
|
||||
return items;
|
||||
}
|
||||
|
||||
static List<FeaturePlan> hostProducersFirst(Collection<FeaturePlan> plans) {
|
||||
List<FeaturePlan> pending = new ArrayList<>(plans);
|
||||
List<FeaturePlan> ordered = new ArrayList<>(pending.size());
|
||||
while (!pending.isEmpty()) {
|
||||
FeaturePlan next = pending.getFirst();
|
||||
for (FeaturePlan candidate : pending) {
|
||||
if (!waitsForHostProducer(candidate, pending)) {
|
||||
next = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pending.remove(next);
|
||||
ordered.add(next);
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
private static boolean waitsForHostProducer(FeaturePlan consumer, List<FeaturePlan> pending) {
|
||||
Set<String> hosts = depositBlocks(consumer, REPLACEABLE_BLOCKS);
|
||||
if (hosts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (FeaturePlan producer : pending) {
|
||||
if (producer != consumer && !Collections.disjoint(depositBlocks(producer, PALETTE), hosts)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Set<String> depositBlocks(FeaturePlan plan, String field) {
|
||||
if (plan.deposits() == null) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<String> blocks = new TreeSet<>();
|
||||
for (JsonObject deposit : plan.deposits()) {
|
||||
JsonElement values = deposit.get(field);
|
||||
if (values == null || !values.isJsonArray()) {
|
||||
continue;
|
||||
}
|
||||
for (JsonElement value : values.getAsJsonArray()) {
|
||||
if (value.isJsonPrimitive()) {
|
||||
blocks.add(value.getAsString());
|
||||
} else if (value.isJsonObject() && value.getAsJsonObject().has("block")) {
|
||||
blocks.add(value.getAsJsonObject().get("block").getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private boolean treeInPackVanillaBiomes(FeaturePlan plan) {
|
||||
Optional<CatalogPlacedFeature> feature = index.placedFeature(plan.key());
|
||||
if (feature.isEmpty() || !trees.isTree(feature.get())) {
|
||||
@@ -149,7 +204,8 @@ final class ModFeatureIngester {
|
||||
CatalogPlacedFeature placed = feature.get();
|
||||
try {
|
||||
if (OreDepositConverter.handles(placed)) {
|
||||
List<JsonObject> deposits = OreDepositConverter.convert(placed, model.verticalBounds(), model.packMinY());
|
||||
List<JsonObject> deposits = OreDepositConverter.convert(placed, model.verticalBounds(), model.packMinY(),
|
||||
index.catalog().blockTags());
|
||||
return new FeaturePlan(key, declaring, steps, deposits, null, "");
|
||||
}
|
||||
if (PatchDecoratorConverter.handles(placed)) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -42,7 +43,8 @@ final class OreDepositConverter {
|
||||
return ORE_TYPES.contains(VanillaJson.namespaced(feature.featureType()));
|
||||
}
|
||||
|
||||
static List<JsonObject> convert(CatalogPlacedFeature feature, VanillaHeights.VerticalBounds bounds, int packMinY) throws IngestConversionException {
|
||||
static List<JsonObject> convert(CatalogPlacedFeature feature, VanillaHeights.VerticalBounds bounds, int packMinY,
|
||||
Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
JsonObject config = VanillaJson.featureConfig(feature.config());
|
||||
Placement placement = placement(VanillaJson.placement(feature.placement()), bounds);
|
||||
int size = VanillaJson.integer(config.get("size"), "ore size");
|
||||
@@ -52,7 +54,7 @@ final class OreDepositConverter {
|
||||
double discard = VanillaJson.number(config, "discard_chance_on_air_exposure", 0D, "ore");
|
||||
String shape = VanillaJson.namespaced(feature.featureType()).equals("minecraft:scattered_ore") ? "VANILLA_SCATTERED" : "VANILLA_ELLIPSOID";
|
||||
List<JsonObject> deposits = new ArrayList<>();
|
||||
for (Target target : targets(VanillaJson.array(config.get("targets"), "ore targets"))) {
|
||||
for (Target target : targets(VanillaJson.array(config.get("targets"), "ore targets"), blockTags)) {
|
||||
JsonObject deposit = new JsonObject();
|
||||
deposit.addProperty("minHeight", placement.band().min() - packMinY);
|
||||
deposit.addProperty("maxHeight", placement.band().max() - packMinY);
|
||||
@@ -84,13 +86,13 @@ final class OreDepositConverter {
|
||||
return deposits;
|
||||
}
|
||||
|
||||
private static List<Target> targets(JsonArray targets) throws IngestConversionException {
|
||||
private static List<Target> targets(JsonArray targets, Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
List<Target> result = new ArrayList<>(targets.size());
|
||||
SortedSet<String> claimed = new TreeSet<>();
|
||||
for (JsonElement entry : targets) {
|
||||
JsonObject target = VanillaJson.object(entry, "ore target");
|
||||
JsonObject block = VanillaBlockStates.irisBlock(target.get("state"));
|
||||
VanillaRuleTests.HostSet hosts = VanillaRuleTests.hosts(target.get("target"));
|
||||
VanillaRuleTests.HostSet hosts = VanillaRuleTests.hosts(target.get("target"), blockTags);
|
||||
if (hosts.any()) {
|
||||
if (!claimed.isEmpty()) {
|
||||
throw new IngestConversionException("ore target accepting any block follows restricted targets");
|
||||
@@ -125,6 +127,11 @@ final class OreDepositConverter {
|
||||
}
|
||||
case "minecraft:rarity_filter" -> spawnChance /= Math.max(1D, VanillaJson.number(object.get("chance"), "rarity_filter.chance"));
|
||||
case "minecraft:random_chance" -> spawnChance *= Math.max(0D, Math.min(1D, VanillaJson.number(object.get("chance"), "random_chance.chance")));
|
||||
case "minecraft:noise_based_count", "minecraft:noise_threshold_count" -> {
|
||||
double mean = VanillaPlacementChain.noiseCountMean(object);
|
||||
minCount = Math.min(MAX_PER_CHUNK, Math.round(minCount * mean));
|
||||
maxCount = Math.min(MAX_PER_CHUNK, Math.round(maxCount * mean));
|
||||
}
|
||||
case "minecraft:in_square", "minecraft:biome" -> {
|
||||
}
|
||||
case "minecraft:height_range" -> band = VanillaHeights.band(object.get("height"), bounds);
|
||||
|
||||
@@ -166,6 +166,14 @@ final class VanillaPlacementChain {
|
||||
return values.get(0).getAsInt() == 0 && values.get(1).getAsInt() == -1 && values.get(2).getAsInt() == 0;
|
||||
}
|
||||
|
||||
static double noiseCountMean(JsonObject object) throws IngestConversionException {
|
||||
if (VanillaJson.type(object, "type").equals("minecraft:noise_threshold_count")) {
|
||||
return (VanillaJson.number(object.get("below_noise"), "noise_threshold_count.below_noise")
|
||||
+ VanillaJson.number(object.get("above_noise"), "noise_threshold_count.above_noise")) / 2D;
|
||||
}
|
||||
return noiseBasedMean(object);
|
||||
}
|
||||
|
||||
private static double noiseBasedMean(JsonObject object) throws IngestConversionException {
|
||||
double ratio = VanillaJson.number(object.get("noise_to_count_ratio"), "noise_based_count.noise_to_count_ratio");
|
||||
double offset = VanillaJson.number(object, "noise_offset", 0D, "noise_based_count");
|
||||
|
||||
@@ -42,32 +42,32 @@ final class VanillaRuleTests {
|
||||
private VanillaRuleTests() {
|
||||
}
|
||||
|
||||
static HostSet hosts(JsonElement ruleTest) throws IngestConversionException {
|
||||
static HostSet hosts(JsonElement ruleTest, Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
JsonObject object = VanillaJson.object(ruleTest, "ore target predicate");
|
||||
String type = VanillaJson.type(object, "predicate_type");
|
||||
return switch (type) {
|
||||
case "minecraft:always_true" -> HostSet.ANY;
|
||||
case "minecraft:tag_match" -> tag(VanillaJson.namespaced(VanillaJson.string(object.get("tag"), "tag_match.tag")));
|
||||
case "minecraft:tag_match" -> tag(VanillaJson.namespaced(VanillaJson.string(object.get("tag"), "tag_match.tag")), blockTags);
|
||||
case "minecraft:block_match" -> HostSet.of(Set.of(VanillaJson.namespaced(VanillaJson.string(object.get("block"), "block_match.block"))));
|
||||
case "minecraft:blockstate_match" -> HostSet.of(Set.of(VanillaBlockStates.irisBlock(object.get("block_state")).get("block").getAsString()));
|
||||
case "minecraft:any_of" -> anyOf(rules(object));
|
||||
case "minecraft:all_of" -> allOf(rules(object));
|
||||
case "minecraft:any_of" -> anyOf(rules(object), blockTags);
|
||||
case "minecraft:all_of" -> allOf(rules(object), blockTags);
|
||||
default -> throw new IngestConversionException("unsupported ore target predicate " + (type.isEmpty() ? object.toString() : type));
|
||||
};
|
||||
}
|
||||
|
||||
private static HostSet tag(String tag) throws IngestConversionException {
|
||||
List<String> blocks = BLOCK_TAGS.get(tag);
|
||||
private static HostSet tag(String tag, Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
List<String> blocks = blockTags.containsKey(tag) ? blockTags.get(tag) : BLOCK_TAGS.get(tag);
|
||||
if (blocks == null) {
|
||||
throw new IngestConversionException("ore target uses block tag " + tag + " whose members are not known offline");
|
||||
throw new IngestConversionException("ore target uses block tag " + tag + " whose members the catalog did not capture");
|
||||
}
|
||||
return HostSet.of(Set.copyOf(blocks));
|
||||
}
|
||||
|
||||
private static HostSet anyOf(List<JsonElement> rules) throws IngestConversionException {
|
||||
private static HostSet anyOf(List<JsonElement> rules, Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
SortedSet<String> union = new TreeSet<>();
|
||||
for (JsonElement rule : rules) {
|
||||
HostSet hosts = hosts(rule);
|
||||
HostSet hosts = hosts(rule, blockTags);
|
||||
if (hosts.any()) {
|
||||
return HostSet.ANY;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ final class VanillaRuleTests {
|
||||
return HostSet.of(union);
|
||||
}
|
||||
|
||||
private static HostSet allOf(List<JsonElement> rules) throws IngestConversionException {
|
||||
private static HostSet allOf(List<JsonElement> rules, Map<String, List<String>> blockTags) throws IngestConversionException {
|
||||
HostSet positive = null;
|
||||
SortedSet<String> negative = new TreeSet<>();
|
||||
for (JsonElement rule : rules) {
|
||||
@@ -86,14 +86,14 @@ final class VanillaRuleTests {
|
||||
continue;
|
||||
}
|
||||
if (type.equals("minecraft:not")) {
|
||||
HostSet excluded = hosts(ruleObject.get("rule"));
|
||||
HostSet excluded = hosts(ruleObject.get("rule"), blockTags);
|
||||
if (excluded.any()) {
|
||||
throw new IngestConversionException("ore target predicate never matches");
|
||||
}
|
||||
negative.addAll(excluded.blocks());
|
||||
continue;
|
||||
}
|
||||
positive = intersect(positive, hosts(ruleObject));
|
||||
positive = intersect(positive, hosts(ruleObject, blockTags));
|
||||
}
|
||||
if (positive == null) {
|
||||
throw new IngestConversionException("ore target all_of has no block constraint");
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package art.arcane.iris.pack.ingest.catalog;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public record WorldgenCatalog(
|
||||
@@ -33,8 +34,9 @@ public record WorldgenCatalog(
|
||||
List<CatalogPlacedFeature> placedFeatures,
|
||||
List<CatalogStructure> structures,
|
||||
List<CatalogDimensionType> dimensionTypes,
|
||||
List<CatalogLevelStem> levelStems) {
|
||||
public static final int FORMAT = 1;
|
||||
List<CatalogLevelStem> levelStems,
|
||||
Map<String, List<String>> blockTags) {
|
||||
public static final int FORMAT = 2;
|
||||
|
||||
public WorldgenCatalog {
|
||||
Objects.requireNonNull(minecraftVersion, "minecraftVersion");
|
||||
@@ -46,5 +48,6 @@ public record WorldgenCatalog(
|
||||
structures = List.copyOf(Objects.requireNonNull(structures, "structures"));
|
||||
dimensionTypes = List.copyOf(Objects.requireNonNull(dimensionTypes, "dimensionTypes"));
|
||||
levelStems = List.copyOf(Objects.requireNonNull(levelStems, "levelStems"));
|
||||
blockTags = Map.copyOf(Objects.requireNonNull(blockTags, "blockTags"));
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,10 @@ public final class WorldgenCatalogIndex {
|
||||
return Optional.ofNullable(dimensionTypes.get(key));
|
||||
}
|
||||
|
||||
public Optional<List<String>> blockTag(String key) {
|
||||
return Optional.ofNullable(catalog.blockTags().get(key));
|
||||
}
|
||||
|
||||
public Optional<CatalogLevelStem> levelStem(String key) {
|
||||
return Optional.ofNullable(levelStems.get(key));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user