mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
Added exact imported feature denial
- importedFeatures gains disabledExact, which denies normalized complete placed feature keys without matching key families - Mod ingest puts converted feature keys in disabledExact, so converting mod:ore_tin no longer suppresses mod:ore_tin_large - Mod ingest keeps vanilla and unselected mod features generating when the source pack already ran importedFeatures, and otherwise disables vanilla plus every unselected mod namespace that has placed features - Studio schema digest updated for the new field
This commit is contained in:
1 parent
191d9e88e6
commit
a3f607e264
8 files changed
+159
-13
No files matched your search
@@ -37,18 +37,26 @@ final class ImportedFeatureWriter {
|
||||
private ImportedFeatureWriter() {
|
||||
}
|
||||
|
||||
static IrisImportedFeatureControl write(JsonObject dimension, Set<String> nativeKeys) {
|
||||
static IrisImportedFeatureControl write(JsonObject dimension, Set<String> nativeKeys, Set<String> unselectedNamespaces) {
|
||||
JsonElement existing = dimension.get("importedFeatures");
|
||||
JsonObject imported = existing != null && existing.isJsonObject() ? existing.getAsJsonObject() : new JsonObject();
|
||||
Set<String> disabled = new LinkedHashSet<>(IngestJson.strings(imported.get("disabled")));
|
||||
disabled.add(VANILLA_PREFIX);
|
||||
disabled.addAll(nativeKeys);
|
||||
if (!enabled(imported)) {
|
||||
disabled.add(VANILLA_PREFIX);
|
||||
for (String namespace : unselectedNamespaces) {
|
||||
disabled.add(namespace + ":");
|
||||
}
|
||||
}
|
||||
Set<String> disabledExact = new LinkedHashSet<>(IngestJson.strings(imported.get("disabledExact")));
|
||||
disabledExact.addAll(nativeKeys);
|
||||
imported.addProperty("enabled", true);
|
||||
imported.add("disabled", IngestJson.stringArray(disabled));
|
||||
imported.add("disabledExact", IngestJson.stringArray(disabledExact));
|
||||
dimension.add("importedFeatures", imported);
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl();
|
||||
control.setEnabled(true);
|
||||
control.setDisabled(new KList<>(disabled));
|
||||
control.setDisabledExact(new KList<>(disabledExact));
|
||||
control.setSteps(steps(imported.get("steps")));
|
||||
control.setDisabledSteps(steps(imported.get("disabledSteps")));
|
||||
return control;
|
||||
@@ -90,9 +98,19 @@ final class ImportedFeatureWriter {
|
||||
return "suppressed by importedFeatures.disabled entry " + entry;
|
||||
}
|
||||
}
|
||||
for (String entry : control.getDisabledExact()) {
|
||||
if (entry.trim().equalsIgnoreCase(key.trim())) {
|
||||
return "suppressed by importedFeatures.disabledExact entry " + entry;
|
||||
}
|
||||
}
|
||||
return "suppressed by importedFeatures";
|
||||
}
|
||||
|
||||
private static boolean enabled(JsonObject imported) {
|
||||
JsonElement value = imported.get("enabled");
|
||||
return value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isBoolean() && value.getAsBoolean();
|
||||
}
|
||||
|
||||
private static KList<IrisDecorationStep> steps(JsonElement value) {
|
||||
KList<IrisDecorationStep> steps = new KList<>();
|
||||
for (String name : IngestJson.strings(value)) {
|
||||
|
||||
@@ -83,7 +83,8 @@ public final class ModIngester {
|
||||
.adopt(modBiomes(index, namespaces), biomeItems);
|
||||
List<ModIngestItem> featureItems = features.apply(adopted);
|
||||
JsonObject dimension = pack.edit(model.dimensionPath());
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, features.nativeKeys());
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, features.nativeKeys(),
|
||||
unselectedFeatureNamespaces(request.catalog(), namespaces));
|
||||
featureItems = ImportedFeatureWriter.applySuppression(featureItems, features.plans(), control);
|
||||
Set<String> hostBiomes = new TreeSet<>(adopted.keySet());
|
||||
hostBiomes.addAll(model.reachableBiomes().values());
|
||||
@@ -124,6 +125,16 @@ public final class ModIngester {
|
||||
return selected;
|
||||
}
|
||||
|
||||
private static Set<String> unselectedFeatureNamespaces(WorldgenCatalog catalog, Set<String> selected) {
|
||||
Set<String> unselected = new TreeSet<>();
|
||||
for (CatalogPlacedFeature feature : catalog.placedFeatures()) {
|
||||
unselected.add(WorldgenCatalogIndex.namespaceOf(feature.key()));
|
||||
}
|
||||
unselected.removeAll(selected);
|
||||
unselected.remove(ImportedFeatureWriter.VANILLA_PREFIX.substring(0, ImportedFeatureWriter.VANILLA_PREFIX.length() - 1));
|
||||
return unselected;
|
||||
}
|
||||
|
||||
private static List<CatalogBiome> modBiomes(WorldgenCatalogIndex index, Set<String> namespaces) {
|
||||
List<CatalogBiome> biomes = new ArrayList<>();
|
||||
for (String namespace : namespaces) {
|
||||
|
||||
+15
-2
@@ -29,12 +29,13 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Description("Controls native vanilla, mod, and ingested datapack PLACED FEATURE generation for this dimension (set as the dimension's 'importedFeatures' field). Placed features are ores, trees, plants, springs, geodes and every other decoration entry a biome declares. This is OFF by default: with 'enabled' false Iris generates exactly the terrain it always has and no native feature runs. With it true, every placed feature the biome's vanilla derivative declares runs over Iris terrain, in vanilla step order, on the same worldgen thread vanilla uses. Carvers are never imported - Iris has no NoiseGeneratorSettings, so there is nothing for a carver to carve against. Family matching on 'disabled' uses namespace, slash, or underscore boundaries, so 'minecraft:ore' covers every vanilla ore feature without matching unrelated names.")
|
||||
@Description("Controls native vanilla, mod, and ingested datapack PLACED FEATURE generation for this dimension (set as the dimension's 'importedFeatures' field). Placed features are ores, trees, plants, springs, geodes and every other decoration entry a biome declares. This is OFF by default: with 'enabled' false Iris generates exactly the terrain it always has and no native feature runs. With it true, every placed feature the biome's vanilla derivative declares runs over Iris terrain, in vanilla step order, on the same worldgen thread vanilla uses. Carvers are never imported - Iris has no NoiseGeneratorSettings, so there is nothing for a carver to carve against. Family matching on 'disabled' uses namespace, slash, or underscore boundaries, so 'minecraft:ore' covers every vanilla ore feature without matching unrelated names, while 'disabledExact' compares normalized complete keys only.")
|
||||
@Data
|
||||
public class IrisImportedFeatureControl implements NativeImportedFeatureControl {
|
||||
@Description("Master switch. False (the default) means no native placed feature generates and chunk output is identical to a pack without this block at all. True runs the vanilla decoration feature pass over Iris terrain.")
|
||||
@@ -44,6 +45,10 @@ public class IrisImportedFeatureControl implements NativeImportedFeatureControl
|
||||
@Description("Placed feature keys to deny explicitly, e.g. 'minecraft:ore_diamond'. A namespace:path prefix also matches, so 'minecraft:ore' denies every vanilla ore and 'minecraft:trees' denies every tree placement. Every key not matched here generates while 'enabled' is true.")
|
||||
private KList<String> disabled = new KList<>();
|
||||
|
||||
@ArrayType(type = String.class, min = 1)
|
||||
@Description("Exact placed feature keys to deny after trimming and case normalization. Unlike 'disabled', entries never match key families, so 'minecraft:ore_iron' does not disable 'minecraft:ore_iron_small'.")
|
||||
private KList<String> disabledExact = new KList<>();
|
||||
|
||||
@ArrayType(type = IrisDecorationStep.class, min = 1)
|
||||
@Description("Restrict feature generation to these decoration steps only. Empty (the default) means every step is eligible. Use this to import ores without importing vegetation: set it to UNDERGROUND_ORES.")
|
||||
private KList<IrisDecorationStep> steps = new KList<>();
|
||||
@@ -104,6 +109,14 @@ public class IrisImportedFeatureControl implements NativeImportedFeatureControl
|
||||
return NativeFeatureGenerationStatus.DISABLED_BY_PACK;
|
||||
}
|
||||
}
|
||||
KList<String> deniedExactKeys = Objects.requireNonNull(
|
||||
disabledExact, "importedFeatures.disabledExact must not be null");
|
||||
String normalizedKey = placedFeatureKey.trim().toLowerCase(Locale.ROOT);
|
||||
for (String entry : deniedExactKeys) {
|
||||
if (entry != null && entry.trim().toLowerCase(Locale.ROOT).equals(normalizedKey)) {
|
||||
return NativeFeatureGenerationStatus.DISABLED_BY_PACK;
|
||||
}
|
||||
}
|
||||
return NativeFeatureGenerationStatus.GENERATE_NATIVE;
|
||||
}
|
||||
@Override
|
||||
@@ -113,7 +126,7 @@ public class IrisImportedFeatureControl implements NativeImportedFeatureControl
|
||||
|
||||
@Override
|
||||
public boolean hasFeatureFilter() {
|
||||
return disabled != null && !disabled.isEmpty();
|
||||
return (disabled != null && !disabled.isEmpty()) || (disabledExact != null && !disabledExact.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.structure.nativegen.IrisImportedFeatureControl;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ImportedFeatureWriterTest {
|
||||
@Test
|
||||
public void sourceThatAlreadyRanImportedFeaturesKeepsVanillaAndOtherModsGenerating() {
|
||||
JsonObject dimension = dimension("{\"importedFeatures\": {\"enabled\": true, \"disabled\": [\"minecraft:ore_diamond\"]}}");
|
||||
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"));
|
||||
|
||||
JsonObject imported = dimension.getAsJsonObject("importedFeatures");
|
||||
assertEquals(List.of("minecraft:ore_diamond"), ModIngestFixture.strings(imported.get("disabled")));
|
||||
assertEquals(List.of("examplemod:ore_tin"), ModIngestFixture.strings(imported.get("disabledExact")));
|
||||
assertTrue(control.shouldGenerate("minecraft:ore_iron"));
|
||||
assertTrue(control.shouldGenerate("othermod:daisies"));
|
||||
assertFalse(control.shouldGenerate("minecraft:ore_diamond"));
|
||||
assertFalse(control.shouldGenerate("examplemod:ore_tin"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceWithoutImportedFeaturesOnlyRunsTheSelectedModsPassthrough() {
|
||||
JsonObject dimension = dimension("{}");
|
||||
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"));
|
||||
|
||||
JsonObject imported = dimension.getAsJsonObject("importedFeatures");
|
||||
assertTrue(imported.get("enabled").getAsBoolean());
|
||||
assertEquals(List.of("minecraft:", "othermod:"), ModIngestFixture.strings(imported.get("disabled")));
|
||||
assertFalse(control.shouldGenerate("minecraft:ore_iron"));
|
||||
assertFalse(control.shouldGenerate("othermod:daisies"));
|
||||
assertFalse(control.shouldGenerate("examplemod:ore_tin"));
|
||||
assertTrue(control.shouldGenerate("examplemod:ore_tin_large"));
|
||||
assertTrue(control.shouldGenerate("examplemod:maple_trees"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingExactEntriesAreKept() {
|
||||
JsonObject dimension = dimension("{\"importedFeatures\": {\"enabled\": false, \"disabledExact\": [\"examplemod:old\"]}}");
|
||||
|
||||
ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of());
|
||||
|
||||
assertEquals(List.of("examplemod:old", "examplemod:ore_tin"),
|
||||
ModIngestFixture.strings(dimension.getAsJsonObject("importedFeatures").get("disabledExact")));
|
||||
}
|
||||
|
||||
private static JsonObject dimension(String json) {
|
||||
return JsonParser.parseString(json).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
@@ -155,20 +155,21 @@ public class ModIngesterFeatureTest {
|
||||
assertEquals(ModIngestTier.PASSTHROUGH, weird.tier());
|
||||
assertTrue(weird.reason(), weird.reason().contains("examplemod:custom_stone"));
|
||||
assertTrue(imported.get("enabled").getAsBoolean());
|
||||
assertEquals(List.of("minecraft:ore_diamond", "minecraft:", "examplemod:bluebell_patch", "examplemod:glow_flowers",
|
||||
"examplemod:ore_ruby", "examplemod:ore_sapphire", "examplemod:ore_tin"),
|
||||
assertEquals(List.of("minecraft:ore_diamond", "minecraft:", "othermod:"),
|
||||
ModIngestFixture.strings(imported.get("disabled")));
|
||||
assertEquals(List.of("examplemod:bluebell_patch", "examplemod:glow_flowers",
|
||||
"examplemod:ore_ruby", "examplemod:ore_sapphire", "examplemod:ore_tin"),
|
||||
ModIngestFixture.strings(imported.get("disabledExact")));
|
||||
assertEquals(List.of("LAKES"), ModIngestFixture.strings(imported.get("disabledSteps")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void featuresWithoutHostsOrShadowedByDisabledPrefixesAreSkipped() {
|
||||
public void featuresWithoutHostsAreSkippedAndConvertedKeysNeverShadowTheirFamily() {
|
||||
ModIngestItem nether = ModIngestFixture.item(report, "examplemod:nether_ore");
|
||||
ModIngestItem buried = ModIngestFixture.item(report, "examplemod:ore_tin_buried");
|
||||
|
||||
assertEquals(ModIngestTier.SKIPPED, nether.tier());
|
||||
assertEquals(ModIngestTier.SKIPPED, buried.tier());
|
||||
assertTrue(buried.reason(), buried.reason().contains("examplemod:ore_tin"));
|
||||
assertEquals(ModIngestTier.PASSTHROUGH, buried.tier());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ModIngesterPackTest {
|
||||
assertFalse(modified.contains("biomes/unused/orphan.json"));
|
||||
assertEquals(report.items().size(), manifest.getAsJsonArray("items").size());
|
||||
assertTrue(report.summaryLines().toString(),
|
||||
report.summaryLines().contains("examplemod: 5 native, 6 adopted, 2 passthrough, 7 skipped"));
|
||||
report.summaryLines().contains("examplemod: 5 native, 6 adopted, 3 passthrough, 6 skipped"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SchemaBuilderIdentityTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "2db2ba19e3466b7c686adca580943c1cc832bf0b44a0568e8db86a920116cfc0";
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "5841ff4a1ad2813e4ad45f941fd40e5083a6b1b29a61c522df9efa12eb04f8c0";
|
||||
|
||||
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
|
||||
IrisDimension.class,
|
||||
|
||||
+25
@@ -86,6 +86,31 @@ public class IrisImportedFeatureControlTest {
|
||||
control.resolve("minecraft:ore_diamond", IrisDecorationStep.UNDERGROUND_ORES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledExactDeniesOnlyTheCompleteKey() {
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
.setEnabled(true)
|
||||
.setDisabledExact(keys(" ExampleMod:Ore_Tin "));
|
||||
|
||||
assertFalse(control.shouldGenerate("examplemod:ore_tin"));
|
||||
assertTrue(control.shouldGenerate("examplemod:ore_tin_large"));
|
||||
assertTrue(control.shouldGenerate("examplemod:ore_tin/deep"));
|
||||
assertTrue(control.hasFeatureFilter());
|
||||
assertEquals(NativeFeatureGenerationStatus.DISABLED_BY_PACK,
|
||||
control.resolve("examplemod:ore_tin", IrisDecorationStep.UNDERGROUND_ORES));
|
||||
assertEquals(NativeFeatureGenerationStatus.GENERATE_NATIVE,
|
||||
control.resolve("examplemod:ore_tin_large", IrisDecorationStep.UNDERGROUND_ORES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullDisabledExactIsRejected() {
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
.setEnabled(true)
|
||||
.setDisabledExact(null);
|
||||
|
||||
assertThrows(NullPointerException.class, () -> control.shouldGenerate("examplemod:ore_tin"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stepAllowListNarrowsGenerationToListedStepsOnly() {
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
|
||||
Reference in new issue
Block a user