Switch config UI backend to LibJF 3

This commit is contained in:
Johannes Frohnmeyer 2022-08-28 20:06:59 +02:00
parent c4c1d7f1bb
commit 7c368fab76
Signed by: Johannes
GPG Key ID: E76429612C2929F4
31 changed files with 178 additions and 440 deletions

View File

@ -1,4 +1,4 @@
apply from: "https://jfmods.gitlab.io/scripts/jfmod.gradle"
apply from: "https://jfmods.gitlab.io/scripts/gradle/v2.gradle"
repositories {
maven { url = "https://maven.shedaniel.me/"; name = "Cloth Config" }
@ -17,18 +17,17 @@ repositories {
}
dependencies {
//TODO move modmenu/frex to client-only once they get remapped
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_version}")
modCompileOnly(modRuntimeOnly("com.terraformersmc:modmenu:4.0.5"))
include modImplementation("io.gitlab.jfronny:muscript:${project.muscript_version}")
modApi("me.shedaniel.cloth:cloth-config-fabric:7.0.73") {
exclude(group: "net.fabricmc.fabric-api")
}
modCompileOnly("io.vram:frex-fabric-mc119:+")
modImplementation("io.gitlab.jfronny.libjf:libjf-data-manipulation-v0:${project.jfapi_version}")
modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-devutil-v0:${project.jfapi_version}")
modImplementation("io.gitlab.jfronny.libjf:libjf-data-manipulation-v0:${project.libjf_version}")
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v1:${project.libjf_version}")
modImplementation("io.gitlab.jfronny.libjf:libjf-config-ui-tiny-v1:${project.libjf_version}")
modClientCompileOnly("io.vram:frex-fabric-mc119:+")
testImplementation('org.junit.jupiter:junit-jupiter:5.8.2')
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${project.libjf_version}")
modLocalRuntime("com.terraformersmc:modmenu:4.0.6")
testImplementation('org.junit.jupiter:junit-jupiter:5.9.0')
// Canvas for FREX testing
// modRuntimeOnly("io.vram:canvas-fabric-mc118:+") {

View File

@ -12,8 +12,7 @@ Every entry can contain the following properties, regardless of its type:
## Numbers
There are two ways to display numbers: input boxes and sliders.
Sliders can only be used to select whole numbers but are more intuitive for limited inputs.
They also require a minimum and maximum value to be specified.
Any number input that provides a minimum and maximum value will be displayed as a slider instead of a box.
### Example:
```json
@ -23,7 +22,6 @@ They also require a minimum and maximum value to be specified.
capabilities: ["FileFilter", "DirFilter"],
conf: {
someOption: {
type: "slider", // can also be "number" for a normal number
default: 5,
min: 0,
max: 10

View File

@ -69,7 +69,7 @@ A number box follows the same principle as a boolean: `"entryName": Default Numb
![configExampleNumber](./img/ExamplePackNumber.png "ConfigExampleNumber")
## Adding a slider
A slider is slightly more complicated as a minimum and a maximum need to be defined. Sliders also only support whole numbers.
A slider is slightly more complicated as a minimum and a maximum need to be defined.
More information is available [here](AdvancedConfig.md)
## Select From a list

View File

@ -60,8 +60,15 @@ Corresponds to version 2.10.0
- Replace StarScript representation of enums
## v8
Corresponds to version 3.0.0
Corresponds to version 3.0.0-3.1.0
- Entirely new condition representation via MuScript
- Entirely new resource expansion using MuScript
- Removal of fabric conditions API interop in favor of enabling cleaner syntax
## v9
Corresponds to version 4.0.0
- New config screen backend powered by LibJF (needed to support serverside)
- New translation key syntax
- Removal of manual configuration for sliders vs input boxes

View File

@ -39,7 +39,7 @@ Example:
{
"rpo.examplePack": "Some other name",
"rpo.examplePack.someTexture": "Some Texture",
"rpo.tooltip.examplePack.someTexture": "This would be the tooltip"
"rpo.examplePack.someTexture.tooltip": "This would be the tooltip"
}
```

View File

@ -1,16 +1,17 @@
# https://fabricmc.net/develop/
minecraft_version=1.19.1
yarn_mappings=build.1
loader_version=0.14.8
minecraft_version=1.19.2
yarn_mappings=build.8
loader_version=0.14.9
maven_group=io.gitlab.jfronny
archives_base_name=respackopts
fabric_version=0.58.5+1.19.1
jfapi_version=2.10.0
fabric_version=0.60.0+1.19.2
libjf_version=3.0.3-1661709178
muscript_version=2022.7.4+11-13-3
modrinth_id=respackopts
modrinth_required_dependencies=fabric-api, cloth-config, libjf
modrinth_required_dependencies=fabric-api, libjf
modrinth_optional_dependencies=modmenu
curseforge_id=430090
curseforge_required_dependencies=fabric-api, cloth-config, libjf
curseforge_required_dependencies=fabric-api, libjf
curseforge_optional_dependencies=modmenu

View File

@ -1,7 +1,7 @@
{
"rpo.lumi": "${Lumi} ${Lights}",
"rpo.lumi.tonemap": "Tonemap mode",
"rpo.tooltip.lumi.tonemap": "Tooltip test",
"rpo.lumi.tonemap.tooltip": "Tooltip test",
"rpo.lumi.tonemap.default": "Default Tonemap",
"rpo.lumi.tonemap.vibrant": "Vibrant Tonemap",
"rpo.lumi.tonemap.film": "Filmic Tonemap",
@ -9,5 +9,5 @@
"rpo.lumi.debugMode": "Debug Mode",
"rpo.lumi.debugMode.normal": "${Normal} Mode",
"rpo.lumi.waterVertexWavy": "Wavy water model",
"rpo.tooltip.lumi.subcategoryTest.sliderTest": "Yayyy"
"rpo.lumi.subcategoryTest.sliderTest.tooltip": "Yayyy"
}

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.respackopts.integration.*;
import io.gitlab.jfronny.respackopts.util.GuiFactory;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import net.fabricmc.api.*;
import net.fabricmc.loader.api.FabricLoader;
@ -16,7 +15,6 @@ import java.util.concurrent.CompletableFuture;
@Environment(EnvType.CLIENT)
public class RespackoptsClient implements ClientModInitializer, SaveHook {
private static final boolean FREX_LOADED = FabricLoader.getInstance().isModLoaded("frex");
public static final GuiFactory GUI_FACTORY = new GuiFactory();
public static final Identifier RPO_SHADER_ID = new Identifier(Respackopts.ID, "config_supplier");
public static boolean forcePackReload = false;
private static String shaderImportSource;
@ -31,7 +29,7 @@ public class RespackoptsClient implements ClientModInitializer, SaveHook {
@Override
public void onInitializeClient() {
if (Respackopts.CONFIG.debugCommands)
if (RespackoptsConfig.debugCommands)
RpoClientCommand.register();
if (FREX_LOADED) {
FrexCompat.init();
@ -40,7 +38,7 @@ public class RespackoptsClient implements ClientModInitializer, SaveHook {
@Override
public CompletableFuture<Void> onSave(Arguments args) {
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Generating shader code");
StringBuilder sb = new StringBuilder();
sb.append("#ifndef respackopts_loaded");
@ -69,7 +67,7 @@ public class RespackoptsClient implements ClientModInitializer, SaveHook {
public static CompletableFuture<Void> forceReloadResources() {
forcePackReload = true;
DashLoaderCompat.requestForceReload();
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("Forcing resource reload");
if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("Forcing resource reload");
return CompletableFuture.allOf(MinecraftClient.getInstance().reloadResources());
}

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.respackopts.integration;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import net.fabricmc.loader.api.FabricLoader;
import java.io.IOException;
@ -11,10 +12,10 @@ public class DashLoaderCompat {
public static void requestForceReload() {
if (!FabricLoader.getInstance().isModLoaded("dashloader"))
return;
if (!Respackopts.CONFIG.dashloaderCompat)
if (!RespackoptsConfig.dashloaderCompat)
return;
try {
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Removing DashCache to force dashloader to reload");
Class<?> loaderClass = Class.forName("dev.quantumfusion.dashloader.def.DashLoader");
Path loaderPath = (Path) loaderClass.getDeclaredField("DASH_CACHE_FOLDER").get(null);

View File

@ -1,66 +0,0 @@
package io.gitlab.jfronny.respackopts.integration;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.respackopts.RespackoptsClient;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.ConfigFile;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.gui.screen.FatalErrorScreen;
import net.minecraft.text.*;
public class ModMenuCompat implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> {
try {
ConfigBuilder builder;
builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(Text.translatable("respackopts.mainconfig"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
builder.setSavingRunnable(() -> {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("ModMenuCompat SavingRunnable " + agg.get());
MetaCache.save(agg.get() == PackReloadType.Resource ? SaveHook.Arguments.RELOAD_ALL : SaveHook.Arguments.DO_NOTHING);
});
//Respackopts config screen
ConfigFile defaultConfig = new ConfigFile();
ConfigCategory mainConfig = builder.getOrCreateCategory(Text.translatable("respackopts.mainconfig"));
mainConfig.addEntry(
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.debugCommands"), Respackopts.CONFIG.debugCommands)
.setDefaultValue(defaultConfig.debugCommands)
.setSaveConsumer(b -> Respackopts.CONFIG.debugCommands = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.debugLogs"), Respackopts.CONFIG.debugLogs)
.setDefaultValue(defaultConfig.debugLogs)
.setSaveConsumer(b -> Respackopts.CONFIG.debugLogs = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.dashloaderCompat"), Respackopts.CONFIG.dashloaderCompat)
.requireRestart()
.setDefaultValue(defaultConfig.dashloaderCompat)
.setSaveConsumer(b -> Respackopts.CONFIG.dashloaderCompat = b)
.build()
);
//Pack config screens
MetaCache.forEach((key, state) -> {
ConfigCategory config = builder.getOrCreateCategory(Text.translatable((state.metadata().version >= 5 ? "rpo." : "respackopts.title.") + state.packId()));
RespackoptsClient.GUI_FACTORY.buildCategory(state.configBranch(), state.packId(), config::addEntry, agg, entryBuilder, "");
});
return builder.build();
}
catch (Throwable t) {
t.printStackTrace();
return new FatalErrorScreen(Text.translatable("respackopts.loadFailed"), Text.translatable("respackopts.loadError"));
}
};
}
}

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.respackopts.mixin;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RespackoptsClient;
import io.gitlab.jfronny.respackopts.*;
import net.minecraft.client.gui.screen.option.OptionsScreen;
import net.minecraft.client.option.GameOptions;
import net.minecraft.resource.ResourcePackManager;
@ -20,7 +19,7 @@ public class OptionsScreenMixin {
private void refreshResourcePacks(ResourcePackManager resourcePackManager, CallbackInfo info) {
if (RespackoptsClient.forcePackReload) {
RespackoptsClient.forcePackReload = false;
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Clearing loaded resource packs to enable a proper resource reload");
this.settings.resourcePacks.clear();
}

View File

@ -1,6 +1,8 @@
package io.gitlab.jfronny.respackopts.mixin;
import com.mojang.blaze3d.systems.RenderSystem;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.ConfigScreen;
import io.gitlab.jfronny.respackopts.RespackoptsClient;
import io.gitlab.jfronny.respackopts.model.cache.CacheKey;
import io.gitlab.jfronny.respackopts.util.MetaCache;
@ -46,7 +48,8 @@ public abstract class ResourcePackEntryMixin {
if (dataLocation != null && rpo$selected) {
info.setReturnValue(true);
MinecraftClient c = MinecraftClient.getInstance();
c.setScreen(RespackoptsClient.GUI_FACTORY.buildGui(MetaCache.getBranch(dataLocation), MetaCache.getId(dataLocation), c.currentScreen));
String id = MetaCache.getId(dataLocation);
c.setScreen(ConfigScreen.create(DSL.create(id).config(builder -> MetaCache.getBranch(dataLocation).buildConfig(builder, id)), c.currentScreen));
}
}
}

View File

@ -1,58 +0,0 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.integration.SaveHook;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.model.tree.*;
import me.shedaniel.clothconfig2.api.*;
import net.minecraft.client.gui.screen.FatalErrorScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Language;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
public class GuiFactory implements CategoryFactory {
@Override
public void buildCategory(ConfigBranch source, String screenId, Consumer<AbstractConfigListEntry<?>> config, Consumer<PackReloadType> reloadTypeAggregator, ConfigEntryBuilder entryBuilder, String namePrefix) {
for (Map.Entry<String, ConfigEntry<?>> in : source.getValue().entrySet()) {
ConfigEntry<?> entry = in.getValue();
String entryName = ("".equals(namePrefix) ? "" : namePrefix + ".") + in.getKey();
String translationPrefix = (source.getVersion() < 3 ? "respackopts." + entry.getEntryType() + "." : "rpo.") + screenId;
config.accept(entry.buildEntry(
new GuiEntryBuilderParam(entryBuilder, entryName, () -> {
String k = (source.getVersion() < 3 ? "respackopts.tooltip." : "rpo.tooltip.") + screenId + "." + entryName;
if (Language.getInstance().hasTranslation(k)) {
Text[] res = new Text[1];
res[0] = Text.translatable(k);
return Optional.of(res);
} else
return Optional.empty();
}, screenId, entryName, translationPrefix, () -> reloadTypeAggregator.accept(entry.getReloadType()), reloadTypeAggregator, this)));
}
}
public Screen buildGui(ConfigBranch source, String packId, Screen parent) {
try {
ConfigBuilder builder;
builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(GuiEntryBuilderParam.getText(packId, source.getVersion() < 4 ? "respackopts.title" : "rpo"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
builder.setSavingRunnable(() -> {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("GuiFactory SavingRunnable " + agg.get());
MetaCache.save(new SaveHook.Arguments(agg.get() == PackReloadType.Resource, false, true));
});
ConfigCategory config = builder.getOrCreateCategory(GuiEntryBuilderParam.getText(packId, source.getVersion() < 4 ? "respackopts.title" : "rpo"));
buildCategory(source, packId, config::addEntry, agg, entryBuilder, "");
return builder.build();
}
catch (Throwable t) {
t.printStackTrace();
return new FatalErrorScreen(Text.translatable("respackopts.loadFailed"), Text.translatable("respackopts.loadError"));
}
}
}

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.gson.Gson;
import io.gitlab.jfronny.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.respackopts.filters.DirFilterEventImpl;
import io.gitlab.jfronny.respackopts.filters.FileFilterEventImpl;
@ -10,10 +11,10 @@ import io.gitlab.jfronny.respackopts.gson.*;
import io.gitlab.jfronny.respackopts.gson.entry.*;
import io.gitlab.jfronny.respackopts.integration.SaveHook;
import io.gitlab.jfronny.respackopts.model.Condition;
import io.gitlab.jfronny.respackopts.model.ConfigFile;
import io.gitlab.jfronny.respackopts.model.tree.*;
import io.gitlab.jfronny.respackopts.server.ServerInstanceHolder;
import net.fabricmc.api.*;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.util.Identifier;
@ -23,37 +24,26 @@ import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
public class Respackopts implements ModInitializer, SaveHook {
public static final Integer META_VERSION = 8;
public static final Integer META_VERSION = 9;
public static final String FILE_EXTENSION = ".rpo";
public static final Gson GSON;
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(ConfigEnumEntry.class, new EnumEntrySerializer())
.registerTypeAdapter(ConfigNumericEntry.class, new NumericEntrySerializer())
.registerTypeAdapter(ConfigBooleanEntry.class, new BooleanEntrySerializer())
.registerTypeAdapter(ConfigBranch.class, new ConfigBranchSerializer())
.registerTypeAdapter(Expr.class, new ExprDeserializer())
.registerTypeAdapter(StringExpr.class, new StringExprDeserializer())
.registerTypeAdapter(BoolExpr.class, new BoolExprDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.setLenient()
.setPrettyPrinting()
.create();
public static final String ID = "respackopts";
public static final Logger LOGGER = Logger.forName(ID);
public static final Identifier CONF_ID = new Identifier(ID, "conf.json");
public static Path FALLBACK_CONF_DIR;
public static ConfigFile CONFIG;
static {
GSON = new GsonBuilder()
.registerTypeAdapter(ConfigEnumEntry.class, new EnumEntrySerializer())
.registerTypeAdapter(ConfigNumericEntry.class, new NumericEntrySerializer())
.registerTypeAdapter(ConfigBooleanEntry.class, new BooleanEntrySerializer())
.registerTypeAdapter(ConfigBranch.class, new ConfigBranchSerializer())
.registerTypeAdapter(Expr.class, new ExprDeserializer())
.registerTypeAdapter(StringExpr.class, new StringExprDeserializer())
.registerTypeAdapter(BoolExpr.class, new BoolExprDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.setLenient()
.setPrettyPrinting()
.create();
try {
FALLBACK_CONF_DIR = FabricLoader.getInstance().getConfigDir().resolve(ID);
CONFIG = ConfigFile.load();
} catch (Throwable e) {
LOGGER.error("Could not resolve config directory", e);
}
}
public static Path FALLBACK_CONF_DIR = FabricLoader.getInstance().getConfigDir().resolve(ID);
@Override
public void onInitialize() {
@ -69,7 +59,7 @@ public class Respackopts implements ModInitializer, SaveHook {
@Override
public CompletableFuture<Void> onSave(Arguments args) {
CONFIG.save();
ConfigHolder.getInstance().get(ID).write();
if (args.reloadData() && FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
ServerInstanceHolder.reloadResources();

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.v1.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import java.util.LinkedList;
import java.util.List;
public class RespackoptsConfig implements JfCustomConfig {
public static boolean debugCommands = false;
public static boolean debugLogs = false;
public static boolean dashloaderCompat = true;
@Override
public void register(DSL.Defaulted dsl) {
dsl.register(builder -> builder
.value("debugCommands", debugCommands, () -> debugCommands, v -> debugCommands = v)
.value("debugLogs", debugLogs, () -> debugLogs, v -> debugLogs = v)
.value("dashloaderCompat", dashloaderCompat, () -> dashloaderCompat, v -> dashloaderCompat = v)
.setPath(Respackopts.FALLBACK_CONF_DIR.resolve("_respackopts.conf"))
.referenceConfig(() -> {
List<ConfigInstance> instances = new LinkedList<>();
MetaCache.forEach((key, state) -> instances.add(DSL.create(state.packId())
.config(cb -> state.configBranch().buildConfig(cb, state.packId()))
));
return instances;
})
);
}
}

View File

@ -2,7 +2,6 @@ package io.gitlab.jfronny.respackopts.gson.entry;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.respackopts.model.tree.ConfigNumericEntry;
import io.gitlab.jfronny.respackopts.model.enums.NumericEntryType;
import java.lang.reflect.Type;
@ -14,19 +13,16 @@ public class NumericEntrySerializer implements JsonSerializer<ConfigNumericEntry
@Override
public ConfigNumericEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isNumber()) {
ConfigNumericEntry result = new ConfigNumericEntry(NumericEntryType.Box);
ConfigNumericEntry result = new ConfigNumericEntry();
result.setValue(json.getAsDouble());
result.setDefault(json.getAsDouble());
return result;
}
else if (json.isJsonObject()) {
JsonObject o = json.getAsJsonObject();
boolean slider = o.has("type") && o.get("type").getAsString().equals("slider");
ConfigNumericEntry result = new ConfigNumericEntry(slider ? NumericEntryType.Slider : NumericEntryType.Box);
ConfigNumericEntry result = new ConfigNumericEntry();
JsonElement min = o.get("min");
JsonElement max = o.get("max");
if (slider && (min == null || max == null))
throw new JsonSyntaxException("min/max must not be null for slider");
if (min != null) {
if (min.isJsonPrimitive() && min.getAsJsonPrimitive().isNumber()) {
result.min = min.getAsNumber().doubleValue();
@ -39,14 +35,8 @@ public class NumericEntrySerializer implements JsonSerializer<ConfigNumericEntry
}
else throw new JsonSyntaxException("Expected number as max of numeric entry");
}
if (slider && (isOdd(result.min) || isOdd(result.max)))
throw new JsonSyntaxException("Expected whole number in slider definition");
return result;
}
throw new JsonSyntaxException("Could not deserialize numeric entry");
}
public static boolean isOdd(double v) {
return v != Math.floor(v) || Double.isInfinite(v);
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.respackopts.mixin;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import io.gitlab.jfronny.respackopts.integration.SaveHook;
import io.gitlab.jfronny.respackopts.model.cache.CacheKey;
import io.gitlab.jfronny.respackopts.util.MetaCache;
@ -42,7 +43,7 @@ public class ResourcePackManagerMixin {
String displayName = v.getDisplayName().getString();
String packName = rpi.getName();
PackMeta conf = Respackopts.GSON.fromJson(isr, PackMeta.class);
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Discovered pack: " + conf.id);
if (Respackopts.META_VERSION < conf.version) {
Respackopts.LOGGER.error(displayName + " was not loaded as it specifies a newer respackopts version than is installed");

View File

@ -1,43 +0,0 @@
package io.gitlab.jfronny.respackopts.model;
import io.gitlab.jfronny.respackopts.Respackopts;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ConfigFile {
public boolean debugCommands = false;
public boolean debugLogs = false;
public boolean dashloaderCompat = true;
private static final Path rpoPath = Respackopts.FALLBACK_CONF_DIR.resolve("_respackopts.conf");
public static ConfigFile load() {
if (!Files.exists(rpoPath))
new ConfigFile().save();
try (BufferedReader br = Files.newBufferedReader(rpoPath)) {
return Respackopts.GSON.fromJson(br, ConfigFile.class);
} catch (IOException e) {
Respackopts.LOGGER.error("Could not load config, using default", e);
return new ConfigFile();
}
}
public void save() {
if (!Files.exists(Respackopts.FALLBACK_CONF_DIR)) {
try {
Files.createDirectories(Respackopts.FALLBACK_CONF_DIR);
} catch (IOException e) {
Respackopts.LOGGER.error("Could not create config dir", e);
return;
}
}
try (BufferedWriter bw = Files.newBufferedWriter(rpoPath)) {
Respackopts.GSON.toJson(this, bw);
} catch (IOException e) {
Respackopts.LOGGER.error("Could not save", e);
}
}
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.respackopts.model.enums;
public enum NumericEntryType {
Slider, Box
}

View File

@ -1,8 +1,9 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.commons.data.dynamic.DBool;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.respackopts.Respackopts;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import java.util.Objects;
@ -34,18 +35,14 @@ public class ConfigBooleanEntry extends ConfigEntry<Boolean> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam args) {
return args.entryBuilder().startBooleanToggle(args.getName(), getValue())
.setDefaultValue(getDefault())
.setSaveConsumer(value -> {
if (getValue() != value) {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("ConfigBooleanEntry SaveCallback");
args.saveCallback();
}
setValue(value);
})
.setTooltipSupplier(args.tooltipSupplier())
.build();
public CategoryBuilder<?> buildEntry(GuiEntryBuilderParam args) {
return args.builder().value(args.name(), getDefault(), this::getValue, v -> {
if (getValue() != v) {
if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigBooleanEntry SaveCallback");
args.saveCallback();
}
setValue(v);
});
}
@Override

View File

@ -3,12 +3,15 @@ package io.gitlab.jfronny.respackopts.model.tree;
import com.google.common.collect.ImmutableMap;
import io.gitlab.jfronny.commons.data.dynamic.*;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.integration.SaveHook;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import java.util.*;
@ -18,34 +21,6 @@ public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
setValue(new LinkedHashMap<>());
}
public boolean getBoolean(String name) throws RpoFormatException {
String[] sp = name.split("\\.");
if (!super.getValue().containsKey(sp[0]))
throw new RpoFormatException("Invalid path to key in " + getName() + ": " + name);
ConfigEntry<?> e = super.getValue().get(sp[0]);
if (sp.length == 1) {
if (e instanceof ConfigBooleanEntry b)
return b.getValue();
throw new RpoFormatException("Not a boolean in " + getName() + ": " + name);
}
if (sp.length == 2 && e instanceof ConfigEnumEntry en) {
for (String entry : en.values) {
if (entry.equals(sp[1]))
return entry.equals(en.getValue());
}
throw new RpoFormatException("Could not find enum entry in " + getName() + ": " + name);
}
if (e instanceof ConfigBranch b) {
try {
return b.getBoolean(name.substring(name.indexOf('.') + 1));
}
catch (RpoFormatException fe) {
throw new RpoFormatException("Couldn't evaluate boolean condition in " + getName() + ": " + name, fe);
}
}
throw new RpoFormatException("Invalid path to key in " + getName() + ": " + name);
}
@Override
public void sync(ConfigEntry<Map<String, ConfigEntry<?>>> source, ConfigSyncMode mode) {
for (Map.Entry<String, ConfigEntry<?>> e : source.getValue().entrySet()) {
@ -121,16 +96,30 @@ public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam args) {
SubCategoryBuilder sc = args.entryBuilder().startSubCategory(args.getName());
args.buildCategory(this, args.screenId(), sc::add, args.agg(), args.entryBuilder(), args.entryName());
sc.setTooltipSupplier(args.tooltipSupplier());
return sc.build();
public CategoryBuilder<?> buildEntry(GuiEntryBuilderParam builder) {
return builder.builder().category(builder.name(), cb -> {
for (Map.Entry<String, ConfigEntry<?>> e : getValue().entrySet()) {
e.getValue().buildEntry(new GuiEntryBuilderParam(cb, e.getKey(), builder.onSave()));
}
return cb;
});
}
@Override
public String getEntryType() {
return "title";
public <T extends ConfigBuilder<?>> T buildConfig(T builder, String packId) {
builder.setTranslationPrefix("rpo." + packId + ".");
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
for (Map.Entry<String, ConfigEntry<?>> e : getValue().entrySet()) {
e.getValue().buildEntry(
new GuiEntryBuilderParam(builder,
e.getKey(),
() -> agg.accept(e.getValue().getReloadType()))
);
}
builder.executeAfterWrite(cfg -> {
if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("GuiFactory SavingRunnable " + agg.get());
MetaCache.save(new SaveHook.Arguments(agg.get() == PackReloadType.Resource, false, true));
});
return builder;
}
@Override

View File

@ -1,11 +1,11 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.commons.data.dynamic.Dynamic;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import java.util.Objects;
@ -104,11 +104,7 @@ public abstract class ConfigEntry<T> {
public abstract Dynamic<?> getDynamic();
public abstract AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam args);
public String getEntryType() {
return "field";
}
public abstract CategoryBuilder<?> buildEntry(GuiEntryBuilderParam args);
public Class<? super T> getEntryClass() {
return entryClass;

View File

@ -1,14 +1,13 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.commons.data.dynamic.DEnum;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import net.minecraft.text.Text;
import java.util.*;
import java.util.function.Function;
public class ConfigEnumEntry extends ConfigEntry<String> {
public List<String> values = new ArrayList<>();
@ -118,29 +117,14 @@ public class ConfigEnumEntry extends ConfigEntry<String> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam args) {
Map<String, String> translationReverseLookup = new HashMap<>();
for (String value : values) {
translationReverseLookup.put(args.prefixWithName(value).getString(), value);
}
return args.entryBuilder().startStringDropdownMenu(args.getName(), args.prefixWithName(getValue()).getString(), args::prefixWithName)
.setSuggestionMode(false)
.setDefaultValue(args.prefixWithName(getDefault()).getString())
.setSelections(translationReverseLookup.keySet())
.setSaveConsumer(value -> {
if (!translationReverseLookup.containsKey(value)) {
Respackopts.LOGGER.error("Invalid enum entry selected: " + value);
return;
}
value = translationReverseLookup.get(value);
if (!Objects.equals(getValue(), value)) {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("ConfigEnumEntry SaveCallback");
args.saveCallback();
}
setValue(value);
})
.setTooltipSupplier(args.tooltipSupplier())
.build();
public CategoryBuilder<?> buildEntry(GuiEntryBuilderParam args) {
return args.builder().value(args.name(), getValue(), values.toArray(String[]::new), this::getValue, v -> {
if (!Objects.equals(getValue(), v)) {
if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigEnumEntry SaveCallback");
args.saveCallback();
}
setValue(v);
});
}
@Override

View File

@ -1,24 +1,20 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.commons.data.dynamic.DNumber;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.NumericEntryType;
import io.gitlab.jfronny.respackopts.gson.entry.NumericEntrySerializer;
import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.impl.builders.DoubleFieldBuilder;
import java.util.Objects;
public class ConfigNumericEntry extends ConfigEntry<Double> {
public Double min = null;
public Double max = null;
public final NumericEntryType type;
public ConfigNumericEntry(NumericEntryType type) {
public ConfigNumericEntry() {
super(Double.class);
this.type = type;
setValue(0d);
}
@ -26,8 +22,6 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
public Double setDefault(Double value) {
if ((min != null && value < min) || (max != null && value > max))
throw new RuntimeException("Default value (" + value + ") out of range in number entry definition (" + min + "-" + max + ")");
if (type == NumericEntryType.Slider && NumericEntrySerializer.isOdd(value))
throw new RuntimeException("Default number value must be a whole number");
return super.setDefault(value);
}
@ -50,7 +44,7 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
@Override
public ConfigEntry<Double> clone() {
ConfigNumericEntry ce = new ConfigNumericEntry(type);
ConfigNumericEntry ce = new ConfigNumericEntry();
ce.max = max;
ce.min = min;
ce.setValue(getValue());
@ -72,36 +66,14 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam args) {
if (type == NumericEntryType.Slider) {
return args.entryBuilder().startIntSlider(args.getName(),
getValue().intValue(), min.intValue(), max.intValue())
.setDefaultValue(getDefault().intValue())
.setSaveConsumer(value -> {
if (!Objects.equals(getValue(), value.doubleValue())) {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntrySlider SaveCallback");
args.saveCallback();
}
setValue(value.doubleValue());
})
.setTooltipSupplier(args.tooltipSupplier())
.build();
}
else {
DoubleFieldBuilder builder = args.entryBuilder().startDoubleField(args.getName(), getValue())
.setDefaultValue(getDefault())
.setSaveConsumer(value -> {
if (!Objects.equals(getValue(), value)) {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntryNormal SaveCallback");
args.saveCallback();
}
setValue(value);
})
.setTooltipSupplier(args.tooltipSupplier());
if (min != null) builder.setMin(min);
if (max != null) builder.setMax(max);
return builder.build();
}
public CategoryBuilder<?> buildEntry(GuiEntryBuilderParam args) {
return args.builder().value(args.name(), getValue(), min == null ? Double.NEGATIVE_INFINITY : min, max == null ? Double.POSITIVE_INFINITY : max, this::getValue, v -> {
if (!Objects.equals(getValue(), v)) {
if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntryNormal SaveCallback");
args.saveCallback();
}
setValue(v);
});
}
@Override
@ -109,11 +81,11 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
if (this == o) return true;
if (!(o instanceof ConfigNumericEntry that)) return false;
if (!super.equals(o)) return false;
return Objects.equals(getValue(), that.getValue()) && Objects.equals(getDefault(), that.getDefault()) && Objects.equals(min, that.min) && Objects.equals(max, that.max) && type == that.type;
return Objects.equals(getValue(), that.getValue()) && Objects.equals(getDefault(), that.getDefault()) && Objects.equals(min, that.min) && Objects.equals(max, that.max);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), getValue(), getDefault(), min, max, type);
return Objects.hash(super.hashCode(), getValue(), getDefault(), min, max);
}
}

View File

@ -1,45 +1,9 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.util.CategoryFactory;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.text.Text;
import net.minecraft.util.Language;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
public record GuiEntryBuilderParam(ConfigEntryBuilder entryBuilder, String name,
Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName,
String translationPrefix, Runnable onSave, Consumer<PackReloadType> agg,
CategoryFactory categoryFactory) implements CategoryFactory {
public record GuiEntryBuilderParam(CategoryBuilder<?> builder, String name, Runnable onSave) {
public void saveCallback() {
onSave.run();
}
public Text getName() {
return getText(name);
}
public Text prefixWithName(String subName) {
return getText(subName, translationPrefix.equals("") ? name : translationPrefix + "." + name);
}
public Text getText(String name) {
return getText(name, translationPrefix);
}
public static Text getText(String name, String translationPrefix) {
String translatableNameKey = translationPrefix + "." + name;
return Language.getInstance().hasTranslation(translatableNameKey)
? Text.translatable(translatableNameKey)
: Text.literal(name);
}
@Override
public void buildCategory(ConfigBranch source, String screenId, Consumer<AbstractConfigListEntry<?>> config, Consumer<PackReloadType> reloadTypeAggregator, ConfigEntryBuilder entryBuilder, String namePrefix) {
categoryFactory.buildCategory(source, screenId, config, reloadTypeAggregator, entryBuilder, namePrefix);
}
}

View File

@ -1,12 +0,0 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import java.util.function.Consumer;
public interface CategoryFactory {
void buildCategory(ConfigBranch source, String screenId, Consumer<AbstractConfigListEntry<?>> config, Consumer<PackReloadType> reloadTypeAggregator, ConfigEntryBuilder entryBuilder, String namePrefix);
}

View File

@ -4,6 +4,7 @@ import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.muscript.ExpressionParameter;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.integration.SaveHook;
import io.gitlab.jfronny.respackopts.RespackoptsConfig;
import io.gitlab.jfronny.respackopts.model.PackMeta;
import io.gitlab.jfronny.respackopts.model.cache.CacheKey;
import io.gitlab.jfronny.respackopts.model.cache.CachedPackState;
@ -81,7 +82,7 @@ public class MetaCache {
}
public static CompletableFuture<Void> save(SaveHook.Arguments args) {
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Saving configs");
for (Map.Entry<CacheKey, CachedPackState> e : PACK_STATES.entrySet()) {
save(e.getKey().dataLocation(), e.getValue().configBranch());
@ -109,7 +110,7 @@ public class MetaCache {
public static void load(CacheKey key) {
if (Files.exists(key.dataLocation())) {
if (Respackopts.CONFIG.debugLogs)
if (RespackoptsConfig.debugLogs)
Respackopts.LOGGER.info("Loading configs for: " + key);
try (Reader reader = Files.newBufferedReader(key.dataLocation())) {
ConfigBranch b = Respackopts.GSON.fromJson(reader, ConfigBranch.class);

View File

@ -2,10 +2,10 @@
"respackopts.loadFailed": "Failed to load resource packs",
"respackopts.loadError": "There is an issue with the resource pack configs. Please take a look at the game log",
"respackopts.configure": "Configure",
"respackopts.mainconfig": "ResPackOpts",
"respackopts.mainconfig.debugCommands": "Debug Commands",
"respackopts.mainconfig.debugLogs": "Debug Logs",
"respackopts.mainconfig.dashloaderCompat": "Dashloader compatibility",
"respackopts.jfconfig.title": "ResPackOpts",
"respackopts.jfconfig.debugCommands": "Debug Commands",
"respackopts.jfconfig.debugLogs": "Debug Logs",
"respackopts.jfconfig.dashloaderCompat": "Dashloader compatibility",
"respackopts.invalid": "Invalid value",
"respackopts.dumpSucceeded": "Successfully dumped the resource to %s",
"respackopts.dumpFailed": "Could not dump the requested resource, look at your log for details",

View File

@ -2,6 +2,6 @@
"respackopts.loadFailed": "Nie udało się załadować paczek zasobów",
"respackopts.loadError": "Wystąpił problem z konfiguracją paczki zasobów. Sprawdź logi gry",
"respackopts.configure": "Konfiguruj",
"respackopts.mainconfig": "ResPackOpts",
"respackopts.jfconfig.title": "ResPackOpts",
"respackopts.invalid": "Nieprawidłowa wartość"
}

View File

@ -18,9 +18,6 @@
"client": [
"io.gitlab.jfronny.respackopts.RespackoptsClient"
],
"modmenu": [
"io.gitlab.jfronny.respackopts.integration.ModMenuCompat"
],
"frex": [
"io.gitlab.jfronny.respackopts.integration.FrexCompat"
],
@ -29,6 +26,9 @@
],
"respackopts:client_save_hook": [
"io.gitlab.jfronny.respackopts.RespackoptsClient"
],
"libjf:config": [
"io.gitlab.jfronny.respackopts.RespackoptsConfig"
]
},
"mixins": [
@ -42,9 +42,9 @@
"fabricloader": ">=0.12.0",
"fabric": "*",
"minecraft": "*",
"cloth-config2": ">=7.0.0",
"libjf-data-manipulation-v0": ">=2.0",
"libjf-base": ">=2.0"
"libjf-data-manipulation-v0": ">=3.0.0",
"libjf-base": ">=3.0.0",
"libjf-config-core-v1": ">=3.0.0"
},
"conflicts": {
"libjf": "<2.0"

View File

@ -3,7 +3,6 @@ package io.gitlab.jfronny.respackopts.test;
//import io.gitlab.jfronny.respackopts.model.ConfigFile;
import io.gitlab.jfronny.respackopts.model.tree.*;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.NumericEntryType;
//import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
//import org.junit.jupiter.api.BeforeEach;
@ -90,7 +89,7 @@ class ConfigTreeTest {
ConfigBranch cb = new ConfigBranch();
ConfigBranch cbNew = new ConfigBranch();
cb.add(testEntryName, new ConfigBooleanEntry(false));
cbNew.add(testEntryName, new ConfigNumericEntry(NumericEntryType.Slider));
cbNew.add(testEntryName, new ConfigNumericEntry());
LOGGER.info("Expecting warning message");
cbNew.sync(cb, ConfigSyncMode.RESPACK_LOAD);
LOGGER.info("Expected warning end");