diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/Configurator.java b/common/src/vanillin/java/dev/engine_room/vanillin/Configurator.java deleted file mode 100644 index 027b60a4d..000000000 --- a/common/src/vanillin/java/dev/engine_room/vanillin/Configurator.java +++ /dev/null @@ -1,82 +0,0 @@ -package dev.engine_room.vanillin; - -import java.util.HashMap; -import java.util.Map; - -import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer; -import dev.engine_room.flywheel.api.visualization.EntityVisualizer; -import dev.engine_room.flywheel.api.visualization.VisualizerRegistry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; - -public class Configurator { - public final Map, ConfiguredBlockEntity> blockEntities = new HashMap<>(); - public final Map, ConfiguredEntity> entities = new HashMap<>(); - - public void register(BlockEntityType type, BlockEntityVisualizer visualizer, boolean enabledByDefault) { - blockEntities.put(type, new ConfiguredBlockEntity<>(type, visualizer, enabledByDefault)); - } - - public void register(EntityType type, EntityVisualizer visualizer, boolean enabledByDefault) { - entities.put(type, new ConfiguredEntity<>(type, visualizer, enabledByDefault)); - } - - public static class ConfiguredBlockEntity { - public final BlockEntityType type; - public final BlockEntityVisualizer visualizer; - private final boolean enabledByDefault; - - private ConfiguredBlockEntity(BlockEntityType type, BlockEntityVisualizer visualizer, boolean enabledByDefault) { - this.type = type; - this.visualizer = visualizer; - this.enabledByDefault = enabledByDefault; - } - - public String configKey() { - return BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).toString(); - } - - public boolean enabledByDefault() { - return enabledByDefault; - } - - public void set(boolean enabled) { - if (enabled) { - VisualizerRegistry.setVisualizer(type, visualizer); - } else { - VisualizerRegistry.setVisualizer(type, null); - } - } - } - - public static class ConfiguredEntity { - public final EntityType type; - public final EntityVisualizer visualizer; - private final boolean enabledByDefault; - - private ConfiguredEntity(EntityType type, EntityVisualizer visualizer, boolean enabledByDefault) { - this.type = type; - this.visualizer = visualizer; - this.enabledByDefault = enabledByDefault; - } - - public String configKey() { - return BuiltInRegistries.ENTITY_TYPE.getKey(type).toString(); - } - - public boolean defaultEnabled() { - return enabledByDefault; - } - - public void set(boolean enabled) { - if (enabled) { - VisualizerRegistry.setVisualizer(type, visualizer); - } else { - VisualizerRegistry.setVisualizer(type, null); - } - } - } -} diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/VanillaVisuals.java b/common/src/vanillin/java/dev/engine_room/vanillin/VanillaVisuals.java index a24f19843..ae2618add 100644 --- a/common/src/vanillin/java/dev/engine_room/vanillin/VanillaVisuals.java +++ b/common/src/vanillin/java/dev/engine_room/vanillin/VanillaVisuals.java @@ -1,11 +1,9 @@ package dev.engine_room.vanillin; -import dev.engine_room.vanillin.visuals.BellVisual; -import dev.engine_room.vanillin.visuals.ChestVisual; -import dev.engine_room.vanillin.visuals.MinecartVisual; -import dev.engine_room.vanillin.visuals.ShulkerBoxVisual; -import dev.engine_room.vanillin.visuals.SignVisual; -import dev.engine_room.vanillin.visuals.TntMinecartVisual; +import dev.engine_room.vanillin.config.BlockEntityVisualizerBuilder; +import dev.engine_room.vanillin.config.Configurator; +import dev.engine_room.vanillin.config.EntityVisualizerBuilder; +import dev.engine_room.vanillin.visuals.*; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/BlockEntityVisualizerBuilder.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/BlockEntityVisualizerBuilder.java similarity index 98% rename from common/src/vanillin/java/dev/engine_room/vanillin/BlockEntityVisualizerBuilder.java rename to common/src/vanillin/java/dev/engine_room/vanillin/config/BlockEntityVisualizerBuilder.java index 8cc1e6611..8c8070999 100644 --- a/common/src/vanillin/java/dev/engine_room/vanillin/BlockEntityVisualizerBuilder.java +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/BlockEntityVisualizerBuilder.java @@ -1,4 +1,4 @@ -package dev.engine_room.vanillin; +package dev.engine_room.vanillin.config; import java.util.Objects; import java.util.function.Predicate; diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/config/Configurator.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/Configurator.java new file mode 100644 index 000000000..28e97af02 --- /dev/null +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/Configurator.java @@ -0,0 +1,154 @@ +package dev.engine_room.vanillin.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer; +import dev.engine_room.flywheel.api.visualization.EntityVisualizer; +import dev.engine_room.flywheel.api.visualization.VisualizerRegistry; +import dev.engine_room.vanillin.Vanillin; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; + +public class Configurator { + public final Map, ConfiguredBlockEntity> blockEntities = new HashMap<>(); + public final Map, ConfiguredEntity> entities = new HashMap<>(); + + public void register(BlockEntityType type, BlockEntityVisualizer visualizer, boolean enabledByDefault) { + blockEntities.put(type, new ConfiguredBlockEntity<>(type, visualizer, enabledByDefault)); + } + + public void register(EntityType type, EntityVisualizer visualizer, boolean enabledByDefault) { + entities.put(type, new ConfiguredEntity<>(type, visualizer, enabledByDefault)); + } + + public static abstract class ConfiguredVisual { + private final boolean enabledByDefault; + + protected ConfiguredVisual(boolean enabledByDefault) { + this.enabledByDefault = enabledByDefault; + } + + public void set(VisualConfigValue configValue, @Nullable List overrides) { + if (configValue == VisualConfigValue.DISABLE) { + disable(); + } else if (configValue == VisualConfigValue.FORCE_ENABLE) { + enable(); + maybeWarnEnabledDespiteOverrides(overrides); + } else if (configValue == VisualConfigValue.DEFAULT) { + if (disableAndWarnDueToOverrides(overrides)) { + disable(); + } else { + if (enabledByDefault) { + enable(); + } else { + disable(); + } + } + } + } + + private boolean disableAndWarnDueToOverrides(@Nullable List overrides) { + if (overrides == null || overrides.isEmpty()) { + return false; + } + + var modIds = disablingModIds(overrides); + + if (modIds.isEmpty()) { + return false; + } else { + Vanillin.CONFIG_LOGGER.warn("Disabling {} visual due to overrides from mods: {}", configKey(), String.join(", ", modIds)); + return true; + } + } + + private void maybeWarnEnabledDespiteOverrides(@Nullable List overrides) { + if (overrides == null || overrides.isEmpty()) { + return; + } + + var modIds = disablingModIds(overrides); + + if (!modIds.isEmpty()) { + Vanillin.CONFIG_LOGGER.warn("Enabling {} visual despite overrides from mods: {}", configKey(), String.join(", ", modIds)); + } + } + + public abstract String configKey(); + + protected abstract void enable(); + + protected abstract void disable(); + + private static List disablingModIds(List overrides) { + List out = new ArrayList<>(); + + for (VisualOverride override : overrides) { + if (override.value() == VisualOverrideValue.DISABLE) { + out.add(override.modId()); + } + } + return out; + } + } + + public static class ConfiguredBlockEntity extends ConfiguredVisual { + public final BlockEntityType type; + public final BlockEntityVisualizer visualizer; + + private ConfiguredBlockEntity(BlockEntityType type, BlockEntityVisualizer visualizer, boolean enabledByDefault) { + super(enabledByDefault); + this.type = type; + this.visualizer = visualizer; + } + + @Override + public String configKey() { + return BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).toString(); + } + + @Override + protected void enable() { + VisualizerRegistry.setVisualizer(type, visualizer); + } + + @Override + protected void disable() { + VisualizerRegistry.setVisualizer(type, null); + } + } + + public static class ConfiguredEntity extends ConfiguredVisual { + public final EntityType type; + public final EntityVisualizer visualizer; + + private ConfiguredEntity(EntityType type, EntityVisualizer visualizer, boolean enabledByDefault) { + super(enabledByDefault); + this.type = type; + this.visualizer = visualizer; + } + + @Override + public String configKey() { + return BuiltInRegistries.ENTITY_TYPE.getKey(type).toString(); + } + + @Override + protected void enable() { + VisualizerRegistry.setVisualizer(type, visualizer); + } + + @Override + protected void disable() { + VisualizerRegistry.setVisualizer(type, null); + } + } +} diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/EntityVisualizerBuilder.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/EntityVisualizerBuilder.java similarity index 98% rename from common/src/vanillin/java/dev/engine_room/vanillin/EntityVisualizerBuilder.java rename to common/src/vanillin/java/dev/engine_room/vanillin/config/EntityVisualizerBuilder.java index 1d3bb81a1..6e9a8aa54 100644 --- a/common/src/vanillin/java/dev/engine_room/vanillin/EntityVisualizerBuilder.java +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/EntityVisualizerBuilder.java @@ -1,4 +1,4 @@ -package dev.engine_room.vanillin; +package dev.engine_room.vanillin.config; import java.util.Objects; import java.util.function.Predicate; diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/config/ModOverrides.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/ModOverrides.java new file mode 100644 index 000000000..3bc6efa1e --- /dev/null +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/ModOverrides.java @@ -0,0 +1,15 @@ +package dev.engine_room.vanillin.config; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public record ModOverrides(Map> blockEntities, Map> entities) { + public ModOverrides(List blockEntities, List entities) { + this(sort(blockEntities), sort(entities)); + } + + public static Map> sort(List list) { + return list.stream().collect(Collectors.groupingBy(VisualOverride::name)); + } +} diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualConfigValue.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualConfigValue.java new file mode 100644 index 000000000..723c72b7a --- /dev/null +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualConfigValue.java @@ -0,0 +1,12 @@ +package dev.engine_room.vanillin.config; + +import com.google.gson.annotations.SerializedName; + +public enum VisualConfigValue { + @SerializedName("default") + DEFAULT, + @SerializedName("disable") + DISABLE, + @SerializedName("force_enable") + FORCE_ENABLE, +} diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverride.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverride.java new file mode 100644 index 000000000..85438f49b --- /dev/null +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverride.java @@ -0,0 +1,4 @@ +package dev.engine_room.vanillin.config; + +public record VisualOverride(String name, String modId, VisualOverrideValue value) { +} diff --git a/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverrideValue.java b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverrideValue.java new file mode 100644 index 000000000..333e060d7 --- /dev/null +++ b/common/src/vanillin/java/dev/engine_room/vanillin/config/VisualOverrideValue.java @@ -0,0 +1,19 @@ +package dev.engine_room.vanillin.config; + +import org.jetbrains.annotations.Nullable; + +public enum VisualOverrideValue { + DEFAULT, + DISABLE, + ; + + @Nullable + public static VisualOverrideValue parse(String string) { + if (string.equals("default")) { + return DEFAULT; + } else if (string.equals("disable")) { + return DISABLE; + } + return null; + } +} diff --git a/vanillinFabric/src/main/java/dev/engine_room/vanillin/FabricVanillinConfig.java b/vanillinFabric/src/main/java/dev/engine_room/vanillin/FabricVanillinConfig.java index 46c3a55ab..0cb5ef2bc 100644 --- a/vanillinFabric/src/main/java/dev/engine_room/vanillin/FabricVanillinConfig.java +++ b/vanillinFabric/src/main/java/dev/engine_room/vanillin/FabricVanillinConfig.java @@ -4,14 +4,24 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; +import dev.engine_room.vanillin.config.Configurator; +import dev.engine_room.vanillin.config.ModOverrides; +import dev.engine_room.vanillin.config.VisualConfigValue; +import dev.engine_room.vanillin.config.VisualOverride; +import dev.engine_room.vanillin.config.VisualOverrideValue; import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.CustomValue; +import net.fabricmc.loader.api.metadata.ModMetadata; public class FabricVanillinConfig { public static final Path PATH = FabricLoader.getInstance() @@ -21,10 +31,12 @@ public class FabricVanillinConfig { public static final FabricVanillinConfig INSTANCE = new FabricVanillinConfig(PATH.toFile()); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + public static final String VANILLIN_OVERRIDES = "vanillin:overrides"; private final File file; - private Config config; + private ModOverrides overrides; + private Config config = new Config(); public FabricVanillinConfig(File file) { this.file = file; @@ -36,23 +48,34 @@ public class FabricVanillinConfig { config = GSON.fromJson(reader, Config.class); } catch (Exception e) { Vanillin.CONFIG_LOGGER.warn("Could not load config from file '{}'", file.getAbsolutePath(), e); - config = new Config(); } } + + overrides = modOverrides(); } public void apply(Configurator configurator) { - for (Configurator.ConfiguredBlockEntity configuredBlockEntity : configurator.blockEntities.values()) { - boolean enabled = config.blockEntities.computeIfAbsent(configuredBlockEntity.configKey(), $ -> configuredBlockEntity.enabledByDefault()); - configuredBlockEntity.set(enabled); + var blockEntities = config.blockEntities; + var blockEntityOverrides = this.overrides.blockEntities(); + + for (Configurator.ConfiguredVisual configured : configurator.blockEntities.values()) { + apply(configured, blockEntities, blockEntityOverrides); } - for (Configurator.ConfiguredEntity configured : configurator.entities.values()) { - boolean enabled = config.entities.computeIfAbsent(configured.configKey(), $ -> configured.defaultEnabled()); - configured.set(enabled); + var entities = config.entities; + var entityOverrides = this.overrides.entities(); + for (Configurator.ConfiguredVisual configured : configurator.entities.values()) { + apply(configured, entities, entityOverrides); } } + private static void apply(Configurator.ConfiguredVisual configured, Map config, Map> overrides) { + var key = configured.configKey(); + var enabled = config.computeIfAbsent(key, $ -> VisualConfigValue.DEFAULT); + + configured.set(enabled, overrides.get(key)); + } + public void save() { try (FileWriter writer = new FileWriter(file)) { GSON.toJson(config, writer); @@ -61,16 +84,75 @@ public class FabricVanillinConfig { } } + public static ModOverrides modOverrides() { + var blockEntities = new ArrayList(); + var entities = new ArrayList(); + + for (ModContainer container : FabricLoader.getInstance().getAllMods()) { + ModMetadata meta = container.getMetadata(); + var modid = meta.getId(); + + if (meta.containsCustomValue(VANILLIN_OVERRIDES)) { + CustomValue overridesValue = meta.getCustomValue(VANILLIN_OVERRIDES); + + if (overridesValue.getType() != CustomValue.CvType.OBJECT) { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override options with an invalid value, ignoring", modid); + continue; + } + + var overrides = overridesValue.getAsObject(); + + readSection(blockEntities, modid, overrides, "block_entities", "block entity"); + readSection(entities, modid, overrides, "entities", "entity"); + } + } + + return new ModOverrides(blockEntities, entities); + } + + private static void readSection(List dst, String modid, CustomValue.CvObject overrides, String sectionName, String singular) { + if (!overrides.containsKey(sectionName)) { + return; + } + + var section = overrides.get(sectionName); + + if (section.getType() != CustomValue.CvType.OBJECT) { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} with an invalid value, ignoring", modid, sectionName); + return; + } + + for (Map.Entry entry : section.getAsObject()) { + var value = entry.getValue(); + var key = entry.getKey(); + if (value.getType() != CustomValue.CvType.STRING) { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} '{}' with an invalid value, ignoring", modid, singular, key); + continue; + } + + var valueString = value.getAsString(); + + var parsed = VisualOverrideValue.parse(valueString); + + if (parsed == null) { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} '{}' with an invalid value '{}', ignoring", modid, singular, key, valueString); + continue; + } + + dst.add(new VisualOverride(key, modid, parsed)); + } + } + public static class Config { @SerializedName("block_entities") - public Map blockEntities; - public Map entities; + public Map blockEntities; + public Map entities; public Config() { this(new HashMap<>(), new HashMap<>()); } - public Config(Map blockEntities, Map entities) { + public Config(Map blockEntities, Map entities) { this.blockEntities = blockEntities; this.entities = entities; } diff --git a/vanillinForge/src/main/java/dev/engine_room/vanillin/ForgeVanillinConfig.java b/vanillinForge/src/main/java/dev/engine_room/vanillin/ForgeVanillinConfig.java index e3567e41c..5504545c2 100644 --- a/vanillinForge/src/main/java/dev/engine_room/vanillin/ForgeVanillinConfig.java +++ b/vanillinForge/src/main/java/dev/engine_room/vanillin/ForgeVanillinConfig.java @@ -1,62 +1,140 @@ package dev.engine_room.vanillin; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import com.electronwill.nightconfig.core.Config; + +import dev.engine_room.vanillin.config.Configurator; +import dev.engine_room.vanillin.config.ModOverrides; +import dev.engine_room.vanillin.config.VisualConfigValue; +import dev.engine_room.vanillin.config.VisualOverride; +import dev.engine_room.vanillin.config.VisualOverrideValue; import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.forgespi.language.IModInfo; public class ForgeVanillinConfig { public static final ForgeVanillinConfig INSTANCE = new ForgeVanillinConfig(VanillaVisuals.CONFIGURATOR); - public final Map blockEntities = new HashMap<>(); - public final Map entities = new HashMap<>(); - private final Configurator configurator; private final ForgeConfigSpec clientSpec; + private final ConfigSection blockEntities; + private final ConfigSection entities; + private ForgeVanillinConfig(Configurator configurator) { this.configurator = configurator; var builder = new ForgeConfigSpec.Builder(); - builder.push("block_entities"); - // Seems like we need to register all field ahead of time so this constructor must run after VanillaVisuals#init - for (var configured : configurator.blockEntities.values()) { - var name = configured.configKey(); - var config = builder.define(name, configured.enabledByDefault()); - blockEntities.put(name, config); - } - - builder.pop(); - builder.push("entities"); - - for (var configured : configurator.entities.values()) { - var name = configured.configKey(); - var config = builder.define(name, configured.defaultEnabled()); - entities.put(name, config); - } + var blockEntities = setup(builder, configurator.blockEntities.values(), "block_entities"); + var entities = setup(builder, configurator.entities.values(), "entities"); clientSpec = builder.build(); + + var modOverrides = modOverrides(); + + this.blockEntities = new ConfigSection(blockEntities, modOverrides.blockEntities()); + this.entities = new ConfigSection(entities, modOverrides.entities()); } public void apply() { - for (var configured : configurator.blockEntities.values()) { - var value = blockEntities.get(configured.configKey()); - if (value != null) { - configured.set(value.get()); - } - } - - for (var configured : configurator.entities.values()) { - var value = entities.get(configured.configKey()); - if (value != null) { - configured.set(value.get()); - } - } + blockEntities.apply(configurator.blockEntities.values()); + entities.apply(configurator.entities.values()); } public void registerSpecs(ModLoadingContext context) { context.registerConfig(ModConfig.Type.CLIENT, clientSpec); } + + private static ModOverrides modOverrides() { + var blockEntities = new ArrayList(); + var entities = new ArrayList(); + + ModList.get() + .forEachModFile(file -> { + var info = file.getModFileInfo(); + for (IModInfo mod : info.getMods()) { + var modId = mod.getModId(); + var modProperties = mod.getModProperties() + .get("vanillin:overrides"); + + if (modProperties == null) { + continue; + } + + // There's no well-defined API for custom properties like in fabric. + // It just returns an object, but internally it's represented with nightconfig. + if (modProperties instanceof Config config) { + readSection(blockEntities, modId, config, "block_entities", "block entity"); + readSection(entities, modId, config, "entities", "entity"); + } else { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override options with an invalid value, ignoring", modId); + } + } + }); + + return new ModOverrides(blockEntities, entities); + } + + private static void readSection(List dst, String modId, Config config, String section, String singular) { + if (!config.contains(section)) { + return; + } + + var sectionObject = config.getRaw(section); + + if (sectionObject instanceof Config sectionConfig) { + for (var entry : sectionConfig.entrySet()) { + var key = entry.getKey(); + var value = entry.getValue(); + + if (value instanceof String valueString) { + var parsed = VisualOverrideValue.parse(valueString); + + if (parsed != null) { + dst.add(new VisualOverride(key, modId, parsed)); + } else { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} '{}' with an invalid value '{}', ignoring", modId, singular, key, valueString); + } + } else { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} '{}' with an invalid value, ignoring", modId, singular, key); + } + } + } else { + Vanillin.CONFIG_LOGGER.warn("Mod '{}' attempted to override {} with an invalid value, ignoring", modId, section); + } + } + + private static Map> setup(ForgeConfigSpec.Builder builder, Collection configuredVisuals, String push) { + var out = new HashMap>(); + builder.push(push); + + for (var configured : configuredVisuals) { + var name = configured.configKey(); + var config = builder.defineEnum(name, VisualConfigValue.DEFAULT); + out.put(name, config); + } + + builder.pop(); + + return out; + } + + private record ConfigSection(Map> config, Map> overrides) { + void apply(Collection values) { + for (var configured : values) { + var key = configured.configKey(); + var value = config.get(key); + if (value != null) { + configured.set(value.get(), overrides.get(key)); + } + } + } + } }