diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java new file mode 100644 index 0000000000..f6f10a6146 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config; + +import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder; + +public interface ConfigEntryPoint { + void registerConfigEarly(ConfigBuilder builder); + + void registerConfigLate(ConfigBuilder builder); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java new file mode 100644 index 0000000000..6d71988b95 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java @@ -0,0 +1,11 @@ +package net.caffeinemc.mods.sodium.api.config; + +import net.minecraft.resources.ResourceLocation; + +public interface ConfigState { + boolean readBooleanOption(ResourceLocation id); + + int readIntOption(ResourceLocation id); + + > E readEnumOption(ResourceLocation id, Class enumClass); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java new file mode 100644 index 0000000000..98e0a14cb4 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java @@ -0,0 +1,6 @@ +package net.caffeinemc.mods.sodium.api.config; + +@FunctionalInterface +public interface StorageEventHandler { + void afterSave(); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java new file mode 100644 index 0000000000..674e077dcf --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +import net.minecraft.network.chat.Component; + +public interface ControlValueFormatter { + Component format(int value); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java new file mode 100644 index 0000000000..01186c0384 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +import net.minecraft.network.chat.Component; + +public interface NameProvider { + Component getName(); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java new file mode 100644 index 0000000000..432d0e03ea --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +public interface OptionBinding { + void save(V value); + + V load(); + + // TODO: add shortcuts to generate for vanilla option bindings +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java similarity index 75% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java rename to common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java index 17568af5a5..7f71f462b1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java @@ -1,4 +1,4 @@ -package net.caffeinemc.mods.sodium.client.gui.options; +package net.caffeinemc.mods.sodium.api.config.option; public enum OptionFlag { REQUIRES_RENDERER_RELOAD, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java similarity index 79% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java rename to common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java index 2e274854c8..3c69beb0bc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java @@ -1,9 +1,9 @@ -package net.caffeinemc.mods.sodium.client.gui.options; +package net.caffeinemc.mods.sodium.api.config.option; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; -public enum OptionImpact implements TextProvider { +public enum OptionImpact implements NameProvider { LOW(ChatFormatting.GREEN, "sodium.option_impact.low"), MEDIUM(ChatFormatting.YELLOW, "sodium.option_impact.medium"), HIGH(ChatFormatting.GOLD, "sodium.option_impact.high"), @@ -17,7 +17,7 @@ public enum OptionImpact implements TextProvider { } @Override - public Component getLocalizedName() { + public Component getName() { return this.text; } } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java new file mode 100644 index 0000000000..8df770781a --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +public record Range(int min, int max, int step) { + public boolean isValueValid(int value) { + return value >= this.min && value <= this.max && (value - this.min) % this.step == 0; + } +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java new file mode 100644 index 0000000000..615b303178 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java @@ -0,0 +1,50 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface BooleanOptionBuilder extends OptionBuilder { + @Override + BooleanOptionBuilder setName(Component name); + + @Override + BooleanOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + BooleanOptionBuilder setTooltip(Component tooltip); + + @Override + BooleanOptionBuilder setTooltip(Function tooltip); + + @Override + BooleanOptionBuilder setImpact(OptionImpact impact); + + @Override + BooleanOptionBuilder setFlags(OptionFlag... flags); + + @Override + BooleanOptionBuilder setDefaultValue(Boolean value); + + @Override + BooleanOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + BooleanOptionBuilder setEnabled(boolean available); + + @Override + BooleanOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + BooleanOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + BooleanOptionBuilder setBinding(OptionBinding binding); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java new file mode 100644 index 0000000000..bad9ab73c0 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.resources.ResourceLocation; + +public interface ConfigBuilder { + ModOptionsBuilder registerModConfig(String namespace, String name, String version); + + ModOptionsBuilder registerOwnModConfig(); + + OptionPageBuilder createOptionPage(); + + OptionGroupBuilder createOptionGroup(); + + BooleanOptionBuilder createBooleanOption(ResourceLocation id); + + IntegerOptionBuilder createIntegerOption(ResourceLocation id); + + > EnumOptionBuilder createEnumOption(ResourceLocation id, Class enumClass); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java new file mode 100644 index 0000000000..17678f8c06 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java @@ -0,0 +1,61 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface EnumOptionBuilder> extends OptionBuilder { + @Override + EnumOptionBuilder setName(Component name); + + @Override + EnumOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + EnumOptionBuilder setTooltip(Component tooltip); + + @Override + EnumOptionBuilder setTooltip(Function tooltip); + + @Override + EnumOptionBuilder setImpact(OptionImpact impact); + + @Override + EnumOptionBuilder setFlags(OptionFlag... flags); + + @Override + EnumOptionBuilder setDefaultValue(E value); + + @Override + EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + EnumOptionBuilder setEnabled(boolean available); + + @Override + EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + EnumOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + EnumOptionBuilder setBinding(OptionBinding binding); + + EnumOptionBuilder setAllowedValues(Set allowedValues); + + EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies); + + EnumOptionBuilder setElementNameProvider(Function provider); + + static > Function nameProviderFrom(Component... names) { + return e -> names[e.ordinal()]; + } +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java new file mode 100644 index 0000000000..1991d5606e --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -0,0 +1,56 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.*; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface IntegerOptionBuilder extends OptionBuilder { + @Override + IntegerOptionBuilder setName(Component name); + + @Override + IntegerOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + IntegerOptionBuilder setTooltip(Component tooltip); + + @Override + IntegerOptionBuilder setTooltip(Function tooltip); + + @Override + IntegerOptionBuilder setImpact(OptionImpact impact); + + @Override + IntegerOptionBuilder setFlags(OptionFlag... flags); + + @Override + IntegerOptionBuilder setDefaultValue(Integer value); + + @Override + IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + IntegerOptionBuilder setEnabled(boolean available); + + @Override + IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + IntegerOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + IntegerOptionBuilder setBinding(OptionBinding binding); + + IntegerOptionBuilder setRange(int min, int max, int step); + + IntegerOptionBuilder setRange(Range range); + + IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies); + + IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter); +} \ No newline at end of file diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java new file mode 100644 index 0000000000..f2e57b0791 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +public interface ModOptionsBuilder { + ModOptionsBuilder setName(String name); + + ModOptionsBuilder setVersion(String version); + + ModOptionsBuilder addPage(OptionPageBuilder page); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java new file mode 100644 index 0000000000..c4a6bbd148 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java @@ -0,0 +1,38 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface OptionBuilder { + OptionBuilder setName(Component name); + + OptionBuilder setStorageHandler(StorageEventHandler storage); + + OptionBuilder setTooltip(Component tooltip); + + OptionBuilder setTooltip(Function tooltip); + + OptionBuilder setImpact(OptionImpact impact); + + OptionBuilder setFlags(OptionFlag... flags); + + OptionBuilder setDefaultValue(V value); + + OptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + OptionBuilder setEnabled(boolean available); + + OptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + OptionBuilder setBinding(Consumer save, Supplier load); + + OptionBuilder setBinding(OptionBinding binding); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java new file mode 100644 index 0000000000..0cde674131 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.network.chat.Component; + +public interface OptionGroupBuilder { + OptionGroupBuilder setName(Component name); + + OptionGroupBuilder addOption(OptionBuilder option); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java new file mode 100644 index 0000000000..2299dd26d0 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.network.chat.Component; + +public interface OptionPageBuilder { + OptionPageBuilder setName(Component name); + + OptionPageBuilder addOptionGroup(OptionGroupBuilder group); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java index 9dba7b3641..ededf670eb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java @@ -1,18 +1,18 @@ package net.caffeinemc.mods.sodium.client; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.data.fingerprint.FingerprintMeasure; import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; -import net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions; +import net.caffeinemc.mods.sodium.client.gui.SodiumOptions; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import net.minecraft.network.chat.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; public class SodiumClientMod { - private static SodiumGameOptions CONFIG; + private static SodiumOptions OPTIONS; private static final Logger LOGGER = LoggerFactory.getLogger("Sodium"); private static String MOD_VERSION; @@ -20,7 +20,9 @@ public class SodiumClientMod { public static void onInitialization(String version) { MOD_VERSION = version; - CONFIG = loadConfig(); + OPTIONS = loadConfig(); + + ConfigManager.registerConfigsEarly(); try { updateFingerprint(); @@ -29,12 +31,12 @@ public static void onInitialization(String version) { } } - public static SodiumGameOptions options() { - if (CONFIG == null) { + public static SodiumOptions options() { + if (OPTIONS == null) { throw new IllegalStateException("Config not yet available"); } - return CONFIG; + return OPTIONS; } public static Logger logger() { @@ -45,16 +47,16 @@ public static Logger logger() { return LOGGER; } - private static SodiumGameOptions loadConfig() { + private static SodiumOptions loadConfig() { try { - return SodiumGameOptions.loadFromDisk(); + return SodiumOptions.loadFromDisk(); } catch (Exception e) { LOGGER.error("Failed to load configuration file", e); LOGGER.error("Using default configuration file in read-only mode"); Console.instance().logMessage(MessageLevel.SEVERE, "sodium.console.config_not_loaded", true, 12.5); - var config = SodiumGameOptions.defaults(); + var config = SodiumOptions.defaults(); config.setReadOnly(); return config; @@ -62,10 +64,10 @@ private static SodiumGameOptions loadConfig() { } public static void restoreDefaultOptions() { - CONFIG = SodiumGameOptions.defaults(); + OPTIONS = SodiumOptions.defaults(); try { - SodiumGameOptions.writeToDisk(CONFIG); + SodiumOptions.writeToDisk(OPTIONS); } catch (IOException e) { throw new RuntimeException("Failed to write config file", e); } @@ -97,11 +99,11 @@ private static void updateFingerprint() { if (saved == null || !current.looselyMatches(saved)) { HashedFingerprint.writeToDisk(current.hashed()); - CONFIG.notifications.hasSeenDonationPrompt = false; - CONFIG.notifications.hasClearedDonationButton = false; + OPTIONS.notifications.hasSeenDonationPrompt = false; + OPTIONS.notifications.hasClearedDonationButton = false; try { - SodiumGameOptions.writeToDisk(CONFIG); + SodiumOptions.writeToDisk(OPTIONS); } catch (IOException e) { LOGGER.error("Failed to update config file", e); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java new file mode 100644 index 0000000000..e4f057cf6e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java @@ -0,0 +1,26 @@ +package net.caffeinemc.mods.sodium.client.config; + +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class AnonymousOptionBinding implements OptionBinding { + private final Consumer save; + private final Supplier load; + + public AnonymousOptionBinding(Consumer save, Supplier load) { + this.save = save; + this.load = load; + } + + @Override + public void save(V value) { + this.save.accept(value); + } + + @Override + public V load() { + return this.load.get(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java new file mode 100644 index 0000000000..bdab62fedf --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java @@ -0,0 +1,118 @@ +package net.caffeinemc.mods.sodium.client.config; + + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder; +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.structure.Config; +import net.caffeinemc.mods.sodium.client.config.structure.ConfigBuilderImpl; +import net.caffeinemc.mods.sodium.client.config.structure.ModOptions; +import net.minecraft.CrashReport; +import net.minecraft.client.Minecraft; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +// TODO: is different handling of long version strings necessary? +// TODO: move classes in impl and interface into reasonable packages and figure out correct visibilities +public class ConfigManager { + public static final String JSON_KEY_SODIUM_CONFIG_INTEGRATIONS = "sodium:config_api_user"; + + private record ConfigUser(Supplier configEntrypoint, String modId, String modName, String modVersion) { + } + private static final Collection configUsers = new ArrayList<>(); + + public static Config CONFIG; + + public static void registerConfigEntryPoint(String className, String modId, String modName, String modVersion) { + Class entryPointClass; + try { + entryPointClass = Class.forName(className); + } catch (ClassNotFoundException e) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class is missing: {}", modId, className); + return; + } + if (!ConfigEntryPoint.class.isAssignableFrom(entryPointClass)) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class is of the wrong type: {}", modId, entryPointClass); + return; + } + + registerConfigEntryPoint(() -> { + try { + Constructor constructor = entryPointClass.getDeclaredConstructor(); + constructor.setAccessible(true); + return (ConfigEntryPoint) constructor.newInstance(); + } catch (ReflectiveOperationException e) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class could not be constructed: {}", modId, entryPointClass); + } + return null; + }, modId, modName, modVersion); + } + + public static void registerConfigEntryPoint(Supplier entryPoint, String modId, String modName, String modVersion) { + configUsers.add(new ConfigUser(entryPoint, modId, modName, modVersion)); + } + + public static void registerConfigsEarly() { + registerConfigs(ConfigEntryPoint::registerConfigEarly); + } + + public static void registerConfigsLate() { + registerConfigs(ConfigEntryPoint::registerConfigLate); + } + + private static void registerConfigs(BiConsumer registerMethod) { + var namespaces = new ObjectOpenHashSet<>(); + ModOptions sodiumModOptions = null; + var modConfigs = new ObjectArrayList(); + + for (ConfigUser configUser : configUsers) { + var entryPoint = configUser.configEntrypoint.get(); + if (entryPoint == null) { + continue; + } + + var builder = new ConfigBuilderImpl(configUser.modId, configUser.modName, configUser.modVersion); + registerMethod.accept(entryPoint, builder); + Collection builtConfigs; + try { + builtConfigs = builder.build(); + } catch (Exception e) { + Minecraft.getInstance().emergencySaveAndCrash(new CrashReport("Failed to build config for mod " + configUser.modId, 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; + } + + namespaces.add(namespace); + + if (namespace.equals("sodium")) { + sodiumModOptions = modConfig; + } else { + modConfigs.add(modConfig); + } + } + } + + modConfigs.sort(Comparator.comparing(ModOptions::name)); + + if (sodiumModOptions == null) { + throw new RuntimeException("Sodium mod config not found"); + } + modConfigs.add(0, sodiumModOptions); + + CONFIG = new Config(ImmutableList.copyOf(modConfigs)); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java new file mode 100644 index 0000000000..5a739ceb5f --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java @@ -0,0 +1,26 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.gui.options.control.Control; +import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.function.Function; + +class BooleanOption extends Option { + BooleanOption(ResourceLocation id, Collection dependencies, Component name, StorageEventHandler storage, Function tooltipProvider, OptionImpact impact, EnumSet flags, DependentValue defaultValue, DependentValue enabled, OptionBinding binding) { + super(id, dependencies, name, storage, tooltipProvider, impact, flags, defaultValue, enabled, binding); + } + + @Override + Control createControl() { + return new TickBoxControl(this); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOptionBuilderImpl.java new file mode 100644 index 0000000000..d1f1b68c68 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOptionBuilderImpl.java @@ -0,0 +1,97 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.structure.BooleanOptionBuilder; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class BooleanOptionBuilderImpl extends OptionBuilderImpl implements BooleanOptionBuilder { + BooleanOptionBuilderImpl(ResourceLocation id) { + super(id); + } + + @Override + BooleanOption build() { + this.prepareBuild(); + return new BooleanOption(this.id, this.getDependencies(), this.name, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.enabled, this.binding); + } + + @Override + public BooleanOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public BooleanOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public BooleanOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public BooleanOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public BooleanOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public BooleanOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public BooleanOptionBuilder setDefaultValue(Boolean value) { + super.setDefaultValue(value); + return this; + } + + @Override + public BooleanOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public BooleanOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public BooleanOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public BooleanOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public BooleanOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java new file mode 100644 index 0000000000..df97e8dfec --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -0,0 +1,152 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; +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.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Map; + +// TODO: notify storage handlers after update is completed +public class Config implements ConfigState { + private final Map> options = new Object2ReferenceLinkedOpenHashMap<>(); + private final ObjectOpenHashSet pendingStorageHandlers = new ObjectOpenHashSet<>(); + private final ImmutableList modOptions; + + public Config(ImmutableList modOptions) { + this.modOptions = modOptions; + + this.validateDependencyGraph(); + + // load options initially from their bindings + resetAllOptions(); + } + + private void validateDependencyGraph() { + for (var modConfig : this.modOptions) { + for (var page : modConfig.pages()) { + for (var group : page.groups()) { + for (var option : group.options()) { + this.options.put(option.id, option); + option.setParentConfig(this); + } + } + } + } + + for (var option : this.options.values()) { + for (var dependency : option.dependencies) { + if (!this.options.containsKey(dependency)) { + throw new IllegalArgumentException("Option " + option.id + " depends on non-existent option " + dependency); + } + } + } + + // make sure there are no cycles + var stack = new ObjectOpenHashSet(); + var finished = new ObjectOpenHashSet(); + for (var option : this.options.values()) { + this.checkDependencyCycles(option, stack, finished); + } + } + + private void checkDependencyCycles(Option option, ObjectOpenHashSet stack, ObjectOpenHashSet finished) { + if (!stack.add(option.id)) { + throw new IllegalArgumentException("Cycle detected in dependency graph starting from option " + option.id); + } + + for (var dependency : option.dependencies) { + if (finished.contains(dependency)) { + continue; + } + this.checkDependencyCycles(this.options.get(dependency), stack, finished); + } + + stack.remove(option.id); + finished.add(option.id); + } + + public void resetAllOptions() { + for (var option : this.options.values()) { + option.resetFromBinding(); + } + } + + public Collection applyAllOptions() { + var flags = EnumSet.noneOf(OptionFlag.class); + + for (var option : this.options.values()) { + if (option.applyChanges()) { + flags.addAll(option.getFlags()); + } + } + + this.flushStorageHandlers(); + + return flags; + } + + public boolean anyOptionChanged() { + for (var option : this.options.values()) { + if (option.hasChanged()) { + return true; + } + } + + return false; + } + + void notifyStorageWrite(StorageEventHandler handler) { + this.pendingStorageHandlers.add(handler); + } + + void flushStorageHandlers() { + for (var handler : this.pendingStorageHandlers) { + handler.afterSave(); + } + this.pendingStorageHandlers.clear(); + } + + public ImmutableList getModConfigs() { + return this.modOptions; + } + + @Override + public boolean readBooleanOption(ResourceLocation id) { + var option = this.options.get(id); + if (option instanceof BooleanOption booleanOption) { + return booleanOption.getValidatedValue(); + } + + throw new IllegalArgumentException("Can't read boolean value from option with id " + id); + } + + @Override + public int readIntOption(ResourceLocation id) { + var option = this.options.get(id); + if (option instanceof IntegerOption intOption) { + return intOption.getValidatedValue(); + } + + throw new IllegalArgumentException("Can't read int value from option with id " + id); + } + + @Override + public > E readEnumOption(ResourceLocation id, Class enumClass) { + var option = this.options.get(id); + if (option instanceof EnumOption enumOption) { + if (enumOption.enumClass != enumClass) { + throw new IllegalArgumentException("Enum class mismatch for option with id " + id + ": requested " + enumClass + ", option has " + enumOption.enumClass); + } + + return enumClass.cast(enumOption.getValidatedValue()); + } + + throw new IllegalArgumentException("Can't read enum value from option with id " + id); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ConfigBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ConfigBuilderImpl.java new file mode 100644 index 0000000000..c6ca7d3f48 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ConfigBuilderImpl.java @@ -0,0 +1,67 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.structure.*; +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ConfigBuilderImpl implements ConfigBuilder { + private final List pendingModConfigBuilders = new ArrayList<>(1); + + private final String defaultNamespace; + private final String defaultName; + private final String defaultVersion; + + public ConfigBuilderImpl(String defaultNamespace, String defaultName, String defaultVersion) { + this.defaultNamespace = defaultNamespace; + this.defaultName = defaultName; + this.defaultVersion = defaultVersion; + } + + @Override + public ModOptionsBuilder registerModConfig(String namespace, String name, String version) { + var builder = new ModOptionsBuilderImpl(namespace, name, version); + this.pendingModConfigBuilders.add(builder); + return builder; + } + + @Override + public ModOptionsBuilder registerOwnModConfig() { + return this.registerModConfig(this.defaultNamespace, this.defaultName, this.defaultVersion); + } + + @Override + public OptionPageBuilder createOptionPage() { + return new OptionPageBuilderImpl(); + } + + @Override + public OptionGroupBuilder createOptionGroup() { + return new OptionGroupBuilderImpl(); + } + + @Override + public BooleanOptionBuilder createBooleanOption(ResourceLocation id) { + return new BooleanOptionBuilderImpl(id); + } + + @Override + public IntegerOptionBuilder createIntegerOption(ResourceLocation id) { + return new IntegerOptionBuilderImpl(id); + } + + @Override + public > EnumOptionBuilder createEnumOption(ResourceLocation id, Class enumClass) { + return new EnumOptionBuilderImpl<>(id, enumClass); + } + + public Collection build() { + var configs = new ArrayList(this.pendingModConfigBuilders.size()); + for (var builder : this.pendingModConfigBuilders) { + configs.add(builder.build()); + } + return configs; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOption.java new file mode 100644 index 0000000000..f1327f65b6 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOption.java @@ -0,0 +1,41 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.gui.options.control.Control; +import net.caffeinemc.mods.sodium.client.gui.options.control.CyclingControl; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; +import java.util.function.Function; + +class EnumOption> extends Option { + final Class enumClass; + + private final DependentValue> allowedValues; + private final Function elementNameProvider; + + EnumOption(ResourceLocation id, Collection dependencies, Class enumClass, Component name, StorageEventHandler storage, Function tooltipProvider, OptionImpact impact, EnumSet flags, DependentValue defaultValue, DependentValue enabled, OptionBinding binding, DependentValue> allowedValues, Function elementNameProvider) { + super(id, dependencies, name, storage, tooltipProvider, impact, flags, defaultValue, enabled, binding); + this.enumClass = enumClass; + this.allowedValues = allowedValues; + this.elementNameProvider = elementNameProvider; + } + + @Override + boolean isValueValid(E value) { + return this.allowedValues.get(this.state).contains(value); + } + + @Override + Control createControl() { + // TODO: doesn't update allowed values when dependencies change + return new CyclingControl<>(this, this.enumClass, this.elementNameProvider, this.allowedValues.get(this.state).toArray(this.enumClass.getEnumConstants())); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOptionBuilderImpl.java new file mode 100644 index 0000000000..339abd560d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOptionBuilderImpl.java @@ -0,0 +1,146 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.structure.EnumOptionBuilder; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class EnumOptionBuilderImpl> extends OptionBuilderImpl implements EnumOptionBuilder { + final Class enumClass; + + DependentValue> allowedValues; + Function elementNameProvider; + + EnumOptionBuilderImpl(ResourceLocation id, Class enumClass) { + super(id); + this.enumClass = enumClass; + } + + @Override + Option build() { + this.prepareBuild(); + + if (this.allowedValues == null) { + this.allowedValues = new ConstantValue<>(Set.of(this.enumClass.getEnumConstants())); + } + + if (this.elementNameProvider == null && TextProvider.class.isAssignableFrom(this.enumClass)) { + this.elementNameProvider = e -> ((TextProvider) e).getLocalizedName(); + } + + Validate.notNull(this.elementNameProvider, "Element name provider must be set or enum class must implement TextProvider"); + + return new EnumOption<>(this.id, this.getDependencies(), this.enumClass, this.name, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.enabled, this.binding, this.allowedValues, this.elementNameProvider); + } + + @Override + Collection getDependencies() { + var deps = super.getDependencies(); + deps.addAll(this.allowedValues.getDependencies()); + return deps; + } + + @Override + public EnumOptionBuilder setAllowedValues(Set allowedValues) { + this.allowedValues = new ConstantValue<>(allowedValues); + return this; + } + + @Override + public EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies) { + this.allowedValues = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setElementNameProvider(Function provider) { + this.elementNameProvider = provider; + return this; + } + + @Override + public EnumOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public EnumOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public EnumOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public EnumOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public EnumOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public EnumOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public EnumOptionBuilder setDefaultValue(E value) { + super.setDefaultValue(value); + return this; + } + + @Override + public EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public EnumOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java new file mode 100644 index 0000000000..19c32e9112 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java @@ -0,0 +1,36 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.*; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.gui.options.control.Control; +import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.function.Function; + +class IntegerOption extends Option { + private final DependentValue range; + private final ControlValueFormatter valueFormatter; + + IntegerOption(ResourceLocation id, Collection dependencies, Component name, StorageEventHandler storage, Function tooltipProvider, OptionImpact impact, EnumSet flags, DependentValue defaultValue, DependentValue enabled, OptionBinding binding, DependentValue range, ControlValueFormatter valueFormatter) { + super(id, dependencies, name, storage, tooltipProvider, impact, flags, defaultValue, enabled, binding); + this.range = range; + this.valueFormatter = valueFormatter; + } + + @Override + boolean isValueValid(Integer value) { + return this.range.get(this.state).isValueValid(value); + } + + @Override + Control createControl() { + var range = this.range.get(this.state); + return new SliderControl(this, range.min(), range.max(), range.step(), this.valueFormatter); + } +} + diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOptionBuilderImpl.java new file mode 100644 index 0000000000..956ea6f1a2 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOptionBuilderImpl.java @@ -0,0 +1,137 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.structure.IntegerOptionBuilder; +import net.caffeinemc.mods.sodium.api.config.option.*; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class IntegerOptionBuilderImpl extends OptionBuilderImpl implements IntegerOptionBuilder { + DependentValue rangeProvider; + ControlValueFormatter valueFormatter; + + IntegerOptionBuilderImpl(ResourceLocation id) { + super(id); + } + + @Override + IntegerOption build() { + this.prepareBuild(); + + Validate.notNull(this.rangeProvider, "Range provider must be set"); + Validate.notNull(this.valueFormatter, "Value formatter must be set"); + + return new IntegerOption(this.id, this.getDependencies(), this.name, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.enabled, this.binding, this.rangeProvider, this.valueFormatter); + } + + @Override + Collection getDependencies() { + var deps = super.getDependencies(); + deps.addAll(this.rangeProvider.getDependencies()); + return deps; + } + + @Override + public IntegerOptionBuilder setRange(int min, int max, int step) { + return this.setRange(new Range(min, max, step)); + } + + @Override + public IntegerOptionBuilder setRange(Range range) { + this.rangeProvider = new ConstantValue<>(range); + return this; + } + + @Override + public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { + this.rangeProvider = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public IntegerOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public IntegerOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public IntegerOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public IntegerOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public IntegerOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public IntegerOptionBuilder setDefaultValue(Integer value) { + super.setDefaultValue(value); + return this; + } + + @Override + public IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public IntegerOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } + + @Override + public IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter) { + this.valueFormatter = formatter; + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java new file mode 100644 index 0000000000..defb956c8e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java @@ -0,0 +1,6 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; + +public record ModOptions(String namespace, String name, String version, ImmutableList pages) { +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptionsBuilderImpl.java new file mode 100644 index 0000000000..17d6fc5f62 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptionsBuilderImpl.java @@ -0,0 +1,48 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; +import net.caffeinemc.mods.sodium.api.config.structure.ModOptionsBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionPageBuilder; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; + +class ModOptionsBuilderImpl implements ModOptionsBuilder { + private final String namespace; + private String name; + private String version; + private final List pages = new ArrayList<>(); + + ModOptionsBuilderImpl(String namespace, String name, String version) { + this.namespace = namespace; + this.name = name; + this.version = version; + } + + 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"); + + return new ModOptions(this.namespace, this.name, this.version, ImmutableList.copyOf(this.pages)); + } + + @Override + public ModOptionsBuilder setName(String name) { + this.name = name; + return this; + } + + @Override + public ModOptionsBuilder setVersion(String version) { + this.version = version; + return this; + } + + @Override + public ModOptionsBuilder addPage(OptionPageBuilder builder) { + this.pages.add(((OptionPageBuilderImpl) builder).build()); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java new file mode 100644 index 0000000000..600ac65e9e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java @@ -0,0 +1,141 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.gui.options.control.Control; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.function.Function; + +/* TODO: +- storage call after update pass is done +- initial loading and validation +- integration with widget rendering +- proper setup for sodium's own options +- proper binding setup, vanilla bindings/binding generator +- entrypoints for mod scanning and option registration +- figure out changed value/reset value features + */ +// TODO: use update tag to prevent multiple calls to dependencies +public abstract class Option { + final ResourceLocation id; + final Collection dependencies; + + final Component name; + final StorageEventHandler storage; + final Function tooltipProvider; + final OptionImpact impact; + final EnumSet flags; + final DependentValue defaultValue; + final DependentValue enabled; + final OptionBinding binding; + + Config state; + private V value; + private V modifiedValue; + + Control control; + + Option(ResourceLocation id, Collection dependencies, Component name, StorageEventHandler storage, Function tooltipProvider, OptionImpact impact, EnumSet flags, DependentValue defaultValue, DependentValue enabled, OptionBinding binding) { + if (dependencies.contains(id)) { + throw new IllegalArgumentException("Option cannot depend on itself"); + } + + this.id = id; + this.dependencies = dependencies; + + this.name = name; + this.storage = storage; + this.tooltipProvider = tooltipProvider; + this.impact = impact; + this.flags = flags; + this.defaultValue = defaultValue; + this.enabled = enabled; + this.binding = binding; + } + + abstract Control createControl(); + + public Control getControl() { + if (this.control == null) { + this.control = this.createControl(); + } + return this.control; + } + + void setParentConfig(Config state) { + this.state = state; + } + + public void modifyValue(V value) { + this.modifiedValue = value; + } + + void resetFromBinding() { + this.value = this.binding.load(); + + if (!isValueValid(this.value)) { + var defaultValue = this.defaultValue.get(this.state); + if (defaultValue != this.value) { + this.value = defaultValue; + this.binding.save(this.value); + this.state.notifyStorageWrite(this.storage); + } + } + + this.modifiedValue = this.value; + } + + public V getValidatedValue() { + if (!isValueValid(this.modifiedValue)) { + this.modifiedValue = this.defaultValue.get(this.state); + } + + return this.modifiedValue; + } + + public boolean hasChanged() { + return this.modifiedValue != this.value; + } + + public boolean applyChanges() { + if (this.hasChanged()) { + this.value = this.modifiedValue; + this.binding.save(this.value); + this.state.notifyStorageWrite(this.storage); + return true; + } + return false; + } + + boolean isValueValid(V value) { + return true; + } + + public boolean isEnabled() { + return this.enabled.get(this.state); + } + + public Component getName() { + return this.name; + } + + public OptionImpact getImpact() { + return this.impact; + } + + public Component getTooltip() { + return this.tooltipProvider.apply(this.getValidatedValue()); + } + + public Collection getFlags() { + return this.flags; + } +} + diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionBuilderImpl.java new file mode 100644 index 0000000000..94fd4580d9 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionBuilderImpl.java @@ -0,0 +1,154 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.client.config.AnonymousOptionBinding; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +abstract class OptionBuilderImpl implements OptionBuilder { + final ResourceLocation id; + + Component name; + StorageEventHandler storage; + Function tooltipProvider; + OptionImpact impact; + EnumSet flags = EnumSet.noneOf(OptionFlag.class); + DependentValue defaultValue; + DependentValue enabled; + OptionBinding binding; + + OptionBuilderImpl(ResourceLocation id) { + this.id = id; + } + + abstract Option build(); + + void prepareBuild() { + Validate.notNull(this.name, "Name must be set"); + Validate.notNull(this.storage, "Storage handler must be set"); + Validate.notNull(this.tooltipProvider, "Tooltip provider must be set"); + Validate.notNull(this.defaultValue, "Default value must be set"); + + if (this.enabled == null) { + this.enabled = new ConstantValue<>(true); + } + + Validate.notNull(this.binding, "Binding must be set"); + } + + Collection getDependencies() { + var dependencies = new ObjectLinkedOpenHashSet(); + dependencies.addAll(this.defaultValue.getDependencies()); + dependencies.addAll(this.enabled.getDependencies()); + return dependencies; + } + + @Override + public OptionBuilder setName(Component name) { + Validate.notNull(name, "Argument must not be null"); + + this.name = name; + return this; + } + + @Override + public OptionBuilder setStorageHandler(StorageEventHandler storage) { + Validate.notNull(storage, "Argument must not be null"); + + this.storage = storage; + return this; + } + + @Override + public OptionBuilder setTooltip(Component tooltip) { + Validate.notNull(tooltip, "Argument must not be null"); + + this.tooltipProvider = v -> tooltip; + return this; + } + + @Override + public OptionBuilder setTooltip(Function tooltip) { + Validate.notNull(tooltip, "Argument must not be null"); + + this.tooltipProvider = tooltip; + return this; + } + + @Override + public OptionBuilder setImpact(OptionImpact impact) { + Validate.notNull(impact, "Argument must not be null"); + + this.impact = impact; + return this; + } + + @Override + public OptionBuilder setFlags(OptionFlag... flags) { + Collections.addAll(this.flags, flags); + return this; + } + + @Override + public OptionBuilder setDefaultValue(V value) { + Validate.notNull(value, "Argument must not be null"); + + this.defaultValue = new ConstantValue<>(value); + return this; + } + + @Override + public OptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + Validate.notNull(provider, "Argument must not be null"); + + this.defaultValue = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public OptionBuilder setEnabled(boolean available) { + this.enabled = new ConstantValue<>(available); + return this; + } + + @Override + public OptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + Validate.notNull(provider, "Argument must not be null"); + + this.enabled = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public OptionBuilder setBinding(Consumer save, Supplier load) { + Validate.notNull(save, "Setter must not be null"); + Validate.notNull(load, "Getter must not be null"); + + this.binding = new AnonymousOptionBinding<>(save, load); + return this; + } + + @Override + public OptionBuilder setBinding(OptionBinding binding) { + Validate.notNull(binding, "Argument must not be null"); + + this.binding = binding; + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroup.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroup.java new file mode 100644 index 0000000000..323bd9b526 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroup.java @@ -0,0 +1,7 @@ +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> options) { +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroupBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroupBuilderImpl.java new file mode 100644 index 0000000000..fca9a5937e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroupBuilderImpl.java @@ -0,0 +1,33 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionGroupBuilder; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; + +class OptionGroupBuilderImpl implements OptionGroupBuilder { + private Component name; + private final List> options = new ArrayList<>(); + + OptionGroup build() { + Validate.notEmpty(this.options, "At least one option must be added"); + + return new OptionGroup(this.name, ImmutableList.copyOf(this.options)); + } + + @Override + public OptionGroupBuilder setName(Component name) { + this.name = name; + return this; + } + + @Override + public OptionGroupBuilder addOption(OptionBuilder option) { + this.options.add(((OptionBuilderImpl) option).build()); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPage.java new file mode 100644 index 0000000000..fcc02ba7bc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPage.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; +import net.minecraft.network.chat.Component; + +public record OptionPage(Component name, ImmutableList groups) { +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPageBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPageBuilderImpl.java new file mode 100644 index 0000000000..3de15cba8d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPageBuilderImpl.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.sodium.client.config.structure; + +import com.google.common.collect.ImmutableList; +import net.caffeinemc.mods.sodium.api.config.structure.OptionGroupBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionPageBuilder; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; + +class OptionPageBuilderImpl implements OptionPageBuilder { + private Component name; + private final List groups = new ArrayList<>(); + + OptionPage build() { + Validate.notNull(this.name, "Name must not be null"); + Validate.notEmpty(this.groups, "At least one group must be added"); + + return new OptionPage(this.name, ImmutableList.copyOf(this.groups)); + } + + @Override + public OptionPageBuilder setName(Component name) { + this.name = name; + return this; + } + + @Override + public OptionPageBuilder addOptionGroup(OptionGroupBuilder group) { + this.groups.add(((OptionGroupBuilderImpl) group).build()); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/ConstantValue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/ConstantValue.java new file mode 100644 index 0000000000..89350d8a67 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/ConstantValue.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.sodium.client.config.value; + +import net.caffeinemc.mods.sodium.client.config.structure.Config; + +public class ConstantValue implements DependentValue { + private final V value; + + public ConstantValue(V value) { + this.value = value; + } + + @Override + public V get(Config state) { + return this.value; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DependentValue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DependentValue.java new file mode 100644 index 0000000000..1ab3ace056 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DependentValue.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.sodium.client.config.value; + +import net.caffeinemc.mods.sodium.client.config.structure.Config; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.Set; + +public interface DependentValue { + V get(Config state); + + default Collection getDependencies() { + return Set.of(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DynamicValue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DynamicValue.java new file mode 100644 index 0000000000..fa72d1b657 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DynamicValue.java @@ -0,0 +1,58 @@ +package net.caffeinemc.mods.sodium.client.config.value; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.client.config.structure.Config; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Function; + +public class DynamicValue implements DependentValue, ConfigState { + private final Set dependencies; + private final Function provider; + private Config state; + + public DynamicValue(Function provider, ResourceLocation[] dependencies) { + this.provider = provider; + this.dependencies = Set.of(dependencies); + } + + @Override + public V get(Config state) { + this.state = state; + var result = this.provider.apply(this); + this.state = null; + return result; + } + + @Override + public Collection getDependencies() { + return this.dependencies; + } + + private void validateRead(ResourceLocation id) { + if (!this.dependencies.contains(id)) { + throw new IllegalStateException("Attempted to read option value that is not a declared dependency"); + } + } + + // TODO: resolve dependencies with update tag here or within ConfigStateImpl? + @Override + public boolean readBooleanOption(ResourceLocation id) { + this.validateRead(id); + return this.state.readBooleanOption(id); + } + + @Override + public int readIntOption(ResourceLocation id) { + this.validateRead(id); + return this.state.readIntOption(id); + } + + @Override + public > E readEnumOption(ResourceLocation id, Class enumClass) { + this.validateRead(id); + return this.state.readEnumOption(id, enumClass); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java new file mode 100644 index 0000000000..25a8885cbc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -0,0 +1,564 @@ +package net.caffeinemc.mods.sodium.client.gui; + +import com.mojang.blaze3d.platform.Monitor; +import com.mojang.blaze3d.platform.VideoMode; +import com.mojang.blaze3d.platform.Window; +import net.caffeinemc.mods.sodium.api.config.*; +import net.caffeinemc.mods.sodium.api.config.structure.*; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; +import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; +import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; +import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatterImpls; +import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; +import net.minecraft.client.*; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GLCapabilities; + +import java.io.IOException; +import java.util.Optional; + +// TODO: get initialValue from the vanilla options (it's private) +// TODO: more elegantly split late and early config init of this class +public class SodiumConfigBuilder implements ConfigEntryPoint { + private static final SodiumOptions DEFAULTS = SodiumOptions.defaults(); + + private final Options vanillaOpts; + private final StorageEventHandler vanillaStorage; + private final SodiumOptions sodiumOpts; + private final StorageEventHandler sodiumStorage; + + private final @Nullable Window window; + private final Monitor monitor; + + public SodiumConfigBuilder() { + var minecraft = Minecraft.getInstance(); + this.window = minecraft.getWindow(); + this.monitor = this.window == null ? null : this.window.findBestMonitor(); + + this.vanillaOpts = minecraft.options; + this.vanillaStorage = this.vanillaOpts == null ? null : () -> { + this.vanillaOpts.save(); + + SodiumClientMod.logger().info("Flushed changes to Minecraft configuration"); + }; + + this.sodiumOpts = SodiumClientMod.options(); + this.sodiumStorage = () -> { + try { + SodiumOptions.writeToDisk(this.sodiumOpts); + } catch (IOException e) { + throw new RuntimeException("Couldn't save configuration changes", e); + } + + SodiumClientMod.logger().info("Flushed changes to Sodium configuration"); + }; + } + + @Override + public void registerConfigEarly(ConfigBuilder builder) { + new SodiumConfigBuilder().buildEarlyConfig(builder); + } + + @Override + public void registerConfigLate(ConfigBuilder builder) { + new SodiumConfigBuilder().buildFullConfig(builder); + } + + private static ModOptionsBuilder createModOptionsBuilder(ConfigBuilder builder) { + return builder.registerOwnModConfig().setName("Sodium Renderer"); + } + + private void buildEarlyConfig(ConfigBuilder builder) { + createModOptionsBuilder(builder).addPage( + builder.createOptionPage() + .setName(Component.translatable("sodium.options.pages.performance")) + .addOptionGroup( + builder.createOptionGroup() + .addOption(this.buildNoErrorContextOption(builder)))); + } + + private void buildFullConfig(ConfigBuilder builder) { + builder.registerOwnModConfig() + .setName("Sodium Renderer") + .addPage(this.buildGeneralPage(builder)) + .addPage(this.buildQualityPage(builder)) + .addPage(this.buildPerformancePage(builder)) + .addPage(this.buildAdvancedPage(builder)); + + // TODO: this is for debugging and dev + buildExampleAPIUserConfig(builder); + } + + private static void buildExampleAPIUserConfig(ConfigBuilder builder) { + class LocalBinding implements OptionBinding { + private V value; + + public LocalBinding(V value) { + this.value = value; + } + + @Override + public void save(V value) { + this.value = value; + } + + @Override + public V load() { + return this.value; + } + } + builder.registerModConfig("foo", "Foo", "1.0") + .addPage(builder.createOptionPage() + .setName(Component.literal("Foo Page")) + .addOptionGroup(builder.createOptionGroup().addOption( + builder.createBooleanOption(ResourceLocation.parse("foo:bar")) + .setStorageHandler(() -> { + }) + .setName(Component.literal("Bar")) + .setTooltip(Component.literal("Baz")) + .setDefaultValue(true) + .setBinding(new LocalBinding<>(true)) + .setImpact(OptionImpact.LOW) + ))); + } + + private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { + var generalPage = builder.createOptionPage().setName(Component.literal("General")); + generalPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.literal("Render Distance")) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.render_distance")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.renderDistance")) + .setTooltip(Component.translatable("sodium.options.view_distance.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.translateVariable("options.chunks")) + .setRange(2, 32, 1) + .setDefaultValue(12) + .setBinding(this.vanillaOpts.renderDistance()::set, this.vanillaOpts.renderDistance()::get) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.simulation_distance")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.simulationDistance")) + .setTooltip(Component.translatable("sodium.options.simulation_distance.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.translateVariable("options.chunks")) + .setRange(5, 32, 1) + .setDefaultValue(12) + .setBinding(this.vanillaOpts.simulationDistance()::set, this.vanillaOpts.simulationDistance()::get) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.gamma")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.gamma")) + .setTooltip(Component.translatable("sodium.options.brightness.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.brightness()) + .setRange(0, 100, 1) + .setDefaultValue(50) + .setBinding(value -> this.vanillaOpts.gamma().set(value * 0.01D), () -> (int) (this.vanillaOpts.gamma().get() / 0.01D)) + ) + ); + generalPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("options.guiScale")) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.gui_scale")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.guiScale")) + .setTooltip(Component.translatable("sodium.options.gui_scale.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.guiScale()) + .setRange(0, this.window.calculateScale(0, Minecraft.getInstance().isEnforceUnicode()), 1) + .setDefaultValue(0) + .setBinding(value -> { + this.vanillaOpts.guiScale().set(value); + Minecraft.getInstance().resizeDisplay(); + }, this.vanillaOpts.guiScale()::get) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:general.fullscreen")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.fullscreen")) + .setTooltip(Component.translatable("sodium.options.fullscreen.tooltip")) + .setDefaultValue(false) + .setBinding(value -> { + this.vanillaOpts.fullscreen().set(value); + + if (this.window.isFullscreen() != this.vanillaOpts.fullscreen().get()) { + this.window.toggleFullScreen(); + + // The client might not be able to enter full-screen mode + this.vanillaOpts.fullscreen().set(this.window.isFullscreen()); + } + }, this.vanillaOpts.fullscreen()::get) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.fullscreen_resolution")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.fullscreen.resolution")) + .setTooltip(Component.translatable("sodium.options.fullscreen_resolution.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.resolution()) + .setRange(0, this.monitor != null ? this.monitor.getModeCount() : 0, 1) + .setDefaultValue(0) + .setBinding(value -> { + if (null != this.monitor) { + this.window.setPreferredFullscreenVideoMode(0 == value ? Optional.empty() : Optional.of(this.monitor.getMode(value - 1))); + } + }, () -> { + if (null == this.monitor) { + return 0; + } else { + Optional optional = this.window.getPreferredFullscreenVideoMode(); + return optional.map((videoMode) -> this.monitor.getVideoModeIndex(videoMode) + 1).orElse(0); + } + }) + .setEnabledProvider( + (state) -> this.monitor != null && + OsUtils.getOs() == OsUtils.OperatingSystem.WIN && + state.readBooleanOption(ResourceLocation.parse("sodium:general.fullscreen")), + ResourceLocation.parse("sodium:general.fullscreen")) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:general.vsync")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.vsync")) + .setTooltip(Component.translatable("sodium.options.v_sync.tooltip")) + .setDefaultValue(true) + .setBinding(this.vanillaOpts.enableVsync()::set, this.vanillaOpts.enableVsync()::get) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:general.framerate_limit")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.framerateLimit")) + .setTooltip(Component.translatable("sodium.options.fps_limit.tooltip")) + .setValueFormatter(ControlValueFormatterImpls.fpsLimit()) + .setRange(10, 260, 10) + .setDefaultValue(60) + .setBinding(this.vanillaOpts.framerateLimit()::set, this.vanillaOpts.framerateLimit()::get) + ) + ); + generalPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.literal("View Bobbing")) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:general.view_bobbing")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.viewBobbing")) + .setTooltip(Component.translatable("sodium.options.view_bobbing.tooltip")) + .setDefaultValue(true) + .setBinding(this.vanillaOpts.bobView()::set, this.vanillaOpts.bobView()::get) + ) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:general.attack_indicator"), AttackIndicatorStatus.class) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.attackIndicator")) + .setTooltip(Component.translatable("sodium.options.attack_indicator.tooltip")) + .setDefaultValue(AttackIndicatorStatus.CROSSHAIR) + .setElementNameProvider(AttackIndicatorStatus::getCaption) + .setBinding(this.vanillaOpts.attackIndicator()::set, this.vanillaOpts.attackIndicator()::get) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:general.autosave_indicator")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.autosaveIndicator")) + .setTooltip(Component.translatable("sodium.options.autosave_indicator.tooltip")) + .setDefaultValue(true) + .setBinding(this.vanillaOpts.showAutosaveIndicator()::set, this.vanillaOpts.showAutosaveIndicator()::get) + ) + ); + return generalPage; + } + + private OptionPageBuilder buildQualityPage(ConfigBuilder builder) { + var qualityPage = builder.createOptionPage().setName(Component.translatable("sodium.options.pages.quality")); + + qualityPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("options.graphics")) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:quality.graphics"), GraphicsStatus.class) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.graphics")) + .setTooltip(Component.translatable("sodium.options.graphics_quality.tooltip")) + .setElementNameProvider(EnumOptionBuilder.nameProviderFrom( + Component.translatable("options.graphics.fast"), + Component.translatable("options.graphics.fancy"), + Component.translatable("options.graphics.fabulous"))) + .setDefaultValue(GraphicsStatus.FANCY) + .setBinding(this.vanillaOpts.graphicsMode()::set, this.vanillaOpts.graphicsMode()::get) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + ); + + qualityPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("options.renderClouds")) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:quality.clouds"), CloudStatus.class) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.renderClouds")) + .setTooltip(Component.translatable("sodium.options.clouds_quality.tooltip")) + .setElementNameProvider(EnumOptionBuilder.nameProviderFrom( + Component.translatable("options.off"), + Component.translatable("options.graphics.fast"), + Component.translatable("options.graphics.fancy"))) + .setDefaultValue(CloudStatus.FANCY) + .setBinding(this.vanillaOpts.cloudStatus()::set, this.vanillaOpts.cloudStatus()::get) + .setImpact(OptionImpact.LOW) + ) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:quality.weather"), SodiumOptions.GraphicsQuality.class) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("soundCategory.weather")) + .setTooltip(Component.translatable("sodium.options.weather_quality.tooltip")) + .setDefaultValue(DEFAULTS.quality.weatherQuality) + .setBinding(value -> this.sodiumOpts.quality.weatherQuality = value, () -> this.sodiumOpts.quality.weatherQuality) + .setImpact(OptionImpact.MEDIUM) + ) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:quality.leaves"), SodiumOptions.GraphicsQuality.class) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.leaves_quality.name")) + .setTooltip(Component.translatable("sodium.options.leaves_quality.tooltip")) + .setDefaultValue(DEFAULTS.quality.leavesQuality) + .setBinding(value -> this.sodiumOpts.quality.leavesQuality = value, () -> this.sodiumOpts.quality.leavesQuality) + .setImpact(OptionImpact.MEDIUM) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:quality.particles"), ParticleStatus.class) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.particles")) + .setTooltip(Component.translatable("sodium.options.particle_quality.tooltip")) + .setElementNameProvider(EnumOptionBuilder.nameProviderFrom( + Component.translatable("options.particles.all"), + Component.translatable("options.particles.decreased"), + Component.translatable("options.particles.minimal") + )) + .setDefaultValue(ParticleStatus.ALL) + .setBinding(this.vanillaOpts.particles()::set, this.vanillaOpts.particles()::get) + .setImpact(OptionImpact.MEDIUM) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:quality.ao")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.ao")) + .setTooltip(Component.translatable("sodium.options.smooth_lighting.tooltip")) + .setDefaultValue(true) + .setBinding(this.vanillaOpts.ambientOcclusion()::set, this.vanillaOpts.ambientOcclusion()::get) + .setImpact(OptionImpact.LOW) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:quality.biome_blend")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.biomeBlendRadius")) + .setValueFormatter(ControlValueFormatterImpls.biomeBlend()) + .setTooltip(Component.translatable("sodium.options.biome_blend.tooltip")) + .setRange(1, 7, 1) + .setDefaultValue(2) + .setBinding(this.vanillaOpts.biomeBlendRadius()::set, this.vanillaOpts.biomeBlendRadius()::get) + .setImpact(OptionImpact.LOW) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:quality.entity_distance")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.entityDistanceScaling")) + .setValueFormatter(ControlValueFormatterImpls.percentage()) + .setTooltip(Component.translatable("sodium.options.entity_distance.tooltip")) + .setRange(50, 500, 25) + .setDefaultValue(100) + .setBinding((value) -> this.vanillaOpts.entityDistanceScaling().set(value / 100.0), () -> Math.round(this.vanillaOpts.entityDistanceScaling().get().floatValue() * 100.0F)) + .setImpact(OptionImpact.HIGH) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:quality.entity_shadows")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.entityShadows")) + .setTooltip(Component.translatable("sodium.options.entity_shadows.tooltip")) + .setDefaultValue(true) + .setBinding(this.vanillaOpts.entityShadows()::set, this.vanillaOpts.entityShadows()::get) + .setImpact(OptionImpact.MEDIUM) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:quality.vignette")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.vignette.name")) + .setTooltip(Component.translatable("sodium.options.vignette.tooltip")) + .setDefaultValue(DEFAULTS.quality.enableVignette) + .setBinding(value -> this.sodiumOpts.quality.enableVignette = value, () -> this.sodiumOpts.quality.enableVignette) + ) + ); + + qualityPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("options.mipmapLevels")) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:quality.mipmap_levels")) + .setStorageHandler(this.vanillaStorage) + .setName(Component.translatable("options.mipmapLevels")) + .setValueFormatter(ControlValueFormatterImpls.multiplier()) + .setTooltip(Component.translatable("sodium.options.mipmap_levels.tooltip")) + .setRange(0, 4, 1) + .setDefaultValue(4) + .setBinding(this.vanillaOpts.mipmapLevels()::set, this.vanillaOpts.mipmapLevels()::get) + .setImpact(OptionImpact.MEDIUM) + .setFlags(OptionFlag.REQUIRES_ASSET_RELOAD) + ) + ); + return qualityPage; + } + + private OptionPageBuilder buildPerformancePage(ConfigBuilder builder) { + var performancePage = builder.createOptionPage().setName(Component.translatable("sodium.options.pages.performance")); + + performancePage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("sodium.options.chunk_update_threads.name")) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:performance.chunk_update_threads")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.chunk_update_threads.name")) + .setValueFormatter(ControlValueFormatterImpls.quantityOrDisabled("threads", "Default")) + .setTooltip(Component.translatable("sodium.options.chunk_update_threads.tooltip")) + .setRange(0, Runtime.getRuntime().availableProcessors(), 1) + .setDefaultValue(DEFAULTS.performance.chunkBuilderThreads) + .setBinding(value -> this.sodiumOpts.performance.chunkBuilderThreads = value, () -> this.sodiumOpts.performance.chunkBuilderThreads) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.always_defer_chunk_updates")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.always_defer_chunk_updates.name")) + .setTooltip(Component.translatable("sodium.options.always_defer_chunk_updates.tooltip")) + .setDefaultValue(DEFAULTS.performance.alwaysDeferChunkUpdates) + .setBinding(value -> this.sodiumOpts.performance.alwaysDeferChunkUpdates = value, () -> this.sodiumOpts.performance.alwaysDeferChunkUpdates) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) + ) + ); + + performancePage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("sodium.options.use_block_face_culling.name")) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.use_block_face_culling")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.use_block_face_culling.name")) + .setTooltip(Component.translatable("sodium.options.use_block_face_culling.tooltip")) + .setDefaultValue(DEFAULTS.performance.useBlockFaceCulling) + .setBinding(value -> this.sodiumOpts.performance.useBlockFaceCulling = value, () -> this.sodiumOpts.performance.useBlockFaceCulling) + .setImpact(OptionImpact.MEDIUM) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.use_fog_occlusion")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.use_fog_occlusion.name")) + .setTooltip(Component.translatable("sodium.options.use_fog_occlusion.tooltip")) + .setDefaultValue(DEFAULTS.performance.useFogOcclusion) + .setBinding(value -> this.sodiumOpts.performance.useFogOcclusion = value, () -> this.sodiumOpts.performance.useFogOcclusion) + .setImpact(OptionImpact.MEDIUM) + .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.use_entity_culling")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.use_entity_culling.name")) + .setTooltip(Component.translatable("sodium.options.use_entity_culling.tooltip")) + .setDefaultValue(DEFAULTS.performance.useEntityCulling) + .setBinding(value -> this.sodiumOpts.performance.useEntityCulling = value, () -> this.sodiumOpts.performance.useEntityCulling) + .setImpact(OptionImpact.MEDIUM) + ) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.animate_only_visible_textures")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.animate_only_visible_textures.name")) + .setTooltip(Component.translatable("sodium.options.animate_only_visible_textures.tooltip")) + .setDefaultValue(DEFAULTS.performance.animateOnlyVisibleTextures) + .setBinding(value -> this.sodiumOpts.performance.animateOnlyVisibleTextures = value, () -> this.sodiumOpts.performance.animateOnlyVisibleTextures) + .setImpact(OptionImpact.HIGH) + .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) + ) + .addOption( + this.buildNoErrorContextOption(builder) + ) + ); + + if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { + performancePage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("sodium.options.sort_behavior.name")) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:performance.sort_behavior")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.sort_behavior.name")) + .setTooltip(Component.translatable("sodium.options.sort_behavior.tooltip")) + .setDefaultValue(DEFAULTS.performance.sortingEnabled) + .setBinding(value -> this.sodiumOpts.performance.sortingEnabled = value, () -> this.sodiumOpts.performance.sortingEnabled) + .setImpact(OptionImpact.LOW) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + ); + } + return performancePage; + } + + private OptionBuilder buildNoErrorContextOption(ConfigBuilder builder) { + return builder.createBooleanOption(ResourceLocation.parse("sodium:performance.use_no_error_context")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.use_no_error_context.name")) + .setTooltip(Component.translatable("sodium.options.use_no_error_context.tooltip")) + .setDefaultValue(DEFAULTS.performance.useNoErrorGLContext) + .setBinding(value -> this.sodiumOpts.performance.useNoErrorGLContext = value, () -> this.sodiumOpts.performance.useNoErrorGLContext) + .setEnabledProvider((state) -> { + GLCapabilities capabilities = GL.getCapabilities(); + return (capabilities.OpenGL46 || capabilities.GL_KHR_no_error) + && !Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED); + }) + .setImpact(OptionImpact.LOW) + .setFlags(OptionFlag.REQUIRES_GAME_RESTART); + } + + private OptionPageBuilder buildAdvancedPage(ConfigBuilder builder) { + var advancedPage = builder.createOptionPage().setName(Component.translatable("sodium.options.pages.advanced")); + + boolean isPersistentMappingSupported = MappedStagingBuffer.isSupported(RenderDevice.INSTANCE); + + advancedPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("sodium.options.use_persistent_mapping.name")) + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:advanced.use_persistent_mapping")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.use_persistent_mapping.name")) + .setTooltip(Component.translatable("sodium.options.use_persistent_mapping.tooltip")) + .setDefaultValue(DEFAULTS.advanced.useAdvancedStagingBuffers) + .setBinding(value -> this.sodiumOpts.advanced.useAdvancedStagingBuffers = value, () -> this.sodiumOpts.advanced.useAdvancedStagingBuffers) + .setEnabled(isPersistentMappingSupported) + .setImpact(OptionImpact.MEDIUM) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + ); + + advancedPage.addOptionGroup(builder.createOptionGroup() + .setName(Component.translatable("sodium.options.cpu_render_ahead_limit.name")) + .addOption( + builder.createIntegerOption(ResourceLocation.parse("sodium:advanced.cpu_render_ahead_limit")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.cpu_render_ahead_limit.name")) + .setValueFormatter(ControlValueFormatterImpls.translateVariable("sodium.options.cpu_render_ahead_limit.value")) + .setTooltip(Component.translatable("sodium.options.cpu_render_ahead_limit.tooltip")) + .setRange(0, 9, 1) + .setDefaultValue(DEFAULTS.advanced.cpuRenderAheadLimit) + .setBinding(value -> this.sodiumOpts.advanced.cpuRenderAheadLimit = value, () -> this.sodiumOpts.advanced.cpuRenderAheadLimit) + ) + ); + return advancedPage; + } + +} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java deleted file mode 100644 index b97e3fa433..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ /dev/null @@ -1,391 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui; - -import com.google.common.collect.ImmutableList; -import com.mojang.blaze3d.pipeline.RenderTarget; -import com.mojang.blaze3d.platform.Monitor; -import com.mojang.blaze3d.platform.VideoMode; -import com.mojang.blaze3d.platform.Window; -import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; -import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; -import net.caffeinemc.mods.sodium.client.gui.options.*; -import net.caffeinemc.mods.sodium.client.gui.options.binding.compat.VanillaBooleanOptionBinding; -import net.caffeinemc.mods.sodium.client.gui.options.control.*; -import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage; -import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; -import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; -import net.minecraft.client.AttackIndicatorStatus; -import net.minecraft.client.CloudStatus; -import net.minecraft.client.GraphicsStatus; -import net.minecraft.client.Minecraft; -import net.minecraft.client.ParticleStatus; -import net.minecraft.network.chat.Component; -import org.lwjgl.opengl.GL; -import org.lwjgl.opengl.GLCapabilities; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -// TODO: Rename in Sodium 0.6 -public class SodiumGameOptionPages { - private static final SodiumOptionsStorage sodiumOpts = new SodiumOptionsStorage(); - private static final MinecraftOptionsStorage vanillaOpts = new MinecraftOptionsStorage(); - private static final Window window = Minecraft.getInstance().getWindow(); - - public static OptionPage general() { - Monitor monitor = window.findBestMonitor(); - List groups = new ArrayList<>(); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.renderDistance")) - .setTooltip(Component.translatable("sodium.options.view_distance.tooltip")) - .setControl(option -> new SliderControl(option, 2, 32, 1, ControlValueFormatter.translateVariable("options.chunks"))) - .setBinding((options, value) -> options.renderDistance().set(value), options -> options.renderDistance().get()) - .setImpact(OptionImpact.HIGH) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.simulationDistance")) - .setTooltip(Component.translatable("sodium.options.simulation_distance.tooltip")) - .setControl(option -> new SliderControl(option, 5, 32, 1, ControlValueFormatter.translateVariable("options.chunks"))) - .setBinding((options, value) -> options.simulationDistance().set(value), options -> options.simulationDistance().get()) - .setImpact(OptionImpact.HIGH) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.gamma")) - .setTooltip(Component.translatable("sodium.options.brightness.tooltip")) - .setControl(opt -> new SliderControl(opt, 0, 100, 1, ControlValueFormatter.brightness())) - .setBinding((opts, value) -> opts.gamma().set(value * 0.01D), (opts) -> (int) (opts.gamma().get() / 0.01D)) - .build()) - .build()); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.guiScale")) - .setTooltip(Component.translatable("sodium.options.gui_scale.tooltip")) - .setControl(option -> new SliderControl(option, 0, Minecraft.getInstance().getWindow().calculateScale(0, Minecraft.getInstance().isEnforceUnicode()), 1, ControlValueFormatter.guiScale())) - .setBinding((opts, value) -> { - opts.guiScale().set(value); - - Minecraft client = Minecraft.getInstance(); - client.resizeDisplay(); - }, opts -> opts.guiScale().get()) - .build()) - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.fullscreen")) - .setTooltip(Component.translatable("sodium.options.fullscreen.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> { - opts.fullscreen().set(value); - - Minecraft client = Minecraft.getInstance(); - Window window = client.getWindow(); - - if (window != null && window.isFullscreen() != opts.fullscreen().get()) { - window.toggleFullScreen(); - - // The client might not be able to enter full-screen mode - opts.fullscreen().set(window.isFullscreen()); - } - }, (opts) -> opts.fullscreen().get()) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.fullscreen.resolution")) - .setTooltip(Component.translatable("sodium.options.fullscreen_resolution.tooltip")) - .setControl(option -> new SliderControl(option, 0, null != monitor? monitor.getModeCount(): 0, 1, ControlValueFormatter.resolution())) - .setBinding((options, value) -> { - if (null != monitor) { - window.setPreferredFullscreenVideoMode(0 == value? Optional.empty(): Optional.of(monitor.getMode(value - 1))); - } - }, options -> { - if (null == monitor) { - return 0; - } - else { - Optional optional = window.getPreferredFullscreenVideoMode(); - return optional.map((videoMode) -> monitor.getVideoModeIndex(videoMode) + 1).orElse(0); - } - }) - .setEnabled(() -> OsUtils.getOs() == OsUtils.OperatingSystem.WIN && Minecraft.getInstance().getWindow().findBestMonitor() != null) - .setFlags(OptionFlag.REQUIRES_VIDEOMODE_RELOAD) - .build()) - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.vsync")) - .setTooltip(Component.translatable("sodium.options.v_sync.tooltip")) - .setControl(TickBoxControl::new) - .setBinding(new VanillaBooleanOptionBinding(Minecraft.getInstance().options.enableVsync())) - .setImpact(OptionImpact.VARIES) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.framerateLimit")) - .setTooltip(Component.translatable("sodium.options.fps_limit.tooltip")) - .setControl(option -> new SliderControl(option, 10, 260, 10, ControlValueFormatter.fpsLimit())) - .setBinding((opts, value) -> { - opts.framerateLimit().set(value); - Minecraft.getInstance().getWindow().setFramerateLimit(value); - }, opts -> opts.framerateLimit().get()) - .build()) - .build()); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.viewBobbing")) - .setTooltip(Component.translatable("sodium.options.view_bobbing.tooltip")) - .setControl(TickBoxControl::new) - .setBinding(new VanillaBooleanOptionBinding(Minecraft.getInstance().options.bobView())) - .build()) - .add(OptionImpl.createBuilder(AttackIndicatorStatus.class, vanillaOpts) - .setName(Component.translatable("options.attackIndicator")) - .setTooltip(Component.translatable("sodium.options.attack_indicator.tooltip")) - .setControl(opts -> new CyclingControl<>(opts, AttackIndicatorStatus.class, new Component[] { Component.translatable("options.off"), Component.translatable("options.attack.crosshair"), Component.translatable("options.attack.hotbar") })) - .setBinding((opts, value) -> opts.attackIndicator().set(value), (opts) -> opts.attackIndicator().get()) - .build()) - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.autosaveIndicator")) - .setTooltip(Component.translatable("sodium.options.autosave_indicator.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.showAutosaveIndicator().set(value), opts -> opts.showAutosaveIndicator().get()) - .build()) - .build()); - - return new OptionPage(Component.translatable("stat.generalButton"), ImmutableList.copyOf(groups)); - } - - public static OptionPage quality() { - List groups = new ArrayList<>(); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(GraphicsStatus.class, vanillaOpts) - .setName(Component.translatable("options.graphics")) - .setTooltip(Component.translatable("sodium.options.graphics_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, GraphicsStatus.class, new Component[] { Component.translatable("options.graphics.fast"), Component.translatable("options.graphics.fancy"), Component.translatable("options.graphics.fabulous") })) - .setBinding( - (opts, value) -> opts.graphicsMode().set(value), - opts -> opts.graphicsMode().get()) - .setImpact(OptionImpact.HIGH) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .build()); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(CloudStatus.class, vanillaOpts) - .setName(Component.translatable("options.renderClouds")) - .setTooltip(Component.translatable("sodium.options.clouds_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, CloudStatus.class, new Component[] { Component.translatable("options.off"), Component.translatable("options.graphics.fast"), Component.translatable("options.graphics.fancy") })) - .setBinding((opts, value) -> { - opts.cloudStatus().set(value); - - if (Minecraft.useShaderTransparency()) { - RenderTarget framebuffer = Minecraft.getInstance().levelRenderer.getCloudsTarget(); - if (framebuffer != null) { - framebuffer.clear(Minecraft.ON_OSX); - } - } - }, opts -> opts.cloudStatus().get()) - .setImpact(OptionImpact.LOW) - .build()) - .add(OptionImpl.createBuilder(SodiumGameOptions.GraphicsQuality.class, sodiumOpts) - .setName(Component.translatable("soundCategory.weather")) - .setTooltip(Component.translatable("sodium.options.weather_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.GraphicsQuality.class)) - .setBinding((opts, value) -> opts.quality.weatherQuality = value, opts -> opts.quality.weatherQuality) - .setImpact(OptionImpact.MEDIUM) - .build()) - .add(OptionImpl.createBuilder(SodiumGameOptions.GraphicsQuality.class, sodiumOpts) - .setName(Component.translatable("sodium.options.leaves_quality.name")) - .setTooltip(Component.translatable("sodium.options.leaves_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.GraphicsQuality.class)) - .setBinding((opts, value) -> opts.quality.leavesQuality = value, opts -> opts.quality.leavesQuality) - .setImpact(OptionImpact.MEDIUM) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .add(OptionImpl.createBuilder(ParticleStatus.class, vanillaOpts) - .setName(Component.translatable("options.particles")) - .setTooltip(Component.translatable("sodium.options.particle_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, ParticleStatus.class, new Component[] { Component.translatable("options.particles.all"), Component.translatable("options.particles.decreased"), Component.translatable("options.particles.minimal") })) - .setBinding((opts, value) -> opts.particles().set(value), (opts) -> opts.particles().get()) - .setImpact(OptionImpact.MEDIUM) - .build()) - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.ao")) - .setTooltip(Component.translatable("sodium.options.smooth_lighting.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.ambientOcclusion().set(value), opts -> opts.ambientOcclusion().get()) - .setImpact(OptionImpact.LOW) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.biomeBlendRadius")) - .setTooltip(Component.translatable("sodium.options.biome_blend.tooltip")) - .setControl(option -> new SliderControl(option, 1, 7, 1, ControlValueFormatter.biomeBlend())) - .setBinding((opts, value) -> opts.biomeBlendRadius().set(value), opts -> opts.biomeBlendRadius().get()) - .setImpact(OptionImpact.LOW) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.entityDistanceScaling")) - .setTooltip(Component.translatable("sodium.options.entity_distance.tooltip")) - .setControl(option -> new SliderControl(option, 50, 500, 25, ControlValueFormatter.percentage())) - .setBinding((opts, value) -> opts.entityDistanceScaling().set(value / 100.0), opts -> Math.round(opts.entityDistanceScaling().get().floatValue() * 100.0F)) - .setImpact(OptionImpact.HIGH) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, vanillaOpts) - .setName(Component.translatable("options.entityShadows")) - .setTooltip(Component.translatable("sodium.options.entity_shadows.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.entityShadows().set(value), opts -> opts.entityShadows().get()) - .setImpact(OptionImpact.MEDIUM) - .build()) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.vignette.name")) - .setTooltip(Component.translatable("sodium.options.vignette.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.quality.enableVignette = value, opts -> opts.quality.enableVignette) - .build()) - .build()); - - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(int.class, vanillaOpts) - .setName(Component.translatable("options.mipmapLevels")) - .setTooltip(Component.translatable("sodium.options.mipmap_levels.tooltip")) - .setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier())) - .setBinding((opts, value) -> opts.mipmapLevels().set(value), opts -> opts.mipmapLevels().get()) - .setImpact(OptionImpact.MEDIUM) - .setFlags(OptionFlag.REQUIRES_ASSET_RELOAD) - .build()) - .build()); - - - return new OptionPage(Component.translatable("sodium.options.pages.quality"), ImmutableList.copyOf(groups)); - } - - public static OptionPage performance() { - List groups = new ArrayList<>(); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(int.class, sodiumOpts) - .setName(Component.translatable("sodium.options.chunk_update_threads.name")) - .setTooltip(Component.translatable("sodium.options.chunk_update_threads.tooltip")) - .setControl(o -> new SliderControl(o, 0, Runtime.getRuntime().availableProcessors(), 1, ControlValueFormatter.quantityOrDisabled("threads", "Default"))) - .setImpact(OptionImpact.HIGH) - .setBinding((opts, value) -> opts.performance.chunkBuilderThreads = value, opts -> opts.performance.chunkBuilderThreads) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.always_defer_chunk_updates.name")) - .setTooltip(Component.translatable("sodium.options.always_defer_chunk_updates.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.HIGH) - .setBinding((opts, value) -> opts.performance.alwaysDeferChunkUpdates = value, opts -> opts.performance.alwaysDeferChunkUpdates) - .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) - .build()) - .build() - ); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.use_block_face_culling.name")) - .setTooltip(Component.translatable("sodium.options.use_block_face_culling.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.MEDIUM) - .setBinding((opts, value) -> opts.performance.useBlockFaceCulling = value, opts -> opts.performance.useBlockFaceCulling) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.use_fog_occlusion.name")) - .setTooltip(Component.translatable("sodium.options.use_fog_occlusion.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.performance.useFogOcclusion = value, opts -> opts.performance.useFogOcclusion) - .setImpact(OptionImpact.MEDIUM) - .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.use_entity_culling.name")) - .setTooltip(Component.translatable("sodium.options.use_entity_culling.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.MEDIUM) - .setBinding((opts, value) -> opts.performance.useEntityCulling = value, opts -> opts.performance.useEntityCulling) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.animate_only_visible_textures.name")) - .setTooltip(Component.translatable("sodium.options.animate_only_visible_textures.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.HIGH) - .setBinding((opts, value) -> opts.performance.animateOnlyVisibleTextures = value, opts -> opts.performance.animateOnlyVisibleTextures) - .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) - .build() - ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.use_no_error_context.name")) - .setTooltip(Component.translatable("sodium.options.use_no_error_context.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.LOW) - .setBinding((opts, value) -> opts.performance.useNoErrorGLContext = value, opts -> opts.performance.useNoErrorGLContext) - .setEnabled(SodiumGameOptionPages::supportsNoErrorContext) - .setFlags(OptionFlag.REQUIRES_GAME_RESTART) - .build()) - .build()); - - if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.sort_behavior.name")) - .setTooltip(Component.translatable("sodium.options.sort_behavior.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.performance.sortingEnabled = value, opts -> opts.performance.sortingEnabled) - .setImpact(OptionImpact.LOW) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .build()); - } - - return new OptionPage(Component.translatable("sodium.options.pages.performance"), ImmutableList.copyOf(groups)); - } - - private static boolean supportsNoErrorContext() { - GLCapabilities capabilities = GL.getCapabilities(); - return (capabilities.OpenGL46 || capabilities.GL_KHR_no_error) - && !Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED); - } - - public static OptionPage advanced() { - List groups = new ArrayList<>(); - - boolean isPersistentMappingSupported = MappedStagingBuffer.isSupported(RenderDevice.INSTANCE); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.use_persistent_mapping.name")) - .setTooltip(Component.translatable("sodium.options.use_persistent_mapping.tooltip")) - .setControl(TickBoxControl::new) - .setImpact(OptionImpact.MEDIUM) - .setEnabled(() -> isPersistentMappingSupported) - .setBinding((opts, value) -> opts.advanced.useAdvancedStagingBuffers = value, opts -> opts.advanced.useAdvancedStagingBuffers) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build() - ) - .build()); - - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(int.class, sodiumOpts) - .setName(Component.translatable("sodium.options.cpu_render_ahead_limit.name")) - .setTooltip(Component.translatable("sodium.options.cpu_render_ahead_limit.tooltip")) - .setControl(opt -> new SliderControl(opt, 0, 9, 1, ControlValueFormatter.translateVariable("sodium.options.cpu_render_ahead_limit.value"))) - .setBinding((opts, value) -> opts.advanced.cpuRenderAheadLimit = value, opts -> opts.advanced.cpuRenderAheadLimit) - .build() - ) - .build()); - - return new OptionPage(Component.translatable("sodium.options.pages.advanced"), ImmutableList.copyOf(groups)); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java similarity index 90% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java rename to common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java index ec681430b8..09bc59ee6b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java @@ -16,8 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; -// TODO: Rename in Sodium 0.6 -public class SodiumGameOptions { +public class SodiumOptions { private static final String DEFAULT_FILE_NAME = "sodium-options.json"; public final QualitySettings quality = new QualitySettings(); @@ -27,12 +26,12 @@ public class SodiumGameOptions { private boolean readOnly; - private SodiumGameOptions() { + private SodiumOptions() { // NO-OP } - public static SodiumGameOptions defaults() { - return new SodiumGameOptions(); + public static SodiumOptions defaults() { + return new SodiumOptions(); } public static class PerformanceSettings { @@ -100,18 +99,18 @@ public boolean isFancy(GraphicsStatus graphicsStatus) { .excludeFieldsWithModifiers(Modifier.PRIVATE) .create(); - public static SodiumGameOptions loadFromDisk() { + public static SodiumOptions loadFromDisk() { Path path = getConfigPath(); - SodiumGameOptions config; + SodiumOptions config; if (Files.exists(path)) { try (FileReader reader = new FileReader(path.toFile())) { - config = GSON.fromJson(reader, SodiumGameOptions.class); + config = GSON.fromJson(reader, SodiumOptions.class); } catch (IOException e) { throw new RuntimeException("Could not parse config", e); } } else { - config = new SodiumGameOptions(); + config = new SodiumOptions(); } try { @@ -128,7 +127,7 @@ private static Path getConfigPath() { .resolve(DEFAULT_FILE_NAME); } - public static void writeToDisk(SodiumGameOptions config) throws IOException { + public static void writeToDisk(SodiumOptions config) throws IOException { if (config.isReadOnly()) { throw new IllegalStateException("Config file is read-only"); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java similarity index 80% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java rename to common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 1c40530c85..48b4c0c772 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -1,14 +1,18 @@ package net.caffeinemc.mods.sodium.client.gui; import com.mojang.blaze3d.systems.RenderSystem; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.*; +import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.config.structure.OptionGroup; +import net.caffeinemc.mods.sodium.client.config.structure.OptionPage; import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import net.caffeinemc.mods.sodium.client.gui.options.*; import net.caffeinemc.mods.sodium.client.gui.options.control.Control; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlElement; -import net.caffeinemc.mods.sodium.client.gui.options.storage.OptionStorage; import net.caffeinemc.mods.sodium.client.gui.prompt.ScreenPrompt; import net.caffeinemc.mods.sodium.client.gui.prompt.ScreenPromptable; import net.caffeinemc.mods.sodium.client.gui.screen.ConfigCorruptedScreen; @@ -22,11 +26,11 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.options.VideoSettingsScreen; import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.util.FormattedCharSequence; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; @@ -34,15 +38,18 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashSet; import java.util.List; import java.util.stream.Stream; -// TODO: Rename in Sodium 0.6 -public class SodiumOptionsGUI extends Screen implements ScreenPromptable { - private final List pages = new ArrayList<>(); - +// TODO: scrolling in the page list, the options themselves, and the tooltip if necessary +// TODO: make the search bar work +// TODO: constrain the tooltip to its safe area if it's too big, then show a scroll bar if it's still too big +// TODO: wrap options within groups in two columns +// TODO: stop the options from overlapping the bottom or top buttons +// TODO: make the mod config headers interactive: only show one mod's pages at a time, click on a mod header to open that mod's first settings page and close the previous mod's page list +// TODO: the donation button is gone when the search button is clicked? +// TODO: "setting unavailable" overlaps with the label, even though the label should be automatically truncated to fit +public class VideoSettingsScreen extends Screen implements ScreenPromptable { private final List> controls = new ArrayList<>(); private final Screen prevScreen; @@ -58,16 +65,11 @@ public class SodiumOptionsGUI extends Screen implements ScreenPromptable { private @Nullable ScreenPrompt prompt; private FlatButtonWidget searchButton; - private SodiumOptionsGUI(Screen prevScreen) { + private VideoSettingsScreen(Screen prevScreen) { super(Component.literal("Sodium Renderer Settings")); this.prevScreen = prevScreen; - this.pages.add(SodiumGameOptionPages.general()); - this.pages.add(SodiumGameOptionPages.quality()); - this.pages.add(SodiumGameOptionPages.performance()); - this.pages.add(SodiumGameOptionPages.advanced()); - this.checkPromptTimers(); } @@ -109,7 +111,7 @@ private void checkPromptTimers() { } } - private void openDonationPrompt(SodiumGameOptions options) { + private void openDonationPrompt(SodiumOptions options) { var prompt = new ScreenPrompt(this, DONATION_PROMPT_MESSAGE, 320, 190, new ScreenPrompt.Action(Component.literal("Buy us a coffee"), this::openDonationPage)); prompt.setFocused(true); @@ -117,7 +119,7 @@ private void openDonationPrompt(SodiumGameOptions options) { options.notifications.hasSeenDonationPrompt = true; try { - SodiumGameOptions.writeToDisk(options); + SodiumOptions.writeToDisk(options); } catch (IOException e) { SodiumClientMod.logger() .error("Failed to update config file", e); @@ -126,9 +128,9 @@ private void openDonationPrompt(SodiumGameOptions options) { public static Screen createScreen(Screen currentScreen) { if (SodiumClientMod.options().isReadOnly()) { - return new ConfigCorruptedScreen(currentScreen, SodiumOptionsGUI::new); + return new ConfigCorruptedScreen(currentScreen, VideoSettingsScreen::new); } else { - return new SodiumOptionsGUI(currentScreen); + return new VideoSettingsScreen(currentScreen); } } @@ -155,12 +157,13 @@ private void rebuildGUI() { this.clearWidgets(); if (this.currentPage == null) { - if (this.pages.isEmpty()) { - throw new IllegalStateException("No pages are available?!"); - } +// if (this.pages.isEmpty()) { +// throw new IllegalStateException("No pages are available?!"); +// } // Just use the first page for now - this.currentPage = this.pages.get(0); + // TODO: fix + this.currentPage = ConfigManager.CONFIG.getModConfigs().getFirst().pages().getFirst(); } this.rebuildGUIPages(); @@ -191,11 +194,11 @@ private void setDonationButtonVisibility(boolean value) { } private void hideDonationButton() { - SodiumGameOptions options = SodiumClientMod.options(); + SodiumOptions options = SodiumClientMod.options(); options.notifications.hasClearedDonationButton = true; try { - SodiumGameOptions.writeToDisk(options); + SodiumOptions.writeToDisk(options); } catch (IOException e) { throw new RuntimeException("Failed to save configuration", e); } @@ -208,20 +211,22 @@ private void rebuildGUIPages() { int y = 5; int width = 125; - CenteredFlatWidget header = new CenteredFlatWidget(new Dim2i(x, y, width, font.lineHeight * 2), Component.literal("Sodium Renderer"), () -> {}, false); + for (var modConfig : ConfigManager.CONFIG.getModConfigs()) { + CenteredFlatWidget header = new CenteredFlatWidget(new Dim2i(x, y, width, this.font.lineHeight * 2), Component.literal(modConfig.name()), () -> { + }, false); - y += font.lineHeight * 2; - - this.addRenderableWidget(header); + y += this.font.lineHeight * 2; - for (OptionPage page : this.pages) { + this.addRenderableWidget(header); - CenteredFlatWidget button = new CenteredFlatWidget(new Dim2i(x, y, width, font.lineHeight * 2), page.getName(), () -> this.setPage(page), true); - button.setSelected(this.currentPage == page); + for (OptionPage page : modConfig.pages()) { + CenteredFlatWidget button = new CenteredFlatWidget(new Dim2i(x, y, width, this.font.lineHeight * 2), page.name(), () -> this.setPage(page), true); + button.setSelected(this.currentPage == page); - y += font.lineHeight * 2; + y += this.font.lineHeight * 2; - this.addRenderableWidget(button); + this.addRenderableWidget(button); + } } /* @@ -241,14 +246,14 @@ private void rebuildGUIPages() { } private int rebuildGUIOptions() { - int x = (int) (130); + int x = 130; int y = 23; - for (OptionGroup group : this.currentPage.getGroups()) { + for (OptionGroup group : this.currentPage.groups()) { // Add each option's control element - for (Option option : group.getOptions()) { + for (Option option : group.options()) { Control control = option.getControl(); - ControlElement element = control.createElement(new Dim2i(x, y, 240, 18)); + ControlElement element = control.createElement(new Dim2i(x, y, 200, 18)); this.addRenderableWidget(element); @@ -266,10 +271,9 @@ private int rebuildGUIOptions() { } @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.updateControls(); - super.render(graphics, this.prompt != null ? -1 : mouseX, this.prompt != null ? -1 : mouseY, delta); if (this.hoveredElement != null) { @@ -297,16 +301,7 @@ private void updateControls() { .findFirst() .orElse(null)); - boolean hasChanges = this.getAllOptions() - .anyMatch(Option::hasChanged); - - for (OptionPage page : this.pages) { - for (Option option : page.getOptions()) { - if (option.hasChanged()) { - hasChanges = true; - } - } - } + boolean hasChanges = ConfigManager.CONFIG.anyOptionChanged(); this.applyButton.setEnabled(hasChanges); this.undoButton.setVisible(hasChanges); @@ -316,11 +311,6 @@ private void updateControls() { this.hoveredElement = hovered; } - private Stream> getAllOptions() { - return this.pages.stream() - .flatMap(s -> s.getOptions().stream()); - } - private Stream> getActiveControls() { return this.controls.stream(); } @@ -342,7 +332,7 @@ private void renderOptionTooltip(GuiGraphics graphics, ControlElement element OptionImpact impact = option.getImpact(); if (impact != null) { - tooltip.add(Language.getInstance().getVisualOrder(Component.translatable("sodium.options.performance_impact_string", impact.getLocalizedName()).withStyle(ChatFormatting.GRAY))); + tooltip.add(Language.getInstance().getVisualOrder(Component.translatable("sodium.options.performance_impact_string", impact.getName()).withStyle(ChatFormatting.GRAY))); } int boxHeight = (tooltip.size() * 12) + boxPadding; @@ -362,19 +352,7 @@ private void renderOptionTooltip(GuiGraphics graphics, ControlElement element } private void applyChanges() { - final HashSet> dirtyStorages = new HashSet<>(); - final EnumSet flags = EnumSet.noneOf(OptionFlag.class); - - this.getAllOptions().forEach((option -> { - if (!option.hasChanged()) { - return; - } - - option.applyChanges(); - - flags.addAll(option.getFlags()); - dirtyStorages.add(option.getStorage()); - })); + var flags = ConfigManager.CONFIG.applyAllOptions(); Minecraft client = Minecraft.getInstance(); @@ -399,15 +377,10 @@ private void applyChanges() { Console.instance().logMessage(MessageLevel.WARN, "sodium.console.game_restart", true, 10.0); } - - for (OptionStorage storage : dirtyStorages) { - storage.save(); - } } private void undoChanges() { - this.getAllOptions() - .forEach(Option::reset); + ConfigManager.CONFIG.resetAllOptions(); } private void openDonationPage() { @@ -422,7 +395,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } if (this.prompt == null && keyCode == GLFW.GLFW_KEY_P && (modifiers & GLFW.GLFW_MOD_SHIFT) != 0) { - Minecraft.getInstance().setScreen(new VideoSettingsScreen(this.prevScreen, Minecraft.getInstance(), Minecraft.getInstance().options)); + Minecraft.getInstance().setScreen(new net.minecraft.client.gui.screens.options.VideoSettingsScreen(this.prevScreen, Minecraft.getInstance(), Minecraft.getInstance().options)); return true; } @@ -443,7 +416,12 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return true; } - return clicked; + return true; + } + + @Override + public boolean mouseScrolled(double d, double e, double f, double g) { + return super.mouseScrolled(d, e, f, g); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Option.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Option.java deleted file mode 100644 index 0b88b64478..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Option.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options; - -import net.caffeinemc.mods.sodium.client.gui.options.control.Control; -import net.caffeinemc.mods.sodium.client.gui.options.storage.OptionStorage; -import net.minecraft.network.chat.Component; -import java.util.Collection; - -public interface Option { - Component getName(); - - Component getTooltip(); - - OptionImpact getImpact(); - - Control getControl(); - - T getValue(); - - void setValue(T value); - - void reset(); - - OptionStorage getStorage(); - - boolean isAvailable(); - - boolean hasChanged(); - - void applyChanges(); - - Collection getFlags(); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionGroup.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionGroup.java deleted file mode 100644 index 88d21dc7d3..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionGroup.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options; - -import com.google.common.collect.ImmutableList; -import org.apache.commons.lang3.Validate; - -import java.util.ArrayList; -import java.util.List; - -public class OptionGroup { - private final ImmutableList> options; - - private OptionGroup(ImmutableList> options) { - this.options = options; - } - - public static Builder createBuilder() { - return new Builder(); - } - - public ImmutableList> getOptions() { - return this.options; - } - - public static class Builder { - private final List> options = new ArrayList<>(); - - public Builder add(Option option) { - this.options.add(option); - - return this; - } - - public OptionGroup build() { - Validate.notEmpty(this.options, "At least one option must be specified"); - - return new OptionGroup(ImmutableList.copyOf(this.options)); - } - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java deleted file mode 100644 index 893bea4363..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java +++ /dev/null @@ -1,204 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options; - -import net.caffeinemc.mods.sodium.client.gui.options.binding.GenericBinding; -import net.caffeinemc.mods.sodium.client.gui.options.binding.OptionBinding; -import net.caffeinemc.mods.sodium.client.gui.options.control.Control; -import net.caffeinemc.mods.sodium.client.gui.options.storage.OptionStorage; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; - -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.function.BiConsumer; -import java.util.function.BooleanSupplier; -import java.util.function.Function; - -public class OptionImpl implements Option { - private final OptionStorage storage; - - private final OptionBinding binding; - private final Control control; - - private final EnumSet flags; - - private final Component name; - private final Component tooltip; - - private final OptionImpact impact; - - private T value; - private T modifiedValue; - - private final BooleanSupplier enabled; - - private OptionImpl(OptionStorage storage, - Component name, - Component tooltip, - OptionBinding binding, - Function, Control> control, - EnumSet flags, - OptionImpact impact, - BooleanSupplier enabled) { - this.storage = storage; - this.name = name; - this.tooltip = tooltip; - this.binding = binding; - this.impact = impact; - this.flags = flags; - this.control = control.apply(this); - this.enabled = enabled; - - this.reset(); - } - - @Override - public Component getName() { - return this.name; - } - - @Override - public Component getTooltip() { - return this.tooltip; - } - - @Override - public OptionImpact getImpact() { - return this.impact; - } - - @Override - public Control getControl() { - return this.control; - } - - @Override - public T getValue() { - return this.modifiedValue; - } - - @Override - public void setValue(T value) { - this.modifiedValue = value; - } - - @Override - public void reset() { - this.value = this.binding.getValue(this.storage.getData()); - this.modifiedValue = this.value; - } - - @Override - public OptionStorage getStorage() { - return this.storage; - } - - @Override - public boolean isAvailable() { - return this.enabled.getAsBoolean(); - } - - @Override - public boolean hasChanged() { - return !this.value.equals(this.modifiedValue); - } - - @Override - public void applyChanges() { - this.binding.setValue(this.storage.getData(), this.modifiedValue); - this.value = this.modifiedValue; - } - - @Override - public Collection getFlags() { - return this.flags; - } - - public static OptionImpl.Builder createBuilder(@SuppressWarnings("unused") Class type, OptionStorage storage) { - return new Builder<>(storage); - } - - public static class Builder { - private final OptionStorage storage; - private Component name; - private Component tooltip; - private OptionBinding binding; - private Function, Control> control; - private OptionImpact impact; - private final EnumSet flags = EnumSet.noneOf(OptionFlag.class); - private BooleanSupplier enabled = () -> true; - - private Builder(OptionStorage storage) { - this.storage = storage; - } - - public Builder setName(Component name) { - Validate.notNull(name, "Argument must not be null"); - - this.name = name; - - return this; - } - - public Builder setTooltip(Component tooltip) { - Validate.notNull(tooltip, "Argument must not be null"); - - this.tooltip = tooltip; - - return this; - } - - public Builder setBinding(BiConsumer setter, Function getter) { - Validate.notNull(setter, "Setter must not be null"); - Validate.notNull(getter, "Getter must not be null"); - - this.binding = new GenericBinding<>(setter, getter); - - return this; - } - - - public Builder setBinding(OptionBinding binding) { - Validate.notNull(binding, "Argument must not be null"); - - this.binding = binding; - - return this; - } - - public Builder setControl(Function, Control> control) { - Validate.notNull(control, "Argument must not be null"); - - this.control = control; - - return this; - } - - public Builder setImpact(OptionImpact impact) { - this.impact = impact; - - return this; - } - - public Builder setEnabled(BooleanSupplier value) { - this.enabled = value; - - return this; - } - - public Builder setFlags(OptionFlag... flags) { - Collections.addAll(this.flags, flags); - - return this; - } - - public OptionImpl build() { - Validate.notNull(this.name, "Name must be specified"); - Validate.notNull(this.tooltip, "Tooltip must be specified"); - Validate.notNull(this.binding, "Option binding must be specified"); - Validate.notNull(this.control, "Control must be specified"); - - return new OptionImpl<>(this.storage, this.name, this.tooltip, this.binding, this.control, this.flags, this.impact, this.enabled); - } - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionPage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionPage.java deleted file mode 100644 index c34450ea22..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionPage.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options; - -import com.google.common.collect.ImmutableList; -import net.minecraft.network.chat.Component; - -public class OptionPage { - private final Component name; - private final ImmutableList groups; - private final ImmutableList> options; - - public OptionPage(Component name, ImmutableList groups) { - this.name = name; - this.groups = groups; - - ImmutableList.Builder> builder = ImmutableList.builder(); - - for (OptionGroup group : groups) { - builder.addAll(group.getOptions()); - } - - this.options = builder.build(); - } - - public ImmutableList getGroups() { - return this.groups; - } - - public ImmutableList> getOptions() { - return this.options; - } - - public Component getName() { - return this.name; - } - -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/GenericBinding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/GenericBinding.java deleted file mode 100644 index a66555039c..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/GenericBinding.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.binding; - -import java.util.function.BiConsumer; -import java.util.function.Function; - -public class GenericBinding implements OptionBinding { - private final BiConsumer setter; - private final Function getter; - - public GenericBinding(BiConsumer setter, Function getter) { - this.setter = setter; - this.getter = getter; - } - - @Override - public void setValue(S storage, T value) { - this.setter.accept(storage, value); - } - - @Override - public T getValue(S storage) { - return this.getter.apply(storage); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/OptionBinding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/OptionBinding.java deleted file mode 100644 index 8af6daffca..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/OptionBinding.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.binding; - -public interface OptionBinding { - void setValue(S storage, T value); - - T getValue(S storage); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/compat/VanillaBooleanOptionBinding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/compat/VanillaBooleanOptionBinding.java deleted file mode 100644 index d72c4bbda4..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/compat/VanillaBooleanOptionBinding.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.binding.compat; - -import net.caffeinemc.mods.sodium.client.gui.options.binding.OptionBinding; -import net.minecraft.client.OptionInstance; -import net.minecraft.client.Options; - -public class VanillaBooleanOptionBinding implements OptionBinding { - private final OptionInstance option; - - public VanillaBooleanOptionBinding(OptionInstance option) { - this.option = option; - } - - @Override - public void setValue(Options storage, Boolean value) { - this.option.set(value); - } - - @Override - public Boolean getValue(Options storage) { - return this.option.get(); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/Control.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/Control.java index efb82730be..c549ed4b05 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/Control.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/Control.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; -import net.caffeinemc.mods.sodium.client.gui.options.Option; +import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.util.Dim2i; public interface Control { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java index 7f39e02eb7..ed261edfed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; -import net.caffeinemc.mods.sodium.client.gui.options.Option; +import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.gui.widgets.AbstractWidget; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.ChatFormatting; @@ -30,7 +30,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { String name = this.option.getName().getString(); // add the star suffix before truncation to prevent it from overlapping with the label text - if (this.option.isAvailable() && this.option.hasChanged()) { + if (this.option.isEnabled() && this.option.hasChanged()) { name = name + " *"; } @@ -40,7 +40,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } String label; - if (this.option.isAvailable()) { + if (this.option.isEnabled()) { if (this.option.hasChanged()) { label = ChatFormatting.ITALIC + name; } else { @@ -97,13 +97,14 @@ public Dim2i getDimensions() { @Override public @Nullable ComponentPath nextFocusPath(FocusNavigationEvent event) { - if (!this.option.isAvailable()) + if (!this.option.isEnabled()) { return null; + } return super.nextFocusPath(event); } @Override - public ScreenRectangle getRectangle() { + public @NotNull ScreenRectangle getRectangle() { return new ScreenRectangle(this.dim.x(), this.dim.y(), this.dim.width(), this.dim.height()); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java similarity index 69% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java rename to common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java index c7d8e30507..0d4b4d62a8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java @@ -1,16 +1,20 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; import com.mojang.blaze3d.platform.Monitor; +import net.caffeinemc.mods.sodium.api.config.option.ControlValueFormatter; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; -public interface ControlValueFormatter { - static ControlValueFormatter guiScale() { +public class ControlValueFormatterImpls { + private ControlValueFormatterImpls() { + } + + public static ControlValueFormatter guiScale() { return (v) -> (v == 0) ? Component.translatable("options.guiScale.auto") : Component.literal(v + "x"); } - static ControlValueFormatter resolution() { + public static ControlValueFormatter resolution() { return (v) -> { Monitor monitor = Minecraft.getInstance().getWindow().findBestMonitor(); @@ -19,15 +23,16 @@ static ControlValueFormatter resolution() { } else if (0 == v) { return Component.translatable("options.fullscreen.current"); } else { - return Component.literal(monitor.getMode(v - 1).toString().replace(" (24bit)","")); + return Component.literal(monitor.getMode(v - 1).toString().replace(" (24bit)", "")); } }; } - static ControlValueFormatter fpsLimit() { + + public static ControlValueFormatter fpsLimit() { return (v) -> (v == 260) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v); } - static ControlValueFormatter brightness() { + public static ControlValueFormatter brightness() { return (v) -> { if (v == 0) { return Component.translatable("options.gamma.min"); @@ -39,29 +44,27 @@ static ControlValueFormatter brightness() { }; } - static ControlValueFormatter biomeBlend() { + public static ControlValueFormatter biomeBlend() { return (v) -> (v == 0) ? Component.translatable("gui.none") : Component.translatable("sodium.options.biome_blend.value", v); } - Component format(int value); - - static ControlValueFormatter translateVariable(String key) { + public static ControlValueFormatter translateVariable(String key) { return (v) -> Component.translatable(key, v); } - static ControlValueFormatter percentage() { + public static ControlValueFormatter percentage() { return (v) -> Component.literal(v + "%"); } - static ControlValueFormatter multiplier() { + public static ControlValueFormatter multiplier() { return (v) -> Component.literal(v + "x"); } - static ControlValueFormatter quantityOrDisabled(String name, String disableText) { + public static ControlValueFormatter quantityOrDisabled(String name, String disableText) { return (v) -> Component.literal(v == 0 ? disableText : v + " " + name); } - static ControlValueFormatter number() { + public static ControlValueFormatter number() { return (v) -> Component.literal(String.valueOf(v)); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index 7c1ce635d5..47af7af0c8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; -import net.caffeinemc.mods.sodium.client.gui.options.Option; -import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.CommonInputs; @@ -9,45 +8,21 @@ import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; +import java.util.function.Function; + public class CyclingControl> implements Control { private final Option option; private final T[] allowedValues; - private final Component[] names; - - public CyclingControl(Option option, Class enumType) { - this(option, enumType, enumType.getEnumConstants()); - } + private final Function elementNameProvider; - public CyclingControl(Option option, Class enumType, Component[] names) { + public CyclingControl(Option option, Class enumType, Function elementNameProvider, T[] allowedValues) { T[] universe = enumType.getEnumConstants(); - Validate.isTrue(universe.length == names.length, "Mismatch between universe length and names array length"); Validate.notEmpty(universe, "The enum universe must contain at least one item"); - this.option = option; - this.allowedValues = universe; - this.names = names; - } - - public CyclingControl(Option option, Class enumType, T[] allowedValues) { - T[] universe = enumType.getEnumConstants(); - this.option = option; this.allowedValues = allowedValues; - this.names = new Component[universe.length]; - - for (int i = 0; i < this.names.length; i++) { - Component name; - T value = universe[i]; - - if (value instanceof TextProvider) { - name = ((TextProvider) value).getLocalizedName(); - } else { - name = Component.literal(value.name()); - } - - this.names[i] = name; - } + this.elementNameProvider = elementNameProvider; } @Override @@ -57,7 +32,7 @@ public Option getOption() { @Override public ControlElement createElement(Dim2i dim) { - return new CyclingControlElement<>(this.option, dim, this.allowedValues, this.names); + return new CyclingControlElement<>(this.option, dim, this.allowedValues, this.elementNameProvider); } @Override @@ -67,18 +42,19 @@ public int getMaxWidth() { private static class CyclingControlElement> extends ControlElement { private final T[] allowedValues; - private final Component[] names; + private final Function elementNameProvider; private int currentIndex; - public CyclingControlElement(Option option, Dim2i dim, T[] allowedValues, Component[] names) { + public CyclingControlElement(Option option, Dim2i dim, T[] allowedValues, Function elementNameProvider) { super(option, dim); this.allowedValues = allowedValues; - this.names = names; + this.elementNameProvider = elementNameProvider; this.currentIndex = 0; + var optionValue = option.getValidatedValue(); for (int i = 0; i < allowedValues.length; i++) { - if (allowedValues[i] == option.getValue()) { + if (allowedValues[i] == optionValue) { this.currentIndex = i; break; } @@ -89,8 +65,8 @@ public CyclingControlElement(Option option, Dim2i dim, T[] allowedValues, Com public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); - Enum value = this.option.getValue(); - Component name = this.names[value.ordinal()]; + var value = this.option.getValidatedValue(); + Component name = this.elementNameProvider.apply(value); int strWidth = this.getStringWidth(name); this.drawString(graphics, name, this.dim.getLimitX() - strWidth - 6, this.dim.getCenterY() - 4, 0xFFFFFFFF); @@ -98,7 +74,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { + if (this.option.isEnabled() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { cycleControl(Screen.hasShiftDown()); this.playClickSound(); @@ -126,7 +102,7 @@ public void cycleControl(boolean reverse) { } else { this.currentIndex = (this.currentIndex + 1) % this.allowedValues.length; } - this.option.setValue(this.allowedValues[this.currentIndex]); + this.option.modifyValue(this.allowedValues[this.currentIndex]); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java index 99c379b493..ddcd1baf29 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java @@ -1,7 +1,8 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; import com.mojang.blaze3d.platform.InputConstants; -import net.caffeinemc.mods.sodium.client.gui.options.Option; +import net.caffeinemc.mods.sodium.api.config.option.ControlValueFormatter; +import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; @@ -68,7 +69,7 @@ public Button(Option option, Dim2i dim, int min, int max, int interval, this.max = max; this.range = max - min; this.interval = interval; - this.thumbPosition = this.getThumbPositionForValue(option.getValue()); + this.thumbPosition = this.getThumbPositionForValue(option.getValidatedValue()); this.formatter = formatter; this.sliderBounds = new Rect2i(dim.getLimitX() - 96, dim.getCenterY() - 5, 90, 10); @@ -82,10 +83,12 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { int sliderWidth = this.sliderBounds.getWidth(); int sliderHeight = this.sliderBounds.getHeight(); - var label = this.formatter.format(this.option.getValue()) - .copy(); + var value = this.option.getValidatedValue(); + var isEnabled = this.option.isEnabled(); - if (!this.option.isAvailable()) { + var label = this.formatter.format(value).copy(); + + if (!isEnabled) { label.setStyle(Style.EMPTY .withColor(ChatFormatting.GRAY) .withItalic(true)); @@ -93,7 +96,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { int labelWidth = this.font.width(label); - boolean drawSlider = this.option.isAvailable() && (this.hovered || this.isFocused()); + boolean drawSlider = isEnabled && (this.hovered || this.isFocused()); if (drawSlider) { this.contentWidth = sliderWidth + labelWidth; } else { @@ -104,7 +107,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); if (drawSlider) { - this.thumbPosition = this.getThumbPositionForValue(this.option.getValue()); + this.thumbPosition = this.getThumbPositionForValue(value); double thumbOffset = Mth.clamp((double) (this.getIntValue() - this.min) / this.range * sliderWidth, 0, sliderWidth); @@ -141,7 +144,7 @@ public double getThumbPositionForValue(int value) { public boolean mouseClicked(double mouseX, double mouseY, int button) { this.sliderHeld = false; - if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { + if (this.option.isEnabled() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { if (this.sliderBounds.contains((int) mouseX, (int) mouseY)) { this.setValueFromMouse(mouseX); this.sliderHeld = true; @@ -162,8 +165,8 @@ public void setValue(double d) { int value = this.getIntValue(); - if (this.option.getValue() != value) { - this.option.setValue(value); + if (this.option.getValidatedValue() != value) { + this.option.modifyValue(value); } } @@ -172,10 +175,10 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (!isFocused()) return false; if (keyCode == InputConstants.KEY_LEFT) { - this.option.setValue(Mth.clamp(this.option.getValue() - this.interval, this.min, this.max)); + this.option.modifyValue(Mth.clamp(this.option.getValidatedValue() - this.interval, this.min, this.max)); return true; } else if (keyCode == InputConstants.KEY_RIGHT) { - this.option.setValue(Mth.clamp(this.option.getValue() + this.interval, this.min, this.max)); + this.option.modifyValue(Mth.clamp(this.option.getValidatedValue() + this.interval, this.min, this.max)); return true; } @@ -184,7 +187,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (this.option.isAvailable() && button == 0) { + if (this.option.isEnabled() && button == 0) { if (this.sliderHeld) { this.setValueFromMouse(mouseX); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index a77748105d..c3917ac3d5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; -import net.caffeinemc.mods.sodium.client.gui.options.Option; +import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.CommonInputs; @@ -46,8 +46,8 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { final int w = x + this.button.getWidth(); final int h = y + this.button.getHeight(); - final boolean enabled = this.option.isAvailable(); - final boolean ticked = enabled && this.option.getValue(); + final boolean enabled = this.option.isEnabled(); + final boolean ticked = enabled && this.option.getValidatedValue(); final int color; @@ -66,7 +66,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { + if (this.option.isEnabled() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { toggleControl(); this.playClickSound(); @@ -91,7 +91,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } public void toggleControl() { - this.option.setValue(!this.option.getValue()); + this.option.modifyValue(!this.option.getValidatedValue()); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/MinecraftOptionsStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/MinecraftOptionsStorage.java deleted file mode 100644 index cb253b8986..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/MinecraftOptionsStorage.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.storage; - -import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.minecraft.client.Minecraft; -import net.minecraft.client.Options; - -public class MinecraftOptionsStorage implements OptionStorage { - private final Minecraft minecraft; - - public MinecraftOptionsStorage() { - this.minecraft = Minecraft.getInstance(); - } - - @Override - public Options getData() { - return this.minecraft.options; - } - - @Override - public void save() { - this.getData().save(); - - SodiumClientMod.logger().info("Flushed changes to Minecraft configuration"); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/OptionStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/OptionStorage.java deleted file mode 100644 index 90f251ae23..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/OptionStorage.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.storage; - -public interface OptionStorage { - T getData(); - - void save(); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/SodiumOptionsStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/SodiumOptionsStorage.java deleted file mode 100644 index cd77f3cf2d..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/SodiumOptionsStorage.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options.storage; - -import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions; - -import java.io.IOException; - -public class SodiumOptionsStorage implements OptionStorage { - private final SodiumGameOptions options; - - public SodiumOptionsStorage() { - this.options = SodiumClientMod.options(); - } - - @Override - public SodiumGameOptions getData() { - return this.options; - } - - @Override - public void save() { - try { - SodiumGameOptions.writeToDisk(this.options); - } catch (IOException e) { - throw new RuntimeException("Couldn't save configuration changes", e); - } - - SodiumClientMod.logger().info("Flushed changes to Sodium configuration"); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java index df0e469db2..0bd43693ae 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java @@ -3,6 +3,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.checks.ResourcePackScanner; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.minecraft.client.Minecraft; import net.minecraft.server.packs.resources.ReloadableResourceManager; import net.minecraft.util.profiling.ProfilerFiller; @@ -77,6 +78,8 @@ private void postRender(boolean tick, CallbackInfo ci) { @Inject(method = "buildInitialScreens", at = @At("TAIL")) private void postInit(CallbackInfoReturnable cir) { ResourcePackScanner.checkIfCoreShaderLoaded(this.resourceManager); + + ConfigManager.registerConfigsLate(); } /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/settings/OptionsScreenMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/settings/OptionsScreenMixin.java index 0e1c1d27ec..5b5d303952 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/settings/OptionsScreenMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/settings/OptionsScreenMixin.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.mixin.features.gui.hooks.settings; -import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI; +import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.options.OptionsScreen; import net.minecraft.network.chat.Component; @@ -22,6 +22,6 @@ protected OptionsScreenMixin(Component title) { "lambda$init$2" }, require = 1, at = @At("HEAD"), cancellable = true) private void open(CallbackInfoReturnable ci) { - ci.setReturnValue(SodiumOptionsGUI.createScreen(this)); + ci.setReturnValue(VideoSettingsScreen.createScreen(this)); } } diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java index 9aa1881b56..cc0d7cf26f 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java @@ -3,6 +3,7 @@ import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.render.frapi.SodiumRenderer; import net.caffeinemc.mods.sodium.client.util.FlawlessFrames; +import net.caffeinemc.mods.sodium.fabric.config.ConfigLoaderFabric; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.renderer.v1.RendererAccess; import net.fabricmc.loader.api.FabricLoader; @@ -17,6 +18,7 @@ public void onInitializeClient() { .getModContainer("sodium") .orElseThrow(NullPointerException::new); + ConfigLoaderFabric.collectConfigEntryPoints(); SodiumClientMod.onInitialization(mod.getMetadata().getVersion().getFriendlyString()); FabricLoader.getInstance() diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/config/ConfigLoaderFabric.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/config/ConfigLoaderFabric.java new file mode 100644 index 0000000000..736cf234b3 --- /dev/null +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/config/ConfigLoaderFabric.java @@ -0,0 +1,26 @@ +package net.caffeinemc.mods.sodium.fabric.config; + +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; +import net.caffeinemc.mods.sodium.client.gui.SodiumConfigBuilder; +import net.fabricmc.loader.api.FabricLoader; + +public class ConfigLoaderFabric { + public static void collectConfigEntryPoints() { + var entryPointContainers = FabricLoader.getInstance().getEntrypointContainers(ConfigManager.JSON_KEY_SODIUM_CONFIG_INTEGRATIONS, ConfigEntryPoint.class); + for (var container : entryPointContainers) { + var mod = container.getProvider(); + var metadata = mod.getMetadata(); + + var modId = metadata.getId(); + var modName = metadata.getName(); + var modVersion = metadata.getVersion().getFriendlyString(); + + ConfigManager.registerConfigEntryPoint(container::getEntrypoint, modId, modName, modVersion); + } + + var sodiumMod = FabricLoader.getInstance().getModContainer("sodium").orElseThrow(NullPointerException::new); + var sodiumMetadata = sodiumMod.getMetadata(); + ConfigManager.registerConfigEntryPoint(SodiumConfigBuilder::new, sodiumMetadata.getId(), sodiumMetadata.getName(), sodiumMetadata.getVersion().getFriendlyString()); + } +} diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java index d6bebd24c0..cdac002336 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.neoforge; -import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI; +import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen; import net.caffeinemc.mods.sodium.client.render.frapi.SodiumRenderer; import net.caffeinemc.mods.sodium.client.util.FlawlessFrames; import net.fabricmc.fabric.api.renderer.v1.RendererAccess; @@ -19,7 +19,7 @@ @Mod(value = "sodium", dist = Dist.CLIENT) public class SodiumForgeMod { public SodiumForgeMod(IEventBus bus, ModContainer modContainer) { - modContainer.registerExtensionPoint(IConfigScreenFactory.class, (minecraft, screen) -> SodiumOptionsGUI.createScreen(screen)); + modContainer.registerExtensionPoint(IConfigScreenFactory.class, (minecraft, screen) -> VideoSettingsScreen.createScreen(screen)); RendererAccess.INSTANCE.registerRenderer(SodiumRenderer.INSTANCE); MethodHandles.Lookup lookup = MethodHandles.lookup(); diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/config/ConfigLoaderForge.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/config/ConfigLoaderForge.java new file mode 100644 index 0000000000..e94d6b1599 --- /dev/null +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/config/ConfigLoaderForge.java @@ -0,0 +1,36 @@ +package net.caffeinemc.mods.sodium.neoforge.config; + +import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; +import net.caffeinemc.mods.sodium.client.gui.SodiumConfigBuilder; +import net.neoforged.fml.ModList; +import net.neoforged.neoforgespi.language.IModInfo; + +/** + * Written with help from Contaria's implementation of this class. + */ +public class ConfigLoaderForge { + public static void collectConfigEntryPoints() { + for (IModInfo mod : ModList.get().getMods()) { + var modId = mod.getModId(); + var modName = mod.getDisplayName(); + var modVersion = mod.getVersion().toString(); + + if (modId.equals("sodium")) { + ConfigManager.registerConfigEntryPoint(SodiumConfigBuilder::new, modId, modName, modVersion); + } else { + Object modProperty = mod.getModProperties().get(ConfigManager.JSON_KEY_SODIUM_CONFIG_INTEGRATIONS); + if (modProperty == null) { + continue; + } + + if (!(modProperty instanceof String)) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the value is of the wrong type: {}", modId, modProperty.getClass()); + continue; + } + + ConfigManager.registerConfigEntryPoint((String) modProperty, modId, modName, modVersion); + } + } + } +} diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/mixin/EntrypointMixin.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/mixin/EntrypointMixin.java index dc96f9a19a..00cbd7dd47 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/mixin/EntrypointMixin.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/mixin/EntrypointMixin.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.neoforge.mixin; import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.neoforge.config.ConfigLoaderForge; import net.minecraft.client.Minecraft; import net.minecraft.client.main.GameConfig; import net.neoforged.fml.ModList; @@ -16,6 +17,7 @@ public class EntrypointMixin { @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Options;loadSelectedResourcePacks(Lnet/minecraft/server/packs/repository/PackRepository;)V")) private void sodium$loadConfig(GameConfig gameConfig, CallbackInfo ci) { + ConfigLoaderForge.collectConfigEntryPoints(); SodiumClientMod.onInitialization(ModList.get().getModContainerById("sodium").map(t -> t.getModInfo().getVersion().toString()).orElse("UNKNOWN")); } }