Use LibJF 2.0, make configs more powerful

This commit is contained in:
JFronny 2021-10-03 16:45:29 +02:00
parent 18886c6635
commit 35bda8675b
No known key found for this signature in database
GPG Key ID: BEC5ACBBD4EE17E5
51 changed files with 688 additions and 588 deletions

5
.gitignore vendored
View File

@ -129,3 +129,8 @@ public/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
.classpath
bin
.project
.settings

View File

@ -13,6 +13,10 @@ repositories {
includeGroup "net.oskarstrom"
}
}
maven {
url "https://gitlab.com/api/v4/projects/25805200/packages/maven"
}
mavenCentral()
}
dependencies {
@ -20,14 +24,18 @@ dependencies {
mappings "net.fabricmc:yarn:${project.minecraft_version}+${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:0.39.2+1.17"
modImplementation "com.terraformersmc:modmenu:2.0.5"
download("https://gitlab.com/jfmods/LibJF/-/jobs/artifacts/master/raw/latest-dev.jar?job=build_test", "libjf")
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_version}")
modImplementation("com.terraformersmc:modmenu:2.0.5")
include modImplementation("com.github.MeteorDevelopment:starscript:0.1.5")
modApi("me.shedaniel.cloth:cloth-config-fabric:5.0.38")
modCompileOnly "grondag:frex-mc117:+"
include modImplementation("io.gitlab.jfronny.libjf:libjf-data-manipulation-v0:${project.jfapi_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
include("io.gitlab.jfronny.libjf:libjf-unsafe-v0:${project.jfapi_version}")
include("io.gitlab.jfronny.libjf:libjf-base-v0:${project.jfapi_version}")
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation('org.junit.jupiter:junit-jupiter:5.8.1')
//Canvas for FREX testing
//modRuntime("grondag:canvas-mc117-1.17:+") {

59
docs/AdvancedConfig.md Normal file
View File

@ -0,0 +1,59 @@
# Advanced config entries
## Common info
Every advanced entry will be represented as a json object.
Every entry can contain the following properties, regardless of its type:
| Name | Required | Description |
| --- | --- | --- |
| type | yes | The type of the entries. Each one can contain specific info, more on that below |
| default | yes | The default value for this entry. This is what you would specify in a normal config entry (except for enums, where this is just the default entries name) |
| reloadType | no | `"Resource"` or `"Simple"`, specifies whether resources have to be reloaded if this is changed. Frex shaders will be reloaded anyways |
## 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.
### Example:
```json
{
"id": "examplePack",
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": {
"type": "slider", // can also be "number" for a normal number
"default": 5,
"min": 0,
"max": 10
}
}
}
```
### Result:
![configExampleSlider](./img/ExamplePackSlider.png)
## Booleans/Toggles
These do not expose any additional options, look under `Common info`. Their type is `boolean`
## Enum entries/Selecting from a list
The possible values for this entry will be under `values`, while the `default` value will just be a string.
Their type is `enum`
### Example:
```json
{
"id": "examplePack",
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": {
"type": "enum",
"default": "Second",
"values": ["First", "Second", "Third"]
}
}
}
```
### Result:
![configExampleSlider](./img/ExamplePackEnum2.png)

View File

@ -15,7 +15,7 @@ You can add [translations](./Translations.md) to work around this in the UI show
```json
{
"id": "<PackID>",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
// Your config options here
@ -37,7 +37,7 @@ To add a boolean entry, add code like this: `"entryName": <Default Option (true/
```json
{
"id": "examplePack",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someTexture": true,
@ -58,7 +58,7 @@ A number box follows the same principle as a boolean: `"entryName": Default Numb
```json
{
"id": "examplePack",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": 10
@ -70,24 +70,7 @@ A number box follows the same principle as a boolean: `"entryName": Default Numb
## 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.
### Example:
```json
{
"id": "examplePack",
"version": 5,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": {
"min": 0,
"default": 5,
"max": 10
}
}
}
```
### Result:
![configExampleSlider](./img/ExamplePackSlider.png)
More information is available [here](AdvancedConfig.md)
## Select From a list
To allow users to select one entry from a list, you can use a json array with string entries. *Numbers/etc are not supported*
@ -96,7 +79,7 @@ To allow users to select one entry from a list, you can use a json array with st
```json
{
"id": "examplePack",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": [
@ -116,7 +99,7 @@ To allow users to select one entry from a list, you can use a json array with st
```json
{
"id": "examplePack",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someCategory": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -3,16 +3,19 @@ org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://fabricmc.net/versions.html
minecraft_version=1.17.1
yarn_mappings=build.43
loader_version=0.11.6
yarn_mappings=build.61
loader_version=0.11.7
# Mod Properties
mod_version=2.7.1
mod_version=2.7.2
maven_group=io.gitlab.jfronny
archives_base_name=respackopts
# Dependencies
fabric_version=0.40.1+1.17
jfapi_version=2.0+381554148
modrinth_id=TiF5QWZY
modrinth_required_dependencies=mzVbb1XI, dOW0jmMj
modrinth_required_dependencies=3CD6YUw1
modrinth_optional_dependencies=
curseforge_id=430090
curseforge_required_dependencies=cloth-config, modmenu, libjf
curseforge_required_dependencies=cloth-config, modmenu
curseforge_optional_dependencies=

View File

@ -12,6 +12,7 @@ nav:
- 'README.md'
- 'Setting up':
- 'MainConfig.md'
- 'AdvancedConfig.md'
- 'Translations.md'
- 'Select files':
- 'ToggleFiles.md'

View File

@ -1,6 +1,6 @@
{
"id": "lumi",
"version": 5,
"version": 6,
"capabilities": ["FileFilter", "DirFilter", "DirFilterAdditive"],
"conf": {
"tonemap": [
@ -8,7 +8,11 @@
"vibrant",
"film"
],
"pbr": true,
"pbr": {
"type": "boolean",
"default": true,
"reloadType": "Simple"
},
"debugMode": [
"none",
"normal",

View File

@ -1,21 +1,35 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.respackopts.data.*;
import io.gitlab.jfronny.respackopts.data.condition.Condition;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.data.enums.PackCapability;
import io.gitlab.jfronny.respackopts.filters.DirFilterEventImpl;
import io.gitlab.jfronny.respackopts.gson.*;
import io.gitlab.jfronny.respackopts.gson.entry.BooleanEntrySerializer;
import io.gitlab.jfronny.respackopts.gson.entry.ConfigBranchSerializer;
import io.gitlab.jfronny.respackopts.gson.entry.EnumEntrySerializer;
import io.gitlab.jfronny.respackopts.gson.entry.NumericEntrySerializer;
import io.gitlab.jfronny.respackopts.util.RpoCommand;
import io.gitlab.jfronny.respackopts.data.entry.*;
import io.gitlab.jfronny.respackopts.filters.FileFilterEventImpl;
import io.gitlab.jfronny.respackopts.util.GuiFactory;
import meteordevelopment.starscript.Script;
import meteordevelopment.starscript.StandardLib;
import meteordevelopment.starscript.Starscript;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.gui.FabricGuiEntry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.resource.ResourcePack;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.Reader;
@ -34,28 +48,53 @@ public class Respackopts implements ClientModInitializer {
public static final Map<String, PackMeta> PACK_METAS = new HashMap<>();
public static final Map<String, String> DISPLAY_NAME_LOOKUP = new HashMap<>();
public static final Map<String, String> PACK_NAME_LOOKUP = new HashMap<>();
public static final Integer META_VERSION = 6;
public static final String FILE_EXTENSION = ".rpo";
public static final Gson GSON;
public static Starscript STAR_SCRIPT;
public static final Identifier CONF_ID = new Identifier(RpoModInfo.ID, "conf.json");
public static final String ID = "respackopts";
public static final Logger LOGGER = LogManager.getFormatterLogger(ID);
public static final Identifier CONF_ID = new Identifier(ID, "conf.json");
public static final Set<Runnable> SAVE_ACTIONS = new HashSet<>();
public static final GuiFactory GUI_FACTORY = new GuiFactory();
public static GuiFactory factory = new GuiFactory();
public static boolean forcePackReload = false;
public static Path 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(Script.class, new ScriptDeserializer())
.registerTypeAdapter(FileRpo.class, new FileRpoDeserializer())
.registerTypeAdapter(DirRpo.class, new DirRpoDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.registerTypeAdapterFactory(new SingleElementSetTypeAdapterFactory())
.setPrettyPrinting()
.create();
try {
CONF_DIR = FabricLoader.getInstance().getConfigDir().resolve(ID);
CONFIG = ConfigFile.load();
} catch (Throwable e) {
LOGGER.error("Could not resolve config directory", e);
}
try {
STAR_SCRIPT = new Starscript();
} catch (Throwable e) {
} catch (Exception e) {
FabricGuiEntry.displayCriticalError(new Exception("Respackopts could not initialize Starscript. Expect issues with packs that use it", e), false);
}
StandardLib.init(STAR_SCRIPT);
}
public static String getId(WrappedPack pack) {
public static String getId(ResourcePack pack) {
return PACK_NAME_LOOKUP.get(pack.getName());
}
public static boolean hasCapability(WrappedPack pack, PackCapability capability) {
public static boolean hasCapability(ResourcePack pack, PackCapability capability) {
String id = getId(pack);
if (id == null) return false;
return PACK_METAS.get(id).capabilities.contains(capability);
@ -64,12 +103,12 @@ public class Respackopts implements ClientModInitializer {
@Override
public void onInitializeClient() {
try {
Files.createDirectories(RpoModInfo.CONF_DIR);
Files.createDirectories(CONF_DIR);
} catch (IOException e) {
RpoModInfo.LOGGER.error("Could not initialize config directory", e);
LOGGER.error("Could not initialize config directory", e);
}
if (RpoModInfo.CONFIG.debugLogs)
SAVE_ACTIONS.add(() -> RpoModInfo.LOGGER.info("Save"));
if (CONFIG.debugLogs)
SAVE_ACTIONS.add(() -> LOGGER.info("Save"));
SAVE_ACTIONS.add(() -> {
for (Map.Entry<String, ConfigBranch> e : CONFIG_BRANCH.entrySet()) {
STAR_SCRIPT.set(sanitizeString(e.getKey()), () -> e.getValue().buildStarscript());
@ -77,19 +116,19 @@ public class Respackopts implements ClientModInitializer {
});
DirFilterEventImpl.init();
FileFilterEventImpl.init();
if (RpoModInfo.CONFIG.debugCommands)
if (CONFIG.debugCommands)
RpoCommand.register();
}
public static void save() {
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Saving configs");
if (CONFIG.debugLogs)
LOGGER.info("Saving configs");
for (Map.Entry<String, ConfigBranch> e : CONFIG_BRANCH.entrySet()) {
try (Writer writer = Files.newBufferedWriter(RpoModInfo.CONF_DIR.resolve(e.getKey() + ".json"))) {
RpoModInfo.GSON.toJson(e.getValue(), writer);
try (Writer writer = Files.newBufferedWriter(CONF_DIR.resolve(e.getKey() + ".json"))) {
GSON.toJson(e.getValue(), writer);
writer.flush();
} catch (IOException ex) {
RpoModInfo.LOGGER.error("Could not save config", ex);
LOGGER.error("Could not save config", ex);
}
}
for (Runnable action : SAVE_ACTIONS) {
@ -98,17 +137,17 @@ public class Respackopts implements ClientModInitializer {
}
public static void load(String id) {
Path q = RpoModInfo.CONF_DIR.resolve(id + ".json");
Path q = CONF_DIR.resolve(id + ".json");
if (Files.exists(q)) {
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Loading configs for: " + id);
if (CONFIG.debugLogs)
LOGGER.info("Loading configs for: " + id);
try (Reader reader = Files.newBufferedReader(q)) {
ConfigBranch b = RpoModInfo.GSON.fromJson(reader, ConfigBranch.class);
ConfigBranch b = GSON.fromJson(reader, ConfigBranch.class);
if (CONFIG_BRANCH.containsKey(id))
CONFIG_BRANCH.get(id).sync(b, SyncMode.CONF_LOAD);
CONFIG_BRANCH.get(id).sync(b, ConfigSyncMode.CONF_LOAD);
STAR_SCRIPT.set(sanitizeString(id), b::buildStarscript);
} catch (IOException e) {
RpoModInfo.LOGGER.error("Failed to load " + id, e);
LOGGER.error("Failed to load " + id, e);
}
}
}
@ -126,8 +165,8 @@ public class Respackopts implements ClientModInitializer {
}
public static CompletableFuture<Void> forceReloadResources() {
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Forcing resource reload");
if (CONFIG.debugLogs)
LOGGER.info("Forcing resource reload");
return CompletableFuture.allOf(MinecraftClient.getInstance().reloadResources(),
reloadData());
}

View File

@ -1,50 +0,0 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.respackopts.data.ConfigFile;
import io.gitlab.jfronny.respackopts.data.DirRpo;
import io.gitlab.jfronny.respackopts.data.FileRpo;
import io.gitlab.jfronny.respackopts.data.condition.Condition;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBooleanEntry;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBranch;
import io.gitlab.jfronny.respackopts.data.entry.ConfigEnumEntry;
import io.gitlab.jfronny.respackopts.data.entry.ConfigNumericEntry;
import io.gitlab.jfronny.respackopts.gson.*;
import meteordevelopment.starscript.Script;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
public class RpoModInfo {
public static final Integer META_VERSION = 5;
public static final String ID = "respackopts";
public static final Logger LOGGER = LogManager.getFormatterLogger(ID);
public static final String FILE_EXTENSION = ".rpo";
public static Path CONF_DIR;
public static ConfigFile CONFIG;
public static final Gson GSON;
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(Script.class, new ScriptDeserializer())
.registerTypeAdapter(FileRpo.class, new FileRpoDeserializer())
.registerTypeAdapter(DirRpo.class, new DirRpoDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.registerTypeAdapterFactory(new SingleElementSetTypeAdapterFactory())
.setPrettyPrinting()
.create();
try {
CONF_DIR = FabricLoader.getInstance().getConfigDir().resolve(ID);
CONFIG = ConfigFile.load();
} catch (Throwable e) {
LOGGER.error("Could not resolve config directory", e);
}
}
}

View File

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

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.respackopts.data;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBranch;
import io.gitlab.jfronny.respackopts.data.enums.PackCapability;
import java.util.HashSet;
import java.util.Set;

View File

@ -31,6 +31,6 @@ public record RpoBooleanCondition(String name) implements Condition {
}
}
}
throw new RpoFormatException("Could not find pack with specified ID");
throw new RpoFormatException("Could not find pack with specified ID: " + packId);
}
}

View File

@ -1,12 +1,7 @@
package io.gitlab.jfronny.respackopts.data.entry;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import meteordevelopment.starscript.value.Value;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.function.Supplier;
public class ConfigBooleanEntry extends ConfigEntry<Boolean> {
public ConfigBooleanEntry(boolean v) {
@ -35,11 +30,14 @@ public class ConfigBooleanEntry extends ConfigEntry<Boolean> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(ConfigEntryBuilder entryBuilder, Text name, Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName, String translationPrefix) {
return entryBuilder.startBooleanToggle(name, getValue())
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam) {
return guiEntryBuilderParam.entryBuilder().startBooleanToggle(guiEntryBuilderParam.name(), getValue())
.setDefaultValue(getDefault())
.setSaveConsumer(this::setValue)
.setTooltipSupplier(tooltipSupplier)
.setSaveConsumer(value -> {
if (getValue() != value) guiEntryBuilderParam.saveCallback();
setValue(value);
})
.setTooltipSupplier(guiEntryBuilderParam.tooltipSupplier())
.build();
}
}

View File

@ -2,19 +2,15 @@ package io.gitlab.jfronny.respackopts.data.entry;
import com.google.common.collect.ImmutableMap;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder;
import meteordevelopment.starscript.value.Value;
import meteordevelopment.starscript.value.ValueMap;
import net.minecraft.text.Text;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
public ConfigBranch() {
@ -44,33 +40,33 @@ public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
}
@Override
public void sync(ConfigEntry<Map<String, ConfigEntry<?>>> source, SyncMode mode) {
public void sync(ConfigEntry<Map<String, ConfigEntry<?>>> source, ConfigSyncMode mode) {
for (Map.Entry<String, ConfigEntry<?>> e : source.getValue().entrySet()) {
if (!has(e.getKey())) {
if (mode == SyncMode.RESPACK_LOAD)
if (mode == ConfigSyncMode.RESPACK_LOAD)
add(e.getKey(), e.getValue().clone());
} else {
ConfigEntry<?> current = get(e.getKey());
if (e.getValue().getClass().equals(current.getClass())) {
if (e.getValue().getEntryType().equals(current.getEntryType())) {
syncSub(current, (ConfigEntry)e.getValue(), mode);
}
else {
if (mode == SyncMode.RESPACK_LOAD) {
RpoModInfo.LOGGER.warn("Type mismatch in config (" + getName() + "), overwriting");
if (mode == ConfigSyncMode.RESPACK_LOAD) {
Respackopts.LOGGER.warn("Type mismatch in config (" + getName() + "), overwriting");
add(e.getKey(), e.getValue().clone());
} else
RpoModInfo.LOGGER.warn("Type mismatch in config (" + getName() + "), ignoring");
Respackopts.LOGGER.warn("Type mismatch in config (" + getName() + "), ignoring");
}
}
}
if (mode == SyncMode.RESPACK_LOAD)
if (mode == ConfigSyncMode.RESPACK_LOAD)
for (Map.Entry<String, ConfigEntry<?>> e : getValue().entrySet()) {
if (!source.getValue().containsKey(e.getKey()))
super.getValue().remove(e.getKey());
}
}
private <T> void syncSub(ConfigEntry<T> current, ConfigEntry<T> next, SyncMode mode) {
private <T> void syncSub(ConfigEntry<T> current, ConfigEntry<T> next, ConfigSyncMode mode) {
current.sync(next, mode);
}
@ -118,10 +114,10 @@ public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(ConfigEntryBuilder entryBuilder, Text name, Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName, String translationPrefix) {
SubCategoryBuilder sc = entryBuilder.startSubCategory(name);
Respackopts.factory.buildCategory(this, screenId, sc::add, entryBuilder, entryName);
sc.setTooltipSupplier(tooltipSupplier);
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam) {
SubCategoryBuilder sc = guiEntryBuilderParam.entryBuilder().startSubCategory(guiEntryBuilderParam.name());
Respackopts.GUI_FACTORY.buildCategory(this, guiEntryBuilderParam.screenId(), sc::add, guiEntryBuilderParam.agg(), guiEntryBuilderParam.entryBuilder(), guiEntryBuilderParam.entryName());
sc.setTooltipSupplier(guiEntryBuilderParam.tooltipSupplier());
return sc.build();
}

View File

@ -1,17 +1,18 @@
package io.gitlab.jfronny.respackopts.data.entry;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.data.enums.PackReloadType;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import meteordevelopment.starscript.value.Value;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.function.Supplier;
import java.lang.reflect.Type;
public abstract class ConfigEntry<T> {
private T defaultValue;
private T value;
private PackReloadType reloadType = PackReloadType.Resource;
protected int version;
protected ConfigBranch parent;
@ -34,7 +35,7 @@ public abstract class ConfigEntry<T> {
public T getValue() {
if (value == null) {
if (defaultValue == null) {
RpoModInfo.LOGGER.warn("No default value or current value set for entry, returning null in " + getName());
Respackopts.LOGGER.warn("No default value or current value set for entry, returning null in " + getName());
return null;
}
value = getDefault();
@ -42,28 +43,30 @@ public abstract class ConfigEntry<T> {
return value;
}
public void setValue(T value) {
public T setValue(T value) {
if (value != null)
this.value = value;
return this.value;
}
public T getDefault() {
if (defaultValue == null) {
defaultValue = getValue();
RpoModInfo.LOGGER.warn("No default value set for entry, using current in " + getName());
Respackopts.LOGGER.warn("No default value set for entry, using current in " + getName());
}
return defaultValue;
}
public void setDefault(T value) {
public T setDefault(T value) {
if (value != null)
defaultValue = value;
return defaultValue;
}
public void sync(ConfigEntry<T> source, SyncMode mode) {
if (mode == SyncMode.RESPACK_LOAD)
public void sync(ConfigEntry<T> source, ConfigSyncMode mode) {
if (mode == ConfigSyncMode.RESPACK_LOAD)
setDefault(source.getDefault());
if (mode == SyncMode.CONF_LOAD)
if (mode == ConfigSyncMode.CONF_LOAD)
setValue(source.getValue());
}
@ -84,9 +87,21 @@ public abstract class ConfigEntry<T> {
public abstract Value buildStarscript();
public abstract AbstractConfigListEntry<?> buildEntry(ConfigEntryBuilder entryBuilder, Text name, Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName, String translationPrefix);
public abstract AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam);
public String getEntryType() {
return "field";
}
public Type getValueType() {
return new TypeToken<T>(){}.getType();
}
public PackReloadType getReloadType() {
return reloadType;
}
public void setReloadType(PackReloadType reloadType) {
this.reloadType = reloadType;
}
}

View File

@ -1,19 +1,17 @@
package io.gitlab.jfronny.respackopts.data.entry;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.util.GuiFactory;
import io.gitlab.jfronny.respackopts.Respackopts;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.impl.builders.DropdownMenuBuilder;
import meteordevelopment.starscript.value.Value;
import net.minecraft.text.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
public class ConfigEnumEntry extends ConfigEntry<String> {
public List<String> values = new ArrayList<>();
@ -51,10 +49,10 @@ public class ConfigEnumEntry extends ConfigEntry<String> {
}
@Override
public void sync(ConfigEntry<String> source, SyncMode mode) {
public void sync(ConfigEntry<String> source, ConfigSyncMode mode) {
super.sync(source, mode);
ConfigEnumEntry n = (ConfigEnumEntry) source;
if (mode == SyncMode.RESPACK_LOAD) {
if (mode == ConfigSyncMode.RESPACK_LOAD) {
if (n.values != null && !n.values.isEmpty())
values = n.values;
}
@ -63,7 +61,7 @@ public class ConfigEnumEntry extends ConfigEntry<String> {
setValue(values.get(n.nextValue));
}
else {
RpoModInfo.LOGGER.error("Could not load default value for enum in " + getName());
Respackopts.LOGGER.error("Could not load default value for enum in " + getName());
}
}
}
@ -119,16 +117,19 @@ public class ConfigEnumEntry extends ConfigEntry<String> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(ConfigEntryBuilder entryBuilder, Text name, Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName, String translationPrefix) {
Function<String, Text> entryText = s -> GuiFactory.getText(s, ("".equals(translationPrefix) ? "" : translationPrefix + ".") + entryName);
return entryBuilder.startDropdownMenu(name,
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam) {
Function<String, Text> entryText = s -> GuiFactory.getText(s, ("".equals(guiEntryBuilderParam.translationPrefix()) ? "" : guiEntryBuilderParam.translationPrefix() + ".") + guiEntryBuilderParam.entryName());
return guiEntryBuilderParam.entryBuilder().startDropdownMenu(guiEntryBuilderParam.name(),
DropdownMenuBuilder.TopCellElementBuilder.of(getValue(), s -> s, entryText),
DropdownMenuBuilder.CellCreatorBuilder.of(entryText))
.setSuggestionMode(false)
.setDefaultValue(getDefault())
.setSelections(() -> values.iterator())
.setSaveConsumer(this::setValue)
.setTooltipSupplier(tooltipSupplier)
.setSaveConsumer(value -> {
if (!Objects.equals(getValue(), value)) guiEntryBuilderParam.saveCallback();
setValue(value);
})
.setTooltipSupplier(guiEntryBuilderParam.tooltipSupplier())
.build();
}
}

View File

@ -1,26 +1,38 @@
package io.gitlab.jfronny.respackopts.data.entry;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.data.enums.NumericEntryType;
import io.gitlab.jfronny.respackopts.gson.entry.NumericEntrySerializer;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.impl.builders.DoubleFieldBuilder;
import meteordevelopment.starscript.value.Value;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.Objects;
public class ConfigNumericEntry extends ConfigEntry<Double> {
public Double min;
public Double max;
public Double min = null;
public Double max = null;
public final NumericEntryType type;
public ConfigNumericEntry() {
public ConfigNumericEntry(NumericEntryType type) {
this.type = type;
setValue(0d);
}
@Override
public void sync(ConfigEntry<Double> source, SyncMode mode) {
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.isWhole(value))
throw new RuntimeException("Default number value must be a whole number");
return super.setDefault(value);
}
@Override
public void sync(ConfigEntry<Double> source, ConfigSyncMode mode) {
super.sync(source, mode);
ConfigNumericEntry n = (ConfigNumericEntry) source;
if (mode == SyncMode.RESPACK_LOAD) {
if (mode == ConfigSyncMode.RESPACK_LOAD) {
if (n.min != null)
min = n.min;
if (n.max != null)
@ -42,7 +54,7 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
@Override
public ConfigEntry<Double> clone() {
ConfigNumericEntry ce = new ConfigNumericEntry();
ConfigNumericEntry ce = new ConfigNumericEntry(type);
ce.max = max;
ce.min = min;
ce.setValue(getValue());
@ -64,21 +76,30 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
}
@Override
public AbstractConfigListEntry<?> buildEntry(ConfigEntryBuilder entryBuilder, Text name, Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName, String translationPrefix) {
if (min != null && max != null) {
return entryBuilder.startIntSlider(name,
public AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam) {
if (type == NumericEntryType.Slider) {
return guiEntryBuilderParam.entryBuilder().startIntSlider(guiEntryBuilderParam.name(),
getValue().intValue(), min.intValue(), max.intValue())
.setDefaultValue(getDefault().intValue())
.setSaveConsumer(v -> setValue(v.doubleValue()))
.setTooltipSupplier(tooltipSupplier)
.setSaveConsumer(value -> {
if (!Objects.equals(getValue(), value.doubleValue())) guiEntryBuilderParam.saveCallback();
setValue(value.doubleValue());
})
.setTooltipSupplier(guiEntryBuilderParam.tooltipSupplier())
.build();
}
else {
return entryBuilder.startDoubleField(name, getValue())
DoubleFieldBuilder builder = guiEntryBuilderParam.entryBuilder().startDoubleField(guiEntryBuilderParam.name(), getValue())
.setDefaultValue(getDefault())
.setSaveConsumer(this::setValue)
.setTooltipSupplier(tooltipSupplier)
.build();
.setSaveConsumer(value -> {
if (!Objects.equals(getValue(), value)) guiEntryBuilderParam.saveCallback();
setValue(value);
})
.setTooltipSupplier(guiEntryBuilderParam.tooltipSupplier());
if (min != null) builder.setMin(min);
if (max != null) builder.setMax(max);
return builder.build();
}
}
}

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.respackopts.data.entry;
import io.gitlab.jfronny.respackopts.data.enums.PackReloadType;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
public record GuiEntryBuilderParam(ConfigEntryBuilder entryBuilder, Text name,
Supplier<Optional<Text[]>> tooltipSupplier, String screenId, String entryName,
String translationPrefix, Runnable onSave, Consumer<PackReloadType> agg) {
public void saveCallback() {
onSave.run();
}
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.respackopts.data.entry;
public enum SyncMode {
RESPACK_LOAD, CONF_LOAD
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.respackopts.data.enums;
public enum ConfigSyncMode {
RESPACK_LOAD, CONF_LOAD
}

View File

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

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.respackopts.data;
package io.gitlab.jfronny.respackopts.data.enums;
public enum PackCapability {
FileFilter, DirFilter, DirFilterAdditive

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.respackopts.data.enums;
import java.util.function.Consumer;
public enum PackReloadType {
Resource, Simple;
public static class Aggregator implements Consumer<PackReloadType> {
private PackReloadType reloadType = Simple;
@Override
public void accept(PackReloadType packReloadType) {
if (packReloadType == PackReloadType.Resource) {
reloadType = PackReloadType.Resource;
}
}
public PackReloadType get() {
return reloadType;
}
}
}

View File

@ -1,13 +1,12 @@
package io.gitlab.jfronny.respackopts.filters;
import io.gitlab.jfronny.libjf.data.ResourcePath;
import io.gitlab.jfronny.libjf.data.UserResourceEvents;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.DirRpo;
import io.gitlab.jfronny.respackopts.data.PackCapability;
import io.gitlab.jfronny.respackopts.data.enums.PackCapability;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import net.minecraft.resource.ResourcePack;
import net.minecraft.util.Identifier;
import java.io.*;
@ -87,12 +86,12 @@ public class DirFilterEventImpl {
try {
return !rpo.conditions.evaluate(packId);
} catch (RpoFormatException e) {
RpoModInfo.LOGGER.error("Couldn't parse dir conditions", e);
Respackopts.LOGGER.error("Couldn't parse dir conditions", e);
}
return false;
}
private static DirRpo findDirRpo(WrappedPack pack, String name) {
private static DirRpo findDirRpo(ResourcePack pack, String name) {
int li = name.lastIndexOf('/');
if (li <= 0)
return null;
@ -107,15 +106,15 @@ public class DirFilterEventImpl {
catch (Exception e) {
return null;
}
if (pack.getUnderlying().contains(rp.getType(), rp.getId())) {
try (InputStream stream = pack.getUnderlying().open(rp.getType(), rp.getId()); Reader w = new InputStreamReader(stream)) {
drp = RpoModInfo.GSON.fromJson(w, DirRpo.class);
if (UserResourceEvents.disable(() -> pack.contains(rp.getType(), rp.getId()))) {
try (InputStream stream = UserResourceEvents.disable(() -> pack.open(rp.getType(), rp.getId())); Reader w = new InputStreamReader(stream)) {
drp = Respackopts.GSON.fromJson(w, DirRpo.class);
drp.path = name;
if (drp.fallback != null && !drp.fallback.endsWith("/"))
drp.fallback += "/";
return drp;
} catch (IOException e) {
RpoModInfo.LOGGER.error("Couldn't open dir rpo", e);
Respackopts.LOGGER.error("Couldn't open dir rpo", e);
}
}
return null;

View File

@ -1,14 +1,14 @@
package io.gitlab.jfronny.respackopts.filters;
import io.gitlab.jfronny.libjf.data.ResourcePath;
import io.gitlab.jfronny.libjf.data.UserResourceEvents;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.data.PackCapability;
import io.gitlab.jfronny.respackopts.data.enums.PackCapability;
import io.gitlab.jfronny.respackopts.filters.util.FileExclusionProvider;
import io.gitlab.jfronny.respackopts.filters.util.FileExpansionProvider;
import io.gitlab.jfronny.respackopts.filters.util.FileFallbackProvider;
import net.minecraft.resource.AbstractFileResourcePack;
import net.minecraft.resource.ResourcePack;
import net.minecraft.util.Identifier;
import java.util.concurrent.atomic.AtomicBoolean;
@ -50,7 +50,7 @@ public class FileFilterEventImpl {
return true;
}
else {
if (pack.getUnderlying().contains(type, new Identifier(id.getNamespace(), id.getPath() + ".rpo")) && FileFallbackProvider.fileVisible(pack, name)) {
if (UserResourceEvents.disable(() -> pack.contains(type, new Identifier(id.getNamespace(), id.getPath() + ".rpo"))) && FileFallbackProvider.fileVisible(pack, name)) {
containsFileWasFallback.set(true);
return true;
}
@ -59,7 +59,7 @@ public class FileFilterEventImpl {
});
}
private static boolean skip(WrappedPack pack) {
return !(pack.getUnderlying() instanceof AbstractFileResourcePack) || !Respackopts.hasCapability(pack, PackCapability.FileFilter);
private static boolean skip(ResourcePack pack) {
return !(pack instanceof AbstractFileResourcePack) || !Respackopts.hasCapability(pack, PackCapability.FileFilter);
}
}

View File

@ -1,19 +1,18 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import net.minecraft.resource.ResourcePack;
public class FileExclusionProvider {
public static boolean fileHidden(WrappedPack pack, String name) {
public static boolean fileHidden(ResourcePack pack, String name) {
return FileRpoSearchProvider.modifyWithRpo(name, pack, rpo -> {
if (rpo.conditions == null)
return false;
try {
return !rpo.conditions.evaluate(Respackopts.getId(pack));
} catch (RpoFormatException e) {
RpoModInfo.LOGGER.error("Could not evaluate condition " + name, e);
Respackopts.LOGGER.error("Could not evaluate condition " + name, e);
return false;
}
}, false);

View File

@ -1,9 +1,8 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import meteordevelopment.starscript.Script;
import net.minecraft.resource.ResourcePack;
import java.io.*;
import java.util.Map;
@ -17,14 +16,14 @@ public class FileExpansionProvider {
return new ByteArrayInputStream(s.getBytes());
}
public static InputStream replace(InputStream inputStream, WrappedPack pack, String name) {
public static InputStream replace(InputStream inputStream, ResourcePack pack, String name) {
return FileRpoSearchProvider.modifyWithRpo(name, pack, rpo -> {
if (rpo.expansions == null || rpo.expansions.isEmpty())
return inputStream;
try {
return replace(inputStream, rpo.expansions);
} catch (IOException e) {
RpoModInfo.LOGGER.error("Could not perform file expansion on " + name, e);
Respackopts.LOGGER.error("Could not perform file expansion on " + name, e);
return inputStream;
}
}, inputStream);

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.libjf.data.ResourcePath;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.respackopts.Respackopts;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
@ -10,7 +10,7 @@ import java.io.InputStream;
import java.util.Collection;
public class FileFallbackProvider {
public static boolean fileVisible(WrappedPack pack, String name) {
public static boolean fileVisible(ResourcePack pack, String name) {
return FileRpoSearchProvider.modifyWithRpo(name, pack, rpo -> {
if (rpo.fallbacks != null) {
for (String s : rpo.fallbacks) {
@ -23,7 +23,7 @@ public class FileFallbackProvider {
}, false);
}
public static InputStream getReplacement(WrappedPack pack, String name) {
public static InputStream getReplacement(ResourcePack pack, String name) {
return FileRpoSearchProvider.modifyWithRpo(name, pack, rpo -> {
try {
if (rpo.fallbacks != null) {
@ -33,21 +33,21 @@ public class FileFallbackProvider {
return pack.open(tmp.getType(), tmp.getId());
}
}
RpoModInfo.LOGGER.error("Could not determine replacement for " + name);
Respackopts.LOGGER.error("Could not determine replacement for " + name);
}
catch (Exception e) {
RpoModInfo.LOGGER.error("Could not determine replacement for " + name, e);
Respackopts.LOGGER.error("Could not determine replacement for " + name, e);
}
return null;
}, null);
}
public static void addFallbackResources(WrappedPack pack, Collection<Identifier> ret, String namespace, ResourceType type) {
public static void addFallbackResources(ResourcePack pack, Collection<Identifier> ret, String namespace, ResourceType type) {
// Warning: the Identifiers here DON'T CONTAIN THE TYPE! Therefore, it needs to be added when calling a method that generates a ResourcePath!
for (Identifier identifier : ret) {
String path = identifier.getPath();
if (FileRpoSearchProvider.isRpo(path)) {
String expectedTarget = path.substring(0, path.length() - RpoModInfo.FILE_EXTENSION.length());
String expectedTarget = path.substring(0, path.length() - Respackopts.FILE_EXTENSION.length());
if (ret.stream().noneMatch(s -> s.getPath().equals(expectedTarget)) && fileVisible(pack, type.getDirectory() + "/" + expectedTarget)) {
ret.add(new Identifier(namespace, expectedTarget));
}

View File

@ -1,9 +1,9 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.libjf.data.ResourcePath;
import io.gitlab.jfronny.libjf.data.WrappedPack;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.data.FileRpo;
import net.minecraft.resource.ResourcePack;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -11,26 +11,26 @@ import java.io.Reader;
public class FileRpoSearchProvider {
public static boolean isRpo(String fileName) {
return fileName.endsWith(RpoModInfo.FILE_EXTENSION);
return fileName.endsWith(Respackopts.FILE_EXTENSION);
}
public static <T> T modifyWithRpo(String fileName, WrappedPack pack, ModifiedGenerator<T> getModified, T defaultValue) {
public static <T> T modifyWithRpo(String fileName, ResourcePack pack, ModifiedGenerator<T> getModified, T defaultValue) {
if (FileRpoSearchProvider.isRpo(fileName))
return defaultValue;
ResourcePath rpoPath;
try {
rpoPath = new ResourcePath(fileName + RpoModInfo.FILE_EXTENSION);
rpoPath = new ResourcePath(fileName + Respackopts.FILE_EXTENSION);
if (!pack.contains(rpoPath.getType(), rpoPath.getId()))
return defaultValue;
} catch (Throwable e) {
RpoModInfo.LOGGER.error("Could not check file filter status", e);
Respackopts.LOGGER.error("Could not check file filter status", e);
return defaultValue;
}
try (InputStream stream = pack.open(rpoPath.getType(), rpoPath.getId()); Reader w = new InputStreamReader(stream)) {
return getModified.getModified(RpoModInfo.GSON.fromJson(w, FileRpo.class));
return getModified.getModified(Respackopts.GSON.fromJson(w, FileRpo.class));
}
catch (Exception e) {
RpoModInfo.LOGGER.error("Could not generate replacement for " + rpoPath.getName() + " in " + pack.getName(), e);
Respackopts.LOGGER.error("Could not generate replacement for " + rpoPath.getName() + " in " + pack.getName(), e);
return defaultValue;
}
}

View File

@ -1,50 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.data.entry.*;
import java.lang.reflect.Type;
import java.util.Map;
public class ConfigBranchSerializer implements JsonSerializer<ConfigBranch>, JsonDeserializer<ConfigBranch> {
@Override
public JsonElement serialize(ConfigBranch src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject o = new JsonObject();
for (Map.Entry<String, ConfigEntry<?>> entry : src.getValue().entrySet()) {
o.add(entry.getKey(), context.serialize(entry.getValue()));
}
return o;
}
@Override
public ConfigBranch deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (!json.isJsonObject())
throw new JsonSyntaxException("ConfigBranch is not an object");
JsonObject o = json.getAsJsonObject();
ConfigBranch cbNew = new ConfigBranch();
for (Map.Entry<String, JsonElement> e : o.entrySet()) {
JsonElement j = e.getValue();
String s = e.getKey();
if (NumericEntrySerializer.isSlider(j))
cbNew.add(s, context.deserialize(j, ConfigNumericEntry.class));
else if (j.isJsonPrimitive()) {
JsonPrimitive p = j.getAsJsonPrimitive();
if (p.isBoolean())
cbNew.add(s, new ConfigBooleanEntry(p.getAsBoolean()));
else if (p.isNumber())
cbNew.add(s, context.deserialize(j, ConfigNumericEntry.class));
else if (p.isString())
cbNew.add(s, context.deserialize(j, ConfigEnumEntry.class));
}
else if (j.isJsonArray())
cbNew.add(s, context.deserialize(j, ConfigEnumEntry.class));
else if (j.isJsonObject())
cbNew.add(s, context.deserialize(j, ConfigBranch.class));
else if (j.isJsonNull())
throw new JsonSyntaxException("Unexpected json null");
else
throw new JsonSyntaxException("Unexpected json object type");
}
return cbNew;
}
}

View File

@ -1,56 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.data.entry.ConfigNumericEntry;
import java.lang.reflect.Type;
public class NumericEntrySerializer implements JsonSerializer<ConfigNumericEntry>, JsonDeserializer<ConfigNumericEntry> {
@Override
public JsonElement serialize(ConfigNumericEntry src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getValue());
}
@Override
public ConfigNumericEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
ConfigNumericEntry result = new ConfigNumericEntry();
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isNumber()) {
result.setValue(json.getAsDouble());
result.setDefault(json.getAsDouble());
return result;
}
else if (isSlider(json)) {
JsonObject o = json.getAsJsonObject();
JsonElement def = o.get("default");
JsonElement min = o.get("min");
JsonElement max = o.get("max");
if (def.isJsonPrimitive() && def.getAsJsonPrimitive().isNumber()
&& min.isJsonPrimitive() && min.getAsJsonPrimitive().isNumber()
&& max.isJsonPrimitive() && max.getAsJsonPrimitive().isNumber()) {
double defV = def.getAsNumber().doubleValue();
double minV = min.getAsNumber().doubleValue();
double maxV = max.getAsNumber().doubleValue();
if (defV < minV || defV > maxV) {
throw new JsonSyntaxException("Default value out of range in slider definition");
}
if (isWhole(defV) && isWhole(minV) && isWhole(maxV)) {
result.setValue(defV);
result.setDefault(defV);
result.min = minV;
result.max = maxV;
return result;
}
throw new JsonSyntaxException("Expected whole number in slider definition");
}
throw new JsonSyntaxException("Expected numeric values in slider definition");
}
throw new JsonSyntaxException("Could not deserialize numeric entry");
}
public static boolean isSlider(JsonElement element) {
return element.isJsonObject() && element.getAsJsonObject().entrySet().stream().allMatch(s -> "default".equals(s.getKey()) || "min".equals(s.getKey()) || "max".equals(s.getKey()));
}
private boolean isWhole(double v) {
return v == Math.floor(v) && !Double.isInfinite(v);
}
}

View File

@ -4,7 +4,7 @@ import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.Respackopts;
import meteordevelopment.starscript.Script;
import meteordevelopment.starscript.compiler.Compiler;
import meteordevelopment.starscript.compiler.Parser;
@ -26,7 +26,7 @@ public class ScriptDeserializer implements JsonDeserializer<Script> {
Parser.Result result = Parser.parse(s);
if (result.hasErrors()) {
for (Error error : result.errors) {
RpoModInfo.LOGGER.error(error);
Respackopts.LOGGER.error(error);
}
throw new JsonParseException("Could not parse script: See errors above");
}

View File

@ -5,7 +5,7 @@ import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.Respackopts;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
@ -20,8 +20,8 @@ public class SingleElementSetTypeAdapterFactory implements TypeAdapterFactory {
if (typeToken.getRawType() != Set.class
|| !(type instanceof ParameterizedType pt))
return null;
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Using SingleElementSetTypeAdapter for " + typeToken.toString());
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Using SingleElementSetTypeAdapter for " + typeToken.toString());
Type elementType = pt.getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) createAdapter(elementAdapter);

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.respackopts.gson;
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBooleanEntry;
@ -8,7 +8,15 @@ import java.lang.reflect.Type;
public class BooleanEntrySerializer implements JsonSerializer<ConfigBooleanEntry>, JsonDeserializer<ConfigBooleanEntry> {
@Override
public ConfigBooleanEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new ConfigBooleanEntry(json.getAsBoolean());
if (json.isJsonPrimitive()) return new ConfigBooleanEntry(json.getAsBoolean());
else if (json.isJsonObject()) {
JsonObject o = json.getAsJsonObject();
if (o.has("default"))
return new ConfigBooleanEntry(o.get("default").getAsBoolean());
else
throw new JsonSyntaxException("Default value for boolean entry must be a boolean");
}
else throw new JsonSyntaxException("Invalid type for boolean entry");
}
@Override

View File

@ -0,0 +1,78 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.data.entry.*;
import io.gitlab.jfronny.respackopts.data.enums.PackReloadType;
import java.lang.reflect.Type;
import java.util.Map;
public class ConfigBranchSerializer implements JsonSerializer<ConfigBranch>, JsonDeserializer<ConfigBranch> {
@Override
public JsonElement serialize(ConfigBranch src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject o = new JsonObject();
for (Map.Entry<String, ConfigEntry<?>> entry : src.getValue().entrySet()) {
o.add(entry.getKey(), context.serialize(entry.getValue()));
}
return o;
}
@Override
public ConfigBranch deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (!json.isJsonObject())
throw new JsonSyntaxException("ConfigBranch is not an object");
JsonObject o = json.getAsJsonObject();
ConfigBranch cbNew = new ConfigBranch();
for (Map.Entry<String, JsonElement> e : o.entrySet()) {
JsonElement j = e.getValue();
String s = e.getKey();
if (j.isJsonPrimitive()) {
JsonPrimitive p = j.getAsJsonPrimitive();
if (p.isBoolean())
cbNew.add(s, new ConfigBooleanEntry(p.getAsBoolean()));
else if (p.isNumber())
cbNew.add(s, context.deserialize(j, ConfigNumericEntry.class));
else if (p.isString())
cbNew.add(s, context.deserialize(j, ConfigEnumEntry.class));
}
else if (j.isJsonArray())
cbNew.add(s, context.deserialize(j, ConfigEnumEntry.class));
else if (j.isJsonObject()) {
JsonObject jo = j.getAsJsonObject();
String type;
if (jo.has("type")) {
type = jo.get("type").getAsString();
}
else {
if (jo.entrySet().stream().allMatch(entry -> "default".equals(entry.getKey()) || "min".equals(entry.getKey()) || "max".equals(entry.getKey()))) {
Respackopts.LOGGER.info("No \"type\" property for non-branch entry object, assuming slider");
type = "slider";
}
else {
cbNew.add(s, context.deserialize(j, ConfigBranch.class));
continue;
}
}
ConfigEntry<?> entry = switch (type.toLowerCase()) {
case "slider", "number", "numeric" -> context.deserialize(j, ConfigNumericEntry.class);
case "boolean", "toggle" -> context.deserialize(j, ConfigBooleanEntry.class);
case "enum", "select" -> context.deserialize(j, ConfigEnumEntry.class);
default -> throw new JsonSyntaxException("Unknown entry type: " + type);
};
if (jo.has("default")) {
entry.setDefault(context.deserialize(jo.get("default"), entry.getValueType()));
entry.setValue(context.deserialize(jo.get("default"), entry.getValueType()));
}
if (jo.has("reloadType")) {
entry.setReloadType(context.deserialize(jo.get("reloadType"), PackReloadType.class));
}
cbNew.add(s, entry);
} else if (j.isJsonNull())
throw new JsonSyntaxException("Unexpected json null");
else
throw new JsonSyntaxException("Unexpected json object type");
}
return cbNew;
}
}

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.respackopts.gson;
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.data.entry.ConfigEnumEntry;
import java.lang.reflect.Type;
@ -38,11 +38,15 @@ public class EnumEntrySerializer implements JsonSerializer<ConfigEnumEntry>, Jso
throw new JsonSyntaxException("Expected string entry in enum");
}
if (result.values.isEmpty())
RpoModInfo.LOGGER.warn("Enum entry empty");
Respackopts.LOGGER.warn("Enum entry empty");
else
result.setDefault(result.values.get(0));
return result;
}
else if (json.isJsonObject()) {
JsonObject o = json.getAsJsonObject();
return deserialize(o.get("values"), typeOfT, context);
}
else
throw new JsonSyntaxException("Expected primitive string key or string array for enum");
}

View File

@ -0,0 +1,52 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.respackopts.data.entry.ConfigNumericEntry;
import io.gitlab.jfronny.respackopts.data.enums.NumericEntryType;
import java.lang.reflect.Type;
public class NumericEntrySerializer implements JsonSerializer<ConfigNumericEntry>, JsonDeserializer<ConfigNumericEntry> {
@Override
public JsonElement serialize(ConfigNumericEntry src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getValue());
}
@Override
public ConfigNumericEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isNumber()) {
ConfigNumericEntry result = new ConfigNumericEntry(NumericEntryType.Box);
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);
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();
}
else throw new JsonSyntaxException("Expected number as min of numeric entry");
}
if (max != null) {
if (max.isJsonPrimitive() && max.getAsJsonPrimitive().isNumber()) {
result.max = max.getAsNumber().doubleValue();
}
else throw new JsonSyntaxException("Expected number as max of numeric entry");
}
if (slider && (!isWhole(result.min) || !isWhole(result.max)))
throw new JsonSyntaxException("Expected whole number in slider definition");
return result;
}
throw new JsonSyntaxException("Could not deserialize numeric entry");
}
public static boolean isWhole(double v) {
return v == Math.floor(v) && !Double.isInfinite(v);
}
}

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.respackopts.integration;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.Respackopts;
import net.fabricmc.loader.api.FabricLoader;
import java.io.IOException;
@ -11,40 +11,32 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class DashLoaderCompat {
public static final boolean modPresent = FabricLoader.getInstance().isModLoaded("dashloader");
private static boolean forceReload = false;
// Called from ASM injected through mixin/AsmPlugin
@SuppressWarnings("unused")
public static void injection() {
if (forceReload) {
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Attempting to force a dashloader reload by removing its data directory");
try {
Class<?> loaderClass = Class.forName("net.oskarstrom.dashloader.DashLoader");
Object loaderInstance = loaderClass.getDeclaredMethod("getInstance").invoke(null);
Path loaderPath = (Path) loaderClass.getDeclaredMethod("getModBoundDir").invoke(loaderInstance);
Files.walkFileTree(loaderPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (Throwable e) {
RpoModInfo.LOGGER.error("Failed to remove dashloader data", e);
}
}
forceReload = false;
}
public static void requestForceReload() {
forceReload = true;
if (!FabricLoader.getInstance().isModLoaded("dashloader"))
return;
if (!Respackopts.CONFIG.dashloaderCompat)
return;
try {
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Removing DashCache to force dashloader to reload");
Class<?> loaderClass = Class.forName("net.oskarstrom.dashloader.DashLoader");
Object loaderInstance = loaderClass.getDeclaredMethod("getInstance").invoke(null);
Path loaderPath = (Path) loaderClass.getDeclaredMethod("getModBoundDir").invoke(loaderInstance);
Files.walkFileTree(loaderPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (Throwable e) {
Respackopts.LOGGER.error("Failed to remove dashloader data, try disabling dashloaderCompat if this keeps happening", e);
}
}
}

View File

@ -1,10 +1,9 @@
package io.gitlab.jfronny.respackopts.integration;
import grondag.frex.FrexInitializer;
import grondag.frex.api.config.ShaderConfig;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBranch;
import io.vram.frex.api.config.ShaderConfig;
import net.minecraft.util.Identifier;
import java.util.Map;
@ -13,8 +12,8 @@ public class FrexCompat implements FrexInitializer {
boolean initial = true;
@Override
public void onInitalizeFrex() {
ShaderConfig.registerShaderConfigSupplier(new Identifier(RpoModInfo.ID, "config_supplier"), FrexCompat::generateShader);
RpoModInfo.LOGGER.info("enabled frex/canvas support");
ShaderConfig.registerShaderConfigSupplier(new Identifier(Respackopts.ID, "config_supplier"), FrexCompat::generateShader);
Respackopts.LOGGER.info("enabled frex/canvas support");
Respackopts.SAVE_ACTIONS.add(() -> {
try {
if (!initial)
@ -22,14 +21,14 @@ public class FrexCompat implements FrexInitializer {
initial = false;
}
catch (Throwable e) {
RpoModInfo.LOGGER.error("Could not reload shader config", e);
Respackopts.LOGGER.error("Could not reload shader config", e);
}
});
}
public static String generateShader() {
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Generating FREX shader code");
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Generating FREX shader code");
StringBuilder sb = new StringBuilder();
sb.append("#define respackopts_loaded");
for (Map.Entry<String, ConfigBranch> e : Respackopts.CONFIG_BRANCH.entrySet()) {

View File

@ -3,8 +3,8 @@ package io.gitlab.jfronny.respackopts.integration;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.ConfigFile;
import io.gitlab.jfronny.respackopts.data.enums.PackReloadType;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
@ -21,38 +21,41 @@ public class ModMenuCompat implements ModMenuApi {
.setParentScreen(parent)
.setTitle(new TranslatableText("respackopts.mainconfig"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
builder.setSavingRunnable(() -> {
RpoModInfo.CONFIG.save();
Respackopts.CONFIG.save();
Respackopts.save();
DashLoaderCompat.requestForceReload();
Respackopts.forceReloadResources();
if (agg.get() == PackReloadType.Resource) {
DashLoaderCompat.requestForceReload();
Respackopts.forceReloadResources();
}
});
//Respackopts config screen
ConfigFile defaultConfig = new ConfigFile();
ConfigCategory mainConfig = builder.getOrCreateCategory(new TranslatableText("respackopts.mainconfig"));
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugCommands"), RpoModInfo.CONFIG.debugCommands)
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugCommands"), Respackopts.CONFIG.debugCommands)
.setDefaultValue(defaultConfig.debugCommands)
.setSaveConsumer(b -> RpoModInfo.CONFIG.debugCommands = b)
.setSaveConsumer(b -> Respackopts.CONFIG.debugCommands = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugLogs"), RpoModInfo.CONFIG.debugLogs)
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugLogs"), Respackopts.CONFIG.debugLogs)
.setDefaultValue(defaultConfig.debugLogs)
.setSaveConsumer(b -> RpoModInfo.CONFIG.debugLogs = b)
.setSaveConsumer(b -> Respackopts.CONFIG.debugLogs = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.dashloaderCompat"), RpoModInfo.CONFIG.dashloaderCompat)
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.dashloaderCompat"), Respackopts.CONFIG.dashloaderCompat)
.requireRestart()
.setDefaultValue(defaultConfig.dashloaderCompat)
.setSaveConsumer(b -> RpoModInfo.CONFIG.dashloaderCompat = b)
.setSaveConsumer(b -> Respackopts.CONFIG.dashloaderCompat = b)
.build()
);
//Pack config screens
Respackopts.CONFIG_BRANCH.forEach((id, conf) -> {
ConfigCategory config = builder.getOrCreateCategory(new TranslatableText((Respackopts.PACK_METAS.get(id).version >= 5 ? "rpo." : "respackopts.title.") + id));
Respackopts.factory.buildCategory(conf, id, config::addEntry, entryBuilder, "");
Respackopts.GUI_FACTORY.buildCategory(conf, id, config::addEntry, agg, entryBuilder, "");
});
return builder.build();
}

View File

@ -1,112 +0,0 @@
package io.gitlab.jfronny.respackopts.mixin;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.integration.DashLoaderCompat;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy;
import org.spongepowered.asm.transformers.MixinClassWriter;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Set;
public class AsmPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {
if (RpoModInfo.CONFIG.dashloaderCompat && DashLoaderCompat.modPresent) {
RpoModInfo.LOGGER.info("Injecting RPO into the mixin pipeline in order to facilitate dashloader integration. If respackopts is mentioned, try disabling this via the config before reporting (\"debugLogs\")");
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> classLoaderClass = classLoader.getClass();
Field delegateField = classLoaderClass.getDeclaredField("delegate");
delegateField.setAccessible(true);
Object delegate = delegateField.get(classLoader);
Class<?> delegateClass = delegate.getClass();
Field mixinTransformerField = delegateClass.getDeclaredField("mixinTransformer");
mixinTransformerField.setAccessible(true);
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Transformer mixinTransformer = (Transformer) unsafe.allocateInstance(Transformer.class);
mixinTransformer.delegate = (FabricMixinTransformerProxy) mixinTransformerField.get(delegate);
mixinTransformerField.set(delegate, mixinTransformer);
} catch (NoSuchFieldException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
private static class Transformer extends FabricMixinTransformerProxy {
private FabricMixinTransformerProxy delegate;
@Override
public byte[] transformClassBytes(String name, String transformedName, byte[] basicClass) {
basicClass = delegate.transformClassBytes(name, transformedName, basicClass);
if (name.equals("net.oskarstrom.dashloader.DashLoader")) {
ClassNode klass = new ClassNode();
ClassReader reader = new ClassReader(basicClass);
reader.accept(klass, ClassReader.EXPAND_FRAMES);
boolean found = false;
for (MethodNode method : klass.methods) {
if (method.name.equals("reload")) {
method.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(DashLoaderCompat.class), "injection", "()V"));
found = true;
}
}
if (!found) RpoModInfo.LOGGER.error("Could not hack into dashloader");
ClassWriter writer = new MixinClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
klass.accept(writer);
basicClass = writer.toByteArray();
}
return basicClass;
}
}
}

View File

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

View File

@ -45,7 +45,7 @@ public abstract class ResourcePackEntryMixin {
k = Respackopts.DISPLAY_NAME_LOOKUP.get(k);
info.setReturnValue(true);
MinecraftClient c = MinecraftClient.getInstance();
c.setScreen(Respackopts.factory.buildGui(Respackopts.CONFIG_BRANCH.get(k), k, c.currentScreen));
c.setScreen(Respackopts.GUI_FACTORY.buildGui(Respackopts.CONFIG_BRANCH.get(k), k, c.currentScreen));
}
}
}

View File

@ -1,11 +1,10 @@
package io.gitlab.jfronny.respackopts.mixin;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.data.enums.PackCapability;
import io.gitlab.jfronny.respackopts.util.MetadataLocateResult;
import io.gitlab.jfronny.respackopts.data.PackCapability;
import io.gitlab.jfronny.respackopts.data.PackMeta;
import io.gitlab.jfronny.respackopts.data.entry.SyncMode;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.resource.ResourcePackProfile;
@ -31,15 +30,15 @@ public class ResourcePackManagerMixin {
MetadataLocateResult scan = rpo$locateMetadata(v);
if (scan.hasMeta()) {
try (InputStream is = scan.pack().open(scan.type(), Respackopts.CONF_ID); InputStreamReader isr = new InputStreamReader(is)) {
PackMeta conf = RpoModInfo.GSON.fromJson(isr, PackMeta.class);
if (RpoModInfo.CONFIG.debugLogs)
RpoModInfo.LOGGER.info("Discovered pack: " + conf.id);
if (RpoModInfo.META_VERSION < conf.version) {
RpoModInfo.LOGGER.error(s + " was not loaded as it specifies a newer respackopts version than is installed");
PackMeta conf = Respackopts.GSON.fromJson(isr, PackMeta.class);
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Discovered pack: " + conf.id);
if (Respackopts.META_VERSION < conf.version) {
Respackopts.LOGGER.error(s + " was not loaded as it specifies a newer respackopts version than is installed");
return;
}
if (RpoModInfo.META_VERSION > conf.version) {
RpoModInfo.LOGGER.warn(s + " uses an outdated RPO format (" + conf.version + "). Although this is supported, using the latest version (" + RpoModInfo.META_VERSION + ") is recommended");
if (Respackopts.META_VERSION > conf.version) {
Respackopts.LOGGER.warn(s + " uses an outdated RPO format (" + conf.version + "). Although this is supported, using the latest version (" + Respackopts.META_VERSION + ") is recommended");
}
conf.conf.setVersion(conf.version);
if (conf.version < 5)
@ -47,13 +46,13 @@ public class ResourcePackManagerMixin {
if (!Respackopts.CONFIG_BRANCH.containsKey(conf.id))
Respackopts.CONFIG_BRANCH.put(conf.id, conf.conf);
else
Respackopts.CONFIG_BRANCH.get(conf.id).sync(conf.conf, SyncMode.RESPACK_LOAD);
Respackopts.CONFIG_BRANCH.get(conf.id).sync(conf.conf, ConfigSyncMode.RESPACK_LOAD);
Respackopts.DISPLAY_NAME_LOOKUP.put(v.getDisplayName().asString(), conf.id);
Respackopts.PACK_NAME_LOOKUP.put(v.createResourcePack().getName(), conf.id);
Respackopts.PACK_METAS.put(conf.id, conf);
Respackopts.load(conf.id);
} catch (Throwable e) {
RpoModInfo.LOGGER.error("Could not initialize pack meta for " + s, e);
Respackopts.LOGGER.error("Could not initialize pack meta for " + s, e);
}
}
});

View File

@ -1,9 +1,11 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.integration.DashLoaderCompat;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBranch;
import io.gitlab.jfronny.respackopts.data.entry.ConfigEntry;
import io.gitlab.jfronny.respackopts.data.entry.GuiEntryBuilderParam;
import io.gitlab.jfronny.respackopts.data.enums.PackReloadType;
import io.gitlab.jfronny.respackopts.integration.DashLoaderCompat;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
@ -20,26 +22,21 @@ import java.util.Optional;
import java.util.function.Consumer;
public class GuiFactory {
public void buildCategory(ConfigBranch source, String screenId, Consumer<AbstractConfigListEntry<?>> config, ConfigEntryBuilder entryBuilder, String namePrefix) {
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(entryBuilder,
getText(entryName, translationPrefix),
() -> {
config.accept(entry.buildEntry(
new GuiEntryBuilderParam(entryBuilder, getText(entryName, translationPrefix), () -> {
String k = (source.getVersion() < 3 ? "respackopts.tooltip." : "rpo.tooltip.") + screenId + "." + entryName;
if (Language.getInstance().hasTranslation(k)) {
Text[] res = new Text[1];
res[0] = new TranslatableText(k);
return Optional.of(res);
}
else
} else
return Optional.empty();
},
screenId,
entryName,
translationPrefix));
}, screenId, entryName, translationPrefix, () -> reloadTypeAggregator.accept(in.getValue().getReloadType()), reloadTypeAggregator)));
}
}
@ -57,14 +54,17 @@ public class GuiFactory {
.setParentScreen(parent)
.setTitle(getText(packId, source.getVersion() < 4 ? "respackopts.title" : "rpo"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
builder.setSavingRunnable(() -> {
Respackopts.save();
Respackopts.forcePackReload = true;
DashLoaderCompat.requestForceReload();
Respackopts.reloadData();
if (agg.get() == PackReloadType.Resource) {
Respackopts.forcePackReload = true;
DashLoaderCompat.requestForceReload();
Respackopts.reloadData();
}
});
ConfigCategory config = builder.getOrCreateCategory(getText(packId, source.getVersion() < 4 ? "respackopts.title" : "rpo"));
buildCategory(source, packId, config::addEntry, entryBuilder, "");
buildCategory(source, packId, config::addEntry, agg, entryBuilder, "");
return builder.build();
}
catch (Throwable t) {

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.RpoModInfo;
import io.gitlab.jfronny.respackopts.data.entry.ConfigBranch;
import io.gitlab.jfronny.respackopts.integration.FrexCompat;
import net.fabricmc.loader.api.FabricLoader;
@ -18,7 +17,7 @@ import java.util.Map;
import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.*;
public class RpoCommand {
private static final ModContainer respackotps = FabricLoader.getInstance().getModContainer(RpoModInfo.ID).get();
private static final ModContainer respackotps = FabricLoader.getInstance().getModContainer(Respackopts.ID).get();
public static void register() {
DISPATCHER.register(literal("rpo").then(literal("dump").then(literal("frex").executes(ctx -> {
ctx.getSource().sendFeedback(dump(FrexCompat.generateShader(), "frex.glsl"));
@ -37,11 +36,11 @@ public class RpoCommand {
return 1;
})));
DISPATCHER.register(literal("rpo").then(literal("version").executes(ctx -> {
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackotps.getMetadata().getVersion(), RpoModInfo.META_VERSION));
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackotps.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
})));
DISPATCHER.register(literal("rpo").executes(ctx -> {
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackotps.getMetadata().getVersion(), RpoModInfo.META_VERSION));
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackotps.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
}));
}

View File

@ -29,6 +29,7 @@
"fabricloader": ">=0.10.8",
"fabric": "*",
"minecraft": "*",
"libjf": ">=1.2.0"
"libjf-data-manipulation-v0": ">=2.0",
"libjf-base": ">=2.0"
}
}

View File

@ -2,7 +2,6 @@
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.respackopts.mixin",
"plugin": "io.gitlab.jfronny.respackopts.mixin.AsmPlugin",
"compatibilityLevel": "JAVA_8",
"mixins": [
],

View File

@ -0,0 +1,82 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.respackopts.data.ConfigFile;
import io.gitlab.jfronny.respackopts.data.condition.Condition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.nio.file.Paths;
import java.util.Set;
import static io.gitlab.jfronny.respackopts.Respackopts.SAVE_ACTIONS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
class ConditionSerializationTest {
@BeforeAll
static void initialize() {
Respackopts.LOGGER.info("Expected error end");
Respackopts.CONF_DIR = Paths.get("./test");
Respackopts.CONFIG = new ConfigFile();
SAVE_ACTIONS.add(() -> Respackopts.LOGGER.info("Save"));
Respackopts.LOGGER.info(Respackopts.CONF_DIR);
}
@Test
void gsonSetOrder() {
String[] expected = new String[] {"one", "two", "three", "zero", "76"};
String gson = "[\"one\", \"two\", \"three\", \"zero\", \"76\"]";
Set<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<Set<String>>(){}.getType());
int i = 0;
for (String s : parsed) {
assertEquals(s, expected[i++]);
}
}
@Test
void gsonSingleEntrySet() {
String gson = "\"someText\"";
Set<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<Set<String>>(){}.getType());
assertEquals(parsed.size(), 1);
for (String s : parsed) {
assertEquals(s, "someText");
}
}
@Test
void gsonConditionSimple() {
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("true", Condition.class).evaluate("test")));
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"not\": false}", Condition.class).evaluate("test")));
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("[true, {\"not\": false}]", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("[true, {\"not\": true}]", Condition.class).evaluate("test")));
}
@Test
void gsonConditionXor() {
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"xor\": [true, false]}", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"xor\": [true, true]}", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"xor\": [false, false]}", Condition.class).evaluate("test")));
}
@Test
void gsonConditionOr() {
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"or\": [true, false]}", Condition.class).evaluate("test")));
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"or\": [true, true]}", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"or\": [false, false]}", Condition.class).evaluate("test")));
}
@Test
void gsonConditionNor() {
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"nor\": [true, false]}", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"nor\": [true, true]}", Condition.class).evaluate("test")));
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"nor\": [false, false]}", Condition.class).evaluate("test")));
}
@Test
void gsonConditionAnd() {
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"and\": [true, false]}", Condition.class).evaluate("test")));
assertTrue(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"and\": [true, true]}", Condition.class).evaluate("test")));
assertFalse(assertDoesNotThrow(() -> Respackopts.GSON.fromJson("{\"and\": [false, false]}", Condition.class).evaluate("test")));
}
}

View File

@ -1,8 +1,9 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.respackopts.data.ConfigFile;
import io.gitlab.jfronny.respackopts.data.entry.*;
import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.data.enums.NumericEntryType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@ -10,14 +11,12 @@ import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Set;
import static io.gitlab.jfronny.respackopts.Respackopts.*;
import static io.gitlab.jfronny.respackopts.RpoModInfo.*;
import static io.gitlab.jfronny.respackopts.Respackopts.CONFIG_BRANCH;
import static org.junit.jupiter.api.Assertions.*;
class RespackoptsTest {
class ConfigTreeTest {
private static final String testEntryName = "test";
private static final String testEntry1Name = "test1";
@ -65,7 +64,7 @@ class RespackoptsTest {
void syncSimple() {
ConfigBranch test = new ConfigBranch();
test.add(testEntry1Name, new ConfigBooleanEntry(false));
CONFIG_BRANCH.get(testEntryName).sync(test, SyncMode.RESPACK_LOAD);
CONFIG_BRANCH.get(testEntryName).sync(test, ConfigSyncMode.RESPACK_LOAD);
save();
load(testEntryName);
assertFalse((Boolean) CONFIG_BRANCH.get(testEntryName).get(testEntry1Name).getValue());
@ -78,9 +77,9 @@ class RespackoptsTest {
be.setValue(true);
assertFalse((Boolean) CONFIG_BRANCH.get(testEntryName).get(testEntry1Name).getValue());
LOGGER.info("E");
CONFIG_BRANCH.get(testEntryName).sync(test, SyncMode.RESPACK_LOAD);
CONFIG_BRANCH.get(testEntryName).sync(test, ConfigSyncMode.RESPACK_LOAD);
assertFalse((Boolean) CONFIG_BRANCH.get(testEntryName).get(testEntry1Name).getValue());
CONFIG_BRANCH.get(testEntryName).sync(test, SyncMode.CONF_LOAD);
CONFIG_BRANCH.get(testEntryName).sync(test, ConfigSyncMode.CONF_LOAD);
save();
load(testEntryName);
assertTrue((Boolean) CONFIG_BRANCH.get(testEntryName).get(testEntry1Name).getValue());
@ -91,32 +90,11 @@ class RespackoptsTest {
ConfigBranch cb = new ConfigBranch();
ConfigBranch cbNew = new ConfigBranch();
cb.add(testEntryName, new ConfigBooleanEntry(false));
cbNew.add(testEntryName, new ConfigNumericEntry());
cbNew.add(testEntryName, new ConfigNumericEntry(NumericEntryType.Slider));
LOGGER.info("Expecting warning message");
cbNew.sync(cb, SyncMode.RESPACK_LOAD);
cbNew.sync(cb, ConfigSyncMode.RESPACK_LOAD);
LOGGER.info("Expected warning end");
cbNew.add(testEntryName, new ConfigBooleanEntry(true));
cbNew.sync(cb, SyncMode.RESPACK_LOAD);
}
@Test
void gsonSetOrder() {
String[] expected = new String[] {"one", "two", "three", "zero", "76"};
String gson = "[\"one\", \"two\", \"three\", \"zero\", \"76\"]";
Set<String> parsed = GSON.fromJson(gson, new TypeToken<Set<String>>(){}.getType());
int i = 0;
for (String s : parsed) {
assertEquals(s, expected[i++]);
}
}
@Test
void gsonSingleEntrySet() {
String gson = "\"someText\"";
Set<String> parsed = GSON.fromJson(gson, new TypeToken<Set<String>>(){}.getType());
assertEquals(parsed.size(), 1);
for (String s : parsed) {
assertEquals(s, "someText");
}
cbNew.sync(cb, ConfigSyncMode.RESPACK_LOAD);
}
}