Skip to content

Commit

Permalink
add ability for mods to override other mods' options with new options
Browse files Browse the repository at this point in the history
  • Loading branch information
douira committed Nov 2, 2024
1 parent c18e6c8 commit 406d5ba
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface ConfigBuilder {

ModOptionsBuilder registerOwnModOptions();

OptionOverrideBuilder createOptionOverride();

ColorThemeBuilder createColorTheme();

OptionPageBuilder createOptionPage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface ModOptionsBuilder {
ModOptionsBuilder setColorTheme(ColorThemeBuilder colorTheme);

ModOptionsBuilder addPage(PageBuilder page);

ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.caffeinemc.mods.sodium.api.config.structure;

import net.minecraft.resources.ResourceLocation;

public interface OptionOverrideBuilder {
OptionOverrideBuilder setTarget(ResourceLocation target);

OptionOverrideBuilder setReplacement(OptionBuilder option);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
public class ConfigManager {
public static final String JSON_KEY_SODIUM_CONFIG_INTEGRATIONS = "sodium:config_api_user";

private record ConfigUser(Supplier<ConfigEntryPoint> configEntrypoint, String modId, String modName, String modVersion) {
private record ConfigUser(Supplier<ConfigEntryPoint> configEntrypoint, String modId, String modName,
String modVersion) {
}

private static final Collection<ConfigUser> configUsers = new ArrayList<>();

public static Config CONFIG;
Expand Down Expand Up @@ -82,25 +84,24 @@ private static void registerConfigs(BiConsumer<ConfigEntryPoint, ConfigBuilder>
try {
registerMethod.accept(entryPoint, builder);
builtConfigs = builder.build();
} catch (Exception e) {
crashWithMessage("Mod '" + configUser.modId + "' failed while registering config options.", e);
return;
}

for (var modConfig : builtConfigs) {
var namespace = modConfig.namespace();
if (namespaces.contains(namespace)) {
SodiumClientMod.logger().warn("Mod '{}' provided a duplicate mod id: {}", configUser.modId, namespace);
continue;
}
for (var modConfig : builtConfigs) {
var namespace = modConfig.namespace();
if (namespaces.contains(namespace)) {
throw new IllegalArgumentException("Mod '" + configUser.modId + "' provided a duplicate mod id: " + namespace);
}

namespaces.add(namespace);
namespaces.add(namespace);

if (namespace.equals("sodium")) {
sodiumModOptions = modConfig;
} else {
modConfigs.add(modConfig);
if (namespace.equals("sodium")) {
sodiumModOptions = modConfig;
} else {
modConfigs.add(modConfig);
}
}
} catch (Exception e) {
crashWithMessage("Mod '" + configUser.modId + "' failed while registering config options.", e);
return;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.caffeinemc.mods.sodium.api.config.ConfigState;
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
import net.caffeinemc.mods.sodium.client.console.Console;
import net.caffeinemc.mods.sodium.client.console.message.MessageLevel;
import net.minecraft.client.Minecraft;
Expand All @@ -23,24 +24,69 @@ public class Config implements ConfigState {
public Config(ImmutableList<ModOptions> modOptions) {
this.modOptions = modOptions;

this.validateDependencyGraph();
this.collectOptions();
this.applyOverrides();
this.validateDependencies();

// load options initially from their bindings
resetAllOptions();
}

private void validateDependencyGraph() {
private void collectOptions() {
for (var modConfig : this.modOptions) {
for (var page : modConfig.pages()) {
for (var group : page.groups()) {
for (var option : group.options()) {
if (!option.id.getNamespace().equals(modConfig.namespace())) {
throw new IllegalArgumentException("Namespace of option id '" + option.id + "' does not match the namespace '" + modConfig.namespace() + "' of the enclosing mod config");
}

this.options.put(option.id, option);
option.setParentConfig(this);
}
}
}
}
}

private void applyOverrides() {
// collect overrides and validate them
var overrides = new Object2ReferenceOpenHashMap<ResourceLocation, OptionOverride>();
for (var modConfig : this.modOptions) {
for (var override : modConfig.overrides()) {
if (override.target().getNamespace().equals(modConfig.namespace())) {
throw new IllegalArgumentException("Override by mod '" + modConfig.namespace() + "' targets its own option '" + override.target() + "'");
}

if (overrides.put(override.target(), override) != null) {
throw new IllegalArgumentException("Multiple overrides for option '" + override.target() + "'");
}
}
}

// apply overrides
for (var modConfig : this.modOptions) {
for (var page : modConfig.pages()) {
for (var group : page.groups()) {
var options = group.options();
for (int i = 0; i < options.size(); i++) {
var option = options.get(i);
var override = overrides.get(option.id);
if (override != null) {
var replacement = override.replacement();
options.set(i, replacement);
this.options.remove(option.id);
this.options.put(replacement.id, replacement);
replacement.setParentConfig(this);
option.setParentConfig(null);
}
}
}
}
}
}

private void validateDependencies() {
for (var option : this.options.values()) {
for (var dependency : option.dependencies) {
if (!this.options.containsKey(dependency)) {
Expand All @@ -57,6 +103,7 @@ private void validateDependencyGraph() {
}
}


private void checkDependencyCycles(Option option, ObjectOpenHashSet<ResourceLocation> stack, ObjectOpenHashSet<ResourceLocation> finished) {
if (!stack.add(option.id)) {
throw new IllegalArgumentException("Cycle detected in dependency graph starting from option " + option.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public ModOptionsBuilder registerOwnModOptions() {
return this.registerModOptions(this.defaultNamespace, this.defaultName, this.defaultVersion);
}

@Override
public OptionOverrideBuilder createOptionOverride() {
return new OptionOverrideBuilderImpl();
}

@Override
public ColorThemeBuilder createColorTheme() {
return new ColorThemeBuilderImpl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
import com.google.common.collect.ImmutableList;
import net.caffeinemc.mods.sodium.client.gui.ColorTheme;

public record ModOptions(String namespace, String name, String version, ColorTheme theme, ImmutableList<Page> pages) {
import java.util.List;

public record ModOptions(String namespace, String name, String version, ColorTheme theme, ImmutableList<Page> pages, List<OptionOverride> overrides) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import com.google.common.collect.ImmutableList;
import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder;
import net.caffeinemc.mods.sodium.api.config.structure.ModOptionsBuilder;
import net.caffeinemc.mods.sodium.api.config.structure.OptionOverrideBuilder;
import net.caffeinemc.mods.sodium.api.config.structure.PageBuilder;
import net.caffeinemc.mods.sodium.client.gui.ColorTheme;
import net.caffeinemc.mods.sodium.client.gui.Colors;
import org.apache.commons.lang3.Validate;

import java.util.ArrayList;
Expand All @@ -18,6 +18,7 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder {
private String version;
private ColorTheme theme;
private final List<Page> pages = new ArrayList<>();
private final List<OptionOverride> optionOverrides = new ArrayList<>(0);

ModOptionsBuilderImpl(String namespace, String name, String version) {
this.namespace = namespace;
Expand All @@ -28,13 +29,16 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder {
ModOptions build() {
Validate.notEmpty(this.name, "Name must not be empty");
Validate.notEmpty(this.version, "Version must not be empty");
Validate.notEmpty(this.pages, "At least one page must be added");

if (this.optionOverrides.isEmpty() && this.pages.isEmpty()) {
throw new IllegalStateException("At least one page or option override must be added");
}

if (this.theme == null) {
this.theme = ColorTheme.PRESETS[Math.abs(this.namespace.hashCode()) % ColorTheme.PRESETS.length];
}

return new ModOptions(this.namespace, this.name, this.version, this.theme, ImmutableList.copyOf(this.pages));
return new ModOptions(this.namespace, this.name, this.version, this.theme, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides));
}

@Override
Expand Down Expand Up @@ -66,4 +70,10 @@ public ModOptionsBuilder addPage(PageBuilder builder) {
this.pages.add(((PageBuilderImpl) builder).build());
return this;
}

@Override
public ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override) {
this.optionOverrides.add(((OptionOverrideBuilderImpl) override).build(this.namespace));
return this;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package net.caffeinemc.mods.sodium.client.config.structure;

import com.google.common.collect.ImmutableList;
import net.minecraft.network.chat.Component;

public record OptionGroup(Component name, ImmutableList<Option> options) {
import java.util.List;

public record OptionGroup(Component name, List<Option> options) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class OptionGroupBuilderImpl implements OptionGroupBuilder {
OptionGroup build() {
Validate.notEmpty(this.options, "At least one option must be added");

return new OptionGroup(this.name, ImmutableList.copyOf(this.options));
return new OptionGroup(this.name, this.options);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.caffeinemc.mods.sodium.client.config.structure;

import net.minecraft.resources.ResourceLocation;

public record OptionOverride(ResourceLocation target, String source, Option replacement) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.caffeinemc.mods.sodium.client.config.structure;

import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder;
import net.caffeinemc.mods.sodium.api.config.structure.OptionOverrideBuilder;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.Validate;

public class OptionOverrideBuilderImpl implements OptionOverrideBuilder {
private ResourceLocation target;
private Option replacement;

OptionOverride build(String source) {
Validate.notNull(this.target, "Target must be set");
Validate.notNull(this.replacement, "Replacement must be set");

return new OptionOverride(this.target, source, this.replacement);
}

@Override
public OptionOverrideBuilder setTarget(ResourceLocation target) {
this.target = target;
return this;
}

@Override
public OptionOverrideBuilder setReplacement(OptionBuilder option) {
this.replacement = ((OptionBuilderImpl) option).build();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public V load() {
// for testing cycle detection
// .setEnabledProvider((state) -> state.readIntOption(ResourceLocation.parse("foo:baz")) == 0, ResourceLocation.parse("foo:baz"))

ModOptionsBuilder options = builder.registerModOptions("foo", "Foo fadsa fdsa fdsa fdas fdsafdsa", "1.0 fdas fdas fdasfdsaf dsa")
var options = builder.registerModOptions("foo", "Foo fadsa fdsa fdsa fdas fdsafdsa", "1.0 fdas fdas fdasfdsaf dsa")
.addPage(
builder.createExternalPage()
.setName(Component.literal("External Page"))
Expand Down Expand Up @@ -216,6 +216,22 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
page.addOptionGroup(group);
}
options.addPage(page);

var other = builder.registerModOptions("bar", "Bar", "1.0 fdas fdas fdasfdsaf dsa");
other.registerOptionOverride(builder.createOptionOverride()
.setTarget(ResourceLocation.parse("foo:bar"))
.setReplacement(
builder.createBooleanOption(ResourceLocation.parse("foo:bar"))
.setStorageHandler(() -> {
})
.setName(Component.literal("Replaced Bar"))
.setTooltip(Component.literal("Baz"))
.setDefaultValue(true)
.setBinding(new LocalBinding<>(true))
.setImpact(OptionImpact.MEDIUM)

)
);
}

private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public void rebuild() {
var headerHeight = this.font.lineHeight * 3;
int listHeight = Layout.BUTTON_SHORT + Layout.INNER_MARGIN * 2 - headerHeight;
for (var modOptions : ConfigManager.CONFIG.getModOptions()) {
if (modOptions.pages().isEmpty()) {
continue;
}

var theme = modOptions.theme();

CenteredFlatWidget header = new HeaderEntryWidget(new Dim2i(x, y + listHeight, width, headerHeight), modOptions, theme);
Expand Down

0 comments on commit 406d5ba

Please sign in to comment.