Introduce μScript and replace the condition and resource expansion systems with it (backwards compatibility is provided)

This commit is contained in:
Johannes Frohnmeyer 2022-06-04 19:43:49 +02:00
parent 306279e92e
commit 23e81eccf3
Signed by: Johannes
GPG Key ID: E76429612C2929F4
63 changed files with 664 additions and 986 deletions

View File

@ -18,9 +18,9 @@ repositories {
dependencies {
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_version}")
modImplementation("com.terraformersmc:modmenu:3.0.1")
include modImplementation("com.github.MeteorDevelopment:starscript:0.1.9")
modApi("me.shedaniel.cloth:cloth-config-fabric:6.2.57") {
modImplementation("com.terraformersmc:modmenu:4.0.0-beta.4")
include modImplementation("io.gitlab.jfronny:muscript:${project.muscript_version}")
modApi("me.shedaniel.cloth:cloth-config-fabric:7.0.65") {
exclude(group: "net.fabricmc.fabric-api")
}
modCompileOnly "io.vram:frex-fabric-mc118:+"
@ -29,13 +29,13 @@ dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.8.2')
//Canvas for FREX testing
// Canvas for FREX testing
// modRuntimeOnly("io.vram:canvas-fabric-mc118:+") {
// exclude(group: "me.shedaniel.cloth")
// }
//DashLoader "compatibility"
modRuntimeOnly 'dev.quantumfusion.dashloader:dashloader-definitions:3.0-rc14-1.18'
// DashLoader "compatibility"
// modRuntimeOnly 'dev.quantumfusion.dashloader:dashloader-definitions:3.0-rc14-1.18'
}
test {

View File

@ -1,87 +1,28 @@
# Advanced Conditions
Respackopts uses "conditions" for deciding what files to load.
Respackopts conditions are usually a simple string with a pack and entry name
(IE `<pack id>:<entry name>` or `<entry name>`),
Respackopts conditions are usually a simple string with the entry name
(for example, `someTexture`),
however, respackopts provides various additional features to enhance their functionality.
This page will go over everything you can do with conditions
This functionality is powered by the [μScript language](https://gitlab.com/JFronny/java-commons/-/tree/master/muscript)
This page will provide a quick overview over the available operations.
For more complete documentation, use the above link.
## (Boolean) logic
Any JSON object will be treated as a logical condition.
This behavior allows defining resources that are only used when a set of conditions are met,
not just one. Be aware that nesting logic operations is supported and recommended for advanced setups
The following expressions are supported:
You can use logic operations on several values to only show resources when a combination of them applies
Some common logical operations are supported, others indirectly.
That includes NOT (`!`), AND (`&`), OR (`|`), XOR (`==`) and XNOR (`!=`)
| Example | Explaination |
|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| `{"not": "examplePack:condition1"}` | True if the contained condition is false |
| `{"and": ["examplePack:condition1", "examplePack:condition2"]}` | True if all contained conditions are true |
| `{"or": ["examplePack:condition1", "examplePack:condition2"]}` | True if any contained condition is true |
| `{"nor": ["examplePack:condition1", "examplePack:condition2"]}` | True if none of the contained conditions are true |
| `{"xor": ["examplePack:condition1", "examplePack:condition2"]}` | True if the number of contained conditions which are true is an odd number |
| `{"eq": ["examplePack:condition1", "examplePack:condition2"]}` | True if all contained conditions have the same value <br>(equivalent to `{"or": [{"and": [...]}, {"nor": [...]}]}`) |
## Numbers
Numeric values can be accessed by their name, just like boolean values. All typical mathematical operations are supported,
that includes addition (`+`), subtraction (`-`), multiplication (`*`), division (`/`) and modulo (`%`)
## Built-in conditions
Respackopts provides some conditions by default, you can use them in any pack.
| Condition | Explaination |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `modversion:<mod>:<predicate>` | True if the specified mod is loaded (use the mod ID) and the version fits the predicate.<br>Example: `modversion:minecraft:>=1.7.10`, `modversion:continuity:*` |
## Accessing values
By default, all values for the current pack will be available by their name or as entries of a category (`someCategory.someOption`).
In addition, all values of other packs are accessible under their pack id (`somePack.someOption`).
Respackopts also allows checking mod (or minecraft) versions against predicates as follows:
`version('<mod name>', '<predicate>')`, for example `version('minecraft', '<=1.16.5')`
## Fabric Resource Conditions Interop
Fabric API provides a system called resource conditions, which is similar to respackopts own system.
To improve compatibility, respackopts allows accessing fabric resource conditions and respackopts conditions from one another.
### Fabric Resource Conditions from Respackopts
A json object containing a singular entry whose key is `fabric:load_conditions` will be treated as a Fabric Resource Condition.
Example:
```json
{
"condition": {
"fabric:load_conditions": {
"condition": "fabric:not",
"value": {
"condition": "fabric:all_mods_loaded",
"values": [
"a"
]
}
}
}
}
```
### Respackopts conditions from Fabric
You may access respackopts conditions from Fabric API Resource Condition blocks by specifying a condition with the ID
`config`. Its value will be treated as a respackopts condition.
Please be aware that fabric resource conditions are not tied to resource packs and entry IDs must therefore be specified fully.
(Using `someTexture` instead of `examplePack:someTexture` is impossible)
Example:
```json
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{
"item": "minecraft:stick"
}
],
"result": {
"item": "minecraft:diamond"
},
"fabric:conditions": [
{
"condition": "fabric:not",
"value": {
"condition": "respackopts:config",
"value": {
"and": [
"examplePack:someTexture"
]
}
}
}
]
}
```
Respackopts previously allowed accessing fabric resource conditions and respackopts conditions from one another,
however, this feature was removed in favor of enforcing the new muscript syntax.

View File

@ -23,12 +23,14 @@ One common issue is that you removed an option but still use it somewhere.
The log will usually reference that option and the source.
## Ensure you are using the correct dots
Ensure conditions follow one of the following conventions.
Using dots, colons or commas where they don't belong will break your pack:
- `<pack id>:<entry id>`, eg `breeze16:bushyLeaves.full`
- `<entry id>`, eg `bushyLeaves.full`
Respackopts only supports normal dots. If you write commas or colons by accident,
your pack WILL fail to load.
## Avoid infinite loops
Ensure that you do not reference an original file or a previous fallback from a fallback.
Respackopts WILL crash if it runs into an infinite loop!
Respackopts WILL crash if it runs into an infinite loop!
## Contact me for support
If you are unable to identify the issue, you can try [contacting](https://jfronny.gitlab.io/contact.html) me.
As respackopts is a complex mod, there is a non-zero chance that your issue may be caused by a bug in the mod.
Even if the issue is in your code, I can still try helping you fix it.

View File

@ -15,7 +15,7 @@ You can add [translations](./Translations.md) to work around this in the UI show
```json
{
"id": "<PackID>",
"version": 6,
"version": 8,
"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": 6,
"version": 8,
"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": 6,
"version": 8,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": 10
@ -79,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": 6,
"version": 8,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someOption": [
@ -99,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": 6,
"version": 8,
"capabilities": ["FileFilter", "DirFilter"],
"conf": {
"someCategory": {

View File

@ -57,4 +57,11 @@ Corresponds to version 2.7.2 - 2.9.1
## v7
Corresponds to version 2.10.0
- Replace StarScript representation of enums
- Replace StarScript representation of enums
## v8
Corresponds to version 3.0.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

View File

@ -1,12 +1,11 @@
# Select one of multiple files
This Allows you to pick between multiple files (3 or more files) using similar logic to [Toggle Files](./ToggleFiles.md)
But this requires multiple `.rpo` files to achieve the desired goal.
You will need the `PackID` and `EntryName` from your `/assets/respackopts/conf.json` that you created earlier
if you have not, see [Main Config](./MainConfig.md) on how to do so.
<br>
You will need to navigate to the file you would like to toggle inside your resource, and create a `.rpo` file for it in the same folder(directory).
You will need to navigate to the file you would like to toggle inside your resource, and create a `.rpo` file for it in the same folder (directory).
<br>
### Examples:
@ -14,13 +13,14 @@ You will need to navigate to the file you would like to toggle inside your resou
`some_recipe.json` would be `some_recipe.json.rpo`<br>
### `.rpo` explanation :
You need a `.rpo` file per texture (except for the last one in your list) if you have 3 files to select beteen then you will need 2 `.rpo` files, If you have 4 files to select between then you will need 3 `.rpo` files and so on etc...
You need a `.rpo` file per texture (except for the last one in your list) if you have 3 files to select beteen then you will need 2 `.rpo` files,
if you have 4 files to select between then you will need 3 `.rpo` files and so on etc...
### Layout:
This is your first `.rpo` with your first file:
```json
{
"condition": "<your pack id>:<your entry>",
"condition": "<your entry>",
"fallback": "path/to/second.json/.png"
}
```
@ -28,7 +28,7 @@ This is your first `.rpo` with your first file:
This is your second `.rpo` with your second file:
```json
{
"condition": "<your pack id>:<your entry>",
"condition": "<your entry>",
"fallback": "path/to/thirdfile.json/.png"
}
```
@ -36,14 +36,14 @@ This is your second `.rpo` with your second file:
### Example:
```json
{
"condition": "examplePack:option_one",
"condition": "option_one",
"fallback": "path/to/secondfile.json"
}
```
```json
{
"condition": "examplePack:option_two",
"condition": "option_two",
"fallback": "path/to/thirdfile.json"
}
```

View File

@ -2,10 +2,13 @@
Sometimes, you may wish to use respackopts values inside your text files (for example as model transforms),
without switching between a bunch of similar files. For this purpose, respackopts allows replacing text in files via resource expansion.
You can use the `expansions` block in your .rpo to replace content with a value computed through executing a custom [StarScript](https://github.com/MeteorDevelopment/starscript/wiki).
Values can be accessed in star script in a similar way to normal condition objects: `<pack id>.<entry>` or `<pack id>.<subcategory>.<entry>`.
You can use the `expansions` block in your .rpo to replace content with a value computed through executing a custom [μScript](https://gitlab.com/JFronny/java-commons/-/tree/master/muscript) block.
Values can be accessed in the script in a the same exact way as in condition objects,
as the same language and object representation is used, though the result of a script is a string and not a boolean here.
The string value of an enum can be accessed via `<pack id>.<entry>.value`
I should probably point out here that, since the result here is always a string,
string concatenation via the `||` operator is possible and recommended.
For example, the following is a valid script: `'Text ' || (someNumber * 15) || someBoolean`
### Example:
conf.json:
@ -27,12 +30,12 @@ oak_fence.json.rpo:
```json
{
"expansions": {
"uvlock": "{examplePack.oakFence.uvLock}",
"orientation": "{!examplePack.oakFence.invertOrientation}",
"y000": "{examplePack.oakFence.yFactor + 0}",
"y090": "{examplePack.oakFence.yFactor + 90}",
"y180": "{examplePack.oakFence.yFactor + 180}",
"y270": "{examplePack.oakFence.yFactor + 270}"
"uvlock": "examplePack.oakFence.uvLock",
"orientation": "!examplePack.oakFence.invertOrientation",
"y000": "examplePack.oakFence.yFactor + 0",
"y090": "examplePack.oakFence.yFactor + 90",
"y180": "examplePack.oakFence.yFactor + 180",
"y270": "examplePack.oakFence.yFactor + 270"
}
}
```

View File

@ -1,5 +1,5 @@
# Toggle Files
This is a simple `IF` statement simply returning true/false to if the texture/file should be loaded into the pack.
A condition can be looked at like an `IF` statement simply returning true/false to configure whether the texture/file should be loaded into the pack.
(Turns the texture "on" or "off").
You will need the `Pack ID` and `Entry Name` from your `/assets/respackopts/conf.json` that you created earlier
if you have not, see [Main Config](./MainConfig.md) on how to do so.
@ -16,13 +16,13 @@ You will need to navigate to the file you would like to toggle inside your resou
### Layout:
```json
{
"condition": "<pack id>:<entry name>"
"condition": "<entry name>"
}
```
### Example:
```json
{
"condition": "examplePack:someTexture"
"condition": "someTexture"
}
```

View File

@ -16,14 +16,14 @@ You will need to navigate to the file you would like to toggle inside your resou
### Layout:
```json
{
"condition": "<pack id>:<entry name>",
"condition": "<entry name>",
"fallback": "location/of/the/file"
}
```
### Example:
```json
{
"condition": "examplePack:someTexture",
"condition": "someTexture",
"fallback": "assets/minecraft/textures/example/alternate.png"
}
```
@ -33,7 +33,7 @@ Respackopts supports specifying multiple possible fallbacks when configuring sin
You can use this functionality as follows:
```json
{
"condition": "<pack id>:<entry name>",
"condition": "<entry name>",
"fallbacks": [
"location/of/the/file",
"assets/minecraft/textures/example/alternate.png"
@ -43,7 +43,7 @@ You can use this functionality as follows:
```json
{
"condition": "examplePack:someTexture"
"condition": "someTexture"
}
```

View File

@ -1,11 +1,12 @@
# https://fabricmc.net/develop/
minecraft_version=1.18.2
yarn_mappings=build.3
loader_version=0.14.5
minecraft_version=1.19-rc2
yarn_mappings=build.1
loader_version=0.14.6
maven_group=io.gitlab.jfronny
archives_base_name=respackopts
fabric_version=0.51.1+1.18.2
jfapi_version=2.8.1
fabric_version=0.55.0+1.19
jfapi_version=2.8.1-1654289121
muscript_version=2022.6.4+17-30-50
modrinth_id=TiF5QWZY
modrinth_required_dependencies=P7dR8mSH, 9s6osm5g, WKwQAwke

View File

@ -1,10 +1,10 @@
{
"expansions": {
"uvlock": "{lumi.oakFence.uvLock}",
"orientation": "{!lumi.oakFence.invertOrientation}",
"y000": "{lumi.oakFence.yFactor + 0}",
"y090": "{lumi.oakFence.yFactor + 90}",
"y180": "{lumi.oakFence.yFactor + 180}",
"y270": "{lumi.oakFence.yFactor + 270}"
"uvlock": "lumi.oakFence.uvLock",
"orientation": "!lumi.oakFence.invertOrientation",
"y000": "lumi.oakFence.yFactor + 0",
"y090": "lumi.oakFence.yFactor + 90",
"y180": "lumi.oakFence.yFactor + 180",
"y270": "lumi.oakFence.yFactor + 270"
}
}

View File

@ -0,0 +1,41 @@
{
"multipart": [
{
"apply": {
"model": "minecraft:block/oak_fence_post"
}
},
{
"when": {
"north": "true"
},
"apply": {
"model": "minecraft:block/oak_fence_side"
}
},
{
"when": {
"east": "true"
},
"apply": {
"model": "minecraft:block/oak_fence_side"
}
},
{
"when": {
"south": "true"
},
"apply": {
"model": "minecraft:block/oak_fence_side"
}
},
{
"when": {
"west": "true"
},
"apply": {
"model": "minecraft:block/oak_fence_side"
}
}
]
}

View File

@ -0,0 +1,3 @@
{
"condition": "version('minecraft', '<=1.16.5')"
}

View File

@ -1,4 +1,4 @@
{
"conditions": ["false"],
"conditions": "false",
"fallback": "assets/minecraft/langer"
}

View File

@ -1,27 +1,11 @@
{
"conditions": [
{
"fabric:load_conditions": {
"condition": "respackopts:config",
"value": "lumi:subcategoryTest.enableLang"
}
},
{
"not": "subcategoryTest.enableLangForceDisable"
},
{
"fabric:load_conditions": {
"condition": "fabric:all_mods_loaded",
"values": ["respackopts"]
}
}
],
"conditions": "lumi.subcategoryTest.enableLang !subcategoryTest.enableLangForceDisable & version('respackopts', '*')",
"fallback": "assets/minecraft/lang/en_us_joke.json",
"expansions": {
"Lights": "{lumi.subcategoryTest.enableLang}",
"Mode": "{lumi.debugMode}",
"Normal": "{lumi.numTest * lumi.subcategoryTest.numberInSub}",
"Lumi": "model",
"Regeneration": "Industrie"
"Lights": "lumi.subcategoryTest.enableLang",
"Mode": "lumi.debugMode",
"Normal": "lumi.numTest * lumi.subcategoryTest.numberInSub",
"Lumi": "'model'",
"Regeneration": "'Industrie'"
}
}

View File

@ -1,5 +1,3 @@
{
"conditions": [
"subcategoryTest.enableLangJokeFallback"
]
"conditions": "subcategoryTest.enableLangJokeFallback"
}

View File

@ -1,6 +1,6 @@
{
"id": "lumi",
"version": 6,
"version": 8,
"capabilities": ["FileFilter", "DirFilter", "DirFilterAdditive"],
"conf": {
"tonemap": [

View File

@ -1,25 +1,16 @@
package io.gitlab.jfronny.respackopts.util;
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch;
import io.gitlab.jfronny.respackopts.model.tree.ConfigEntry;
import io.gitlab.jfronny.respackopts.model.tree.GuiEntryBuilderParam;
import io.gitlab.jfronny.respackopts.model.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;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.gui.screen.FatalErrorScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Language;
import io.gitlab.jfronny.respackopts.integration.*;
import io.gitlab.jfronny.respackopts.model.enums.*;
import io.gitlab.jfronny.respackopts.model.tree.*;
import io.gitlab.jfronny.respackopts.util.*;
import me.shedaniel.clothconfig2.api.*;
import net.minecraft.client.gui.screen.*;
import net.minecraft.text.*;
import net.minecraft.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.*;
import java.util.function.*;
public class GuiFactory {
public void buildCategory(ConfigBranch source, String screenId, Consumer<AbstractConfigListEntry<?>> config, Consumer<PackReloadType> reloadTypeAggregator, ConfigEntryBuilder entryBuilder, String namePrefix) {
@ -32,7 +23,7 @@ public class GuiFactory {
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);
res[0] = Text.translatable(k);
return Optional.of(res);
} else
return Optional.empty();
@ -43,8 +34,8 @@ public class GuiFactory {
public static Text getText(String name, String translationPrefix) {
String translatableNameKey = translationPrefix + "." + name;
return Language.getInstance().hasTranslation(translatableNameKey)
? new TranslatableText(translatableNameKey)
: new LiteralText(name);
? Text.translatable(translatableNameKey)
: Text.literal(name);
}
public Screen buildGui(ConfigBranch source, String packId, Screen parent) {
@ -70,7 +61,7 @@ public class GuiFactory {
}
catch (Throwable t) {
t.printStackTrace();
return new FatalErrorScreen(new TranslatableText("respackopts.loadFailed"), new TranslatableText("respackopts.loadError"));
return new FatalErrorScreen(Text.translatable("respackopts.loadFailed"), Text.translatable("respackopts.loadError"));
}
}
}

View File

@ -1,61 +1,37 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import io.gitlab.jfronny.respackopts.filters.DirFilterEventImpl;
import io.gitlab.jfronny.respackopts.filters.FileFilterEventImpl;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.respackopts.filters.*;
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.integration.FrexCompat;
import io.gitlab.jfronny.respackopts.model.ConfigFile;
import io.gitlab.jfronny.respackopts.model.condition.Condition;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBooleanEntry;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch;
import io.gitlab.jfronny.respackopts.model.tree.ConfigEnumEntry;
import io.gitlab.jfronny.respackopts.model.tree.ConfigNumericEntry;
import io.gitlab.jfronny.respackopts.util.GuiFactory;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import io.gitlab.jfronny.respackopts.util.RpoCommand;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
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.fabric.api.resource.conditions.v1.ResourceConditions;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.impl.gui.FabricGuiEntry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.gitlab.jfronny.respackopts.gson.entry.*;
import io.gitlab.jfronny.respackopts.integration.*;
import io.gitlab.jfronny.respackopts.model.*;
import io.gitlab.jfronny.respackopts.model.tree.*;
import io.gitlab.jfronny.respackopts.util.*;
import net.fabricmc.api.*;
import net.fabricmc.loader.api.*;
import net.minecraft.client.*;
import net.minecraft.server.integrated.*;
import net.minecraft.util.*;
import org.slf4j.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
@Environment(EnvType.CLIENT)
public class Respackopts implements ClientModInitializer {
public static final Integer META_VERSION = 7;
public static final Integer META_VERSION = 8;
public static final String FILE_EXTENSION = ".rpo";
public static final Gson GSON;
public static Starscript STAR_SCRIPT;
public static final String ID = "respackopts";
public static final Logger LOGGER = LoggerFactory.getLogger(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 final Identifier FAPI_CONDITION = new Identifier(ID, "config");
public static boolean forcePackReload = false;
public static Path FALLBACK_CONF_DIR;
@ -69,10 +45,10 @@ public class Respackopts implements ClientModInitializer {
.registerTypeAdapter(ConfigNumericEntry.class, new NumericEntrySerializer())
.registerTypeAdapter(ConfigBooleanEntry.class, new BooleanEntrySerializer())
.registerTypeAdapter(ConfigBranch.class, new ConfigBranchSerializer())
.registerTypeAdapter(Script.class, new ScriptDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.registerTypeAdapterFactory(new SingleEntrySetTypeAdapterFactory())
.registerTypeAdapterFactory(new SingleEntryListTypeAdapterFactory())
.registerTypeAdapter(Expr.class, new ExprDeserializer())
.registerTypeAdapter(Expr.StringExpr.class, new ExprDeserializer.StringX())
.registerTypeAdapter(Expr.BoolExpr.class, new BoolExprDeserializer())
.setLenient()
.setPrettyPrinting()
.create();
try {
@ -81,12 +57,6 @@ public class Respackopts implements ClientModInitializer {
} catch (Throwable e) {
LOGGER.error("Could not resolve config directory", e);
}
try {
STAR_SCRIPT = new Starscript();
} 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);
}
@Override
@ -98,7 +68,6 @@ public class Respackopts implements ClientModInitializer {
}
if (CONFIG.debugLogs)
SAVE_ACTIONS.add(() -> LOGGER.info("Save"));
SAVE_ACTIONS.add(() -> MetaCache.forEach((key, state) -> STAR_SCRIPT.set(sanitizeString(state.packId()), state.configBranch()::buildStarScript)));
SAVE_ACTIONS.add(() -> {
if (CONFIG.debugLogs)
LOGGER.info("Generating shader code");
@ -116,15 +85,6 @@ public class Respackopts implements ClientModInitializer {
if (FabricLoader.getInstance().isModLoaded("frex")) {
FrexCompat.onInitializeFrex();
}
ResourceConditions.register(FAPI_CONDITION, jsonObject -> {
try {
if (!jsonObject.has("value")) throw new RpoFormatException(FAPI_CONDITION + " does not have a value");
return GSON.fromJson(jsonObject.get("value"), Condition.class).evaluate("fabric");
} catch (RpoFormatException | JsonSyntaxException e) {
LOGGER.error("Could not parse condition", e);
return false;
}
});
}
public static String sanitizeString(String s) {

View File

@ -0,0 +1,54 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.respackopts.util.*;
import net.fabricmc.fabric.api.client.command.v2.*;
import net.fabricmc.loader.api.*;
import net.minecraft.text.*;
import java.io.*;
import java.nio.file.*;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*;
public class RpoCommand {
private static final ModContainer CONTAINER = FabricLoader.getInstance().getModContainer(Respackopts.ID).get();
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(literal("rpo").then(literal("dump").then(literal("glsl").executes(ctx -> {
ctx.getSource().sendFeedback(dump(Respackopts.getShaderImportSource(), "frex.glsl"));
return 1;
}))));
dispatcher.register(literal("rpo").then(literal("dump").then(literal("config").executes(ctx -> {
MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.toString(), id + ".txt")));
return 1;
}))));
dispatcher.register(literal("rpo").then(literal("dump").executes(ctx -> {
MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.toString(), id + ".txt")));
return 1;
})));
dispatcher.register(literal("rpo").then(literal("version").executes(ctx -> {
ctx.getSource().sendFeedback(Text.translatable("respackopts.versionText", CONTAINER.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
})));
dispatcher.register(literal("rpo").executes(ctx -> {
ctx.getSource().sendFeedback(Text.translatable("respackopts.versionText", CONTAINER.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
}));
});
}
private static final Path dumpPath = FabricLoader.getInstance().getGameDir().resolve("respackopts");
private static Text dump(String text, String fileName) {
try {
if (!Files.exists(dumpPath)) Files.createDirectories(dumpPath);
Path filePath = dumpPath.resolve(fileName);
try (BufferedWriter bw = Files.newBufferedWriter(filePath, StandardOpenOption.CREATE)) {
bw.write(text);
}
return Text.translatable("respackopts.dumpSucceeded", filePath.toAbsolutePath());
}
catch (Throwable e) {
return Text.translatable("respackopts.dumpFailed");
}
}
}

View File

@ -2,8 +2,11 @@ package io.gitlab.jfronny.respackopts.filters;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import io.gitlab.jfronny.muscript.debug.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.gson.*;
import io.gitlab.jfronny.respackopts.model.DirRpo;
import io.gitlab.jfronny.respackopts.model.cache.*;
import io.gitlab.jfronny.respackopts.model.enums.PackCapability;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
@ -22,7 +25,7 @@ public class DirFilterEventImpl {
return previous.get();
String path = new ResourcePath(type, id).getName();
DirRpo rpo = findDirRpo(pack, path);
if (rpo != null && dirHidden(rpo, MetaCache.getId(MetaCache.getKeyByPack(pack)), path)) {
if (rpo != null && dirHidden(rpo, MetaCache.getKeyByPack(pack), path)) {
path = findReplacementDir(path, rpo);
if (path == null) throw new FileNotFoundException();
ResourcePath rp = new ResourcePath(path);
@ -30,7 +33,7 @@ public class DirFilterEventImpl {
}
return previous.get();
});
UserResourceEvents.FIND_RESOURCE.register((type, namespace, prefix, maxDepth, pathFilter, previous, pack) -> {
UserResourceEvents.FIND_RESOURCE.register((type, namespace, prefix, allowedPathPredicate, previous, pack) -> {
// Warning: the Identifiers here DON'T CONTAIN THE TYPE!
// Therefore, it needs to be added when calling a method that generates a ResourcePath!
Collection<Identifier> prevVals = previous.get();
@ -42,7 +45,7 @@ public class DirFilterEventImpl {
String path = type.getDirectory() + "/" + identifier.getNamespace() + "/" + identifier.getPath();
DirRpo rpo = findDirRpo(pack, path);
if (rpo != null) {
if (dirHidden(rpo, MetaCache.getId(MetaCache.getKeyByPack(pack)), path)) {
if (dirHidden(rpo, MetaCache.getKeyByPack(pack), path)) {
path = findReplacementDir(path, rpo);
if (path == null)
nextRes.remove(identifier);
@ -51,11 +54,11 @@ public class DirFilterEventImpl {
if (s.length == 3) {
ResourcePath rp = new ResourcePath(path);
//TODO improve this impl (used for files that aren't at the original location
for (Identifier resource : pack.findResources(rp.getType(), rp.getId().getNamespace(), rp.getId().getPath(), maxDepth, (a) -> true)) {
for (Identifier resource : pack.findResources(rp.getType(), rp.getId().getNamespace(), rp.getId().getPath(), (a) -> true)) {
String p = type.getDirectory() + "/" + resource.getNamespace() + "/" + resource.getPath();
p = p.replace(rpo.fallback, rpo.path + "/");
rp = new ResourcePath(p);
if (pathFilter.test(p))
if (allowedPathPredicate.test(rp.getId()))
nextRes.add(rp.getId());
}
}
@ -70,7 +73,7 @@ public class DirFilterEventImpl {
return previous.get();
String path = new ResourcePath(type, id).getName();
DirRpo rpo = findDirRpo(pack, path);
if (rpo != null && dirHidden(rpo, MetaCache.getId(MetaCache.getKeyByPack(pack)), path)) {
if (rpo != null && dirHidden(rpo, MetaCache.getKeyByPack(pack), path)) {
path = findReplacementDir(path, rpo);
if (path == null)
return false;
@ -86,19 +89,24 @@ public class DirFilterEventImpl {
return dir.replace(rpo.path + "/", rpo.fallback);
}
private static boolean dirHidden(DirRpo rpo, String packId, String file) {
private static boolean dirHidden(DirRpo rpo, CacheKey key, String file) {
if (rpo.condition == null)
return false;
try {
return !rpo.condition.evaluate(packId);
} catch (RpoFormatException e) {
Respackopts.LOGGER.error("Couldn't parse dir conditions for " + file + " (" + packId + ")", e);
return !rpo.condition.get(MetaCache.getParameter(key));
} catch (RuntimeException e) {
try {
Respackopts.LOGGER.error("Could not evaluate condition " + file + " (pack: " + key.packName() + ") with condition:\n" + ObjectGraphPrinter.printGraph(rpo.condition) + ")", e);
} catch (IllegalAccessException ex) {
Respackopts.LOGGER.error("Could not evaluate condition " + file + " (pack: " + key.packName() + ")", e);
}
}
return false;
}
private static DirRpo findDirRpo(ResourcePack pack, String name) {
Map<String, DirRpo> drpReg = MetaCache.getState(MetaCache.getKeyByPack(pack)).cachedDirRPOs();
CachedPackState state = MetaCache.getState(MetaCache.getKeyByPack(pack));
Map<String, DirRpo> drpReg = state.cachedDirRPOs();
int li = name.lastIndexOf('/');
if (li <= 0)
return null;
@ -118,7 +126,7 @@ public class DirFilterEventImpl {
}
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 = AttachmentHolder.deserialize(state.metadata().version, w, DirRpo.class);
drp.path = name;
if (drp.fallback != null && !drp.fallback.endsWith("/"))
drp.fallback += "/";

View File

@ -41,7 +41,7 @@ public class FileFilterEventImpl {
}
else return null;
});
UserResourceEvents.FIND_RESOURCE.register((type, namespace, prefix, maxDepth, pathFilter, previous, pack) -> {
UserResourceEvents.FIND_RESOURCE.register((type, namespace, prefix, allowedPathPredicate, previous, pack) -> {
// Warning: the Identifiers here DON'T CONTAIN THE TYPE!
// Therefore, it needs to be added when calling a method that generates a ResourcePath!
Collection<Identifier> prevVals = previous.get();

View File

@ -1,5 +1,7 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.muscript.debug.*;
import io.gitlab.jfronny.respackopts.model.cache.*;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
@ -10,10 +12,15 @@ public class FileExclusionProvider {
return FileRpoSearchProvider.modifyWithRpo(file, pack, rpo -> {
if (rpo.condition == null)
return false;
CacheKey key = MetaCache.getKeyByPack(pack);
try {
return !rpo.condition.evaluate(MetaCache.getId(MetaCache.getKeyByPack(pack)));
} catch (RpoFormatException e) {
Respackopts.LOGGER.error("Could not evaluate condition " + file, e);
return !rpo.condition.get(MetaCache.getParameter(key));
} catch (RuntimeException e) {
try {
Respackopts.LOGGER.error("Could not evaluate condition " + file + " (pack: " + key.packName() + ") with condition:\n" + ObjectGraphPrinter.printGraph(rpo.condition) + ")", e);
} catch (IllegalAccessException ex) {
Respackopts.LOGGER.error("Could not evaluate condition " + file + " (pack: " + key.packName() + ")", e);
}
return false;
}
}, false);

View File

@ -1,17 +1,20 @@
package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import meteordevelopment.starscript.Script;
import net.minecraft.resource.ResourcePack;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.*;
import io.gitlab.jfronny.respackopts.util.*;
import net.minecraft.resource.*;
import java.io.*;
import java.util.Map;
import java.util.*;
public class FileExpansionProvider {
public static synchronized InputStream replace(InputStream is, Map<String, Script> expansions) throws IOException {
public static synchronized InputStream replace(OAny<?> parameter, InputStream is, Map<String, Expr.StringExpr> expansions) throws IOException {
String s = new String(is.readAllBytes());
for (Map.Entry<String, Script> entry : expansions.entrySet()) {
s = s.replace("${" + entry.getKey() + "}", Respackopts.STAR_SCRIPT.run(entry.getValue()).toString());
for (Map.Entry<String, Expr.StringExpr> entry : expansions.entrySet()) {
s = s.replace("${" + entry.getKey() + "}", entry.getValue().get(parameter));
}
return new ByteArrayInputStream(s.getBytes());
}
@ -21,7 +24,7 @@ public class FileExpansionProvider {
if (rpo.expansions == null || rpo.expansions.isEmpty())
return inputStream;
try {
return replace(inputStream, rpo.expansions);
return replace(MetaCache.getParameter(MetaCache.getKeyByPack(pack)), inputStream, rpo.expansions);
} catch (IOException e) {
Respackopts.LOGGER.error("Could not perform file expansion on " + file, e);
return inputStream;

View File

@ -2,8 +2,10 @@ package io.gitlab.jfronny.respackopts.filters.util;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.gson.*;
import io.gitlab.jfronny.respackopts.model.DirRpo;
import io.gitlab.jfronny.respackopts.model.FileRpo;
import io.gitlab.jfronny.respackopts.model.cache.*;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import net.minecraft.resource.ResourceNotFoundException;
import net.minecraft.resource.ResourcePack;
@ -22,7 +24,8 @@ public class FileRpoSearchProvider {
public static <T> T modifyWithRpo(String fileName, ResourcePack pack, ModifiedGenerator<T> getModified, T defaultValue) {
if (FileRpoSearchProvider.isRpo(fileName))
return defaultValue;
Map<String, FileRpo> frpReg = MetaCache.getState(MetaCache.getKeyByPack(pack)).cachedFileRPOs();
CachedPackState state = MetaCache.getState(MetaCache.getKeyByPack(pack));
Map<String, FileRpo> frpReg = state.cachedFileRPOs();
String rpPathName = fileName + Respackopts.FILE_EXTENSION;
if (frpReg.containsKey(rpPathName))
return getModified.getModified(frpReg.get(rpPathName));
@ -40,7 +43,7 @@ public class FileRpoSearchProvider {
try (InputStream stream = rpoPath == null ? pack.openRoot(rpPathName) : pack.open(rpoPath.getType(), rpoPath.getId());
Reader w = stream == null ? null : new InputStreamReader(stream)) {
if (w == null) throw new FileNotFoundException("Could not find file: " + fileName);
FileRpo frp = Respackopts.GSON.fromJson(w, FileRpo.class);
FileRpo frp = AttachmentHolder.deserialize(state.metadata().version, w, FileRpo.class);
frp.path = rpPathName;
frpReg.put(rpPathName, frp);
return getModified.getModified(frp);

View File

@ -0,0 +1,39 @@
package io.gitlab.jfronny.respackopts.gson;
import io.gitlab.jfronny.commons.throwable.*;
import io.gitlab.jfronny.respackopts.*;
import java.io.*;
public class AttachmentHolder {
private static final ThreadLocal<Attachment> attachments = new ThreadLocal<>();
public static <TEx extends Throwable> void attach(int version, ThrowingRunnable<TEx> runnable) throws TEx {
try {
attachments.set(new Attachment(version));
runnable.run();
} finally {
attachments.remove();
}
}
public static <TOut, TEx extends Throwable> TOut attach(int version, ThrowingSupplier<TOut, TEx> runnable) throws TEx {
try {
attachments.set(new Attachment(version));
return runnable.get();
} finally {
attachments.remove();
}
}
public static <T> T deserialize(int version, Reader r, Class<T> klazz) {
return attach(version, () -> Respackopts.GSON.fromJson(r, klazz));
}
public static int getAttachedVersion() {
return attachments.get().version;
}
private record Attachment(int version) {
}
}

View File

@ -0,0 +1,87 @@
package io.gitlab.jfronny.respackopts.gson;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.reflect.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import java.lang.reflect.Type;
import java.util.*;
public class BoolExprDeserializer implements JsonDeserializer<Expr.BoolExpr> {
private static final Type conditionListType = new TypeToken<List<Expr.BoolExpr>>(){}.getType();
@Override
public Expr.BoolExpr deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (AttachmentHolder.getAttachedVersion() > 7) {
return ((Expr<?>)context.deserialize(json, Expr.class)).asBoolExpr();
}
// Legacy JSON syntax
if (json.isJsonObject()) {
JsonObject jo = json.getAsJsonObject();
if (jo.size() != 1)
throw new JsonParseException("More than one key in a condition object");
for (Map.Entry<String, JsonElement> entry : jo.entrySet()) {
return switch (entry.getKey().toLowerCase(Locale.ROOT)) {
case "and", "add", "&" -> merge(context.deserialize(entry.getValue(), conditionListType), Token.And);
case "==", "=", "equal", "eq" -> merge(context.deserialize(entry.getValue(), conditionListType), Token.EqualEqual);
case "not", "nor", "!" -> new Not(merge(context.deserialize(entry.getValue(), conditionListType), Token.Or));
case "or", "|" -> merge(context.deserialize(entry.getValue(), conditionListType), Token.Or);
case "^", "xor" -> merge(context.deserialize(entry.getValue(), conditionListType), Token.BangEqual);
default -> throw new JsonParseException("Unknown condition type: " + entry.getKey());
};
}
}
else if (json.isJsonArray()) {
return merge(context.deserialize(json, conditionListType), Token.And);
}
else if (json.isJsonPrimitive()) {
JsonPrimitive pr = json.getAsJsonPrimitive();
if (pr.isString()) {
String name = pr.getAsString();
if (name.toLowerCase(Locale.ROOT).equals("true"))
return Expr.literal(true);
if (name.toLowerCase(Locale.ROOT).equals("false"))
return Expr.literal(false);
return rpoBooleanCondition(name);
}
else if (pr.isBoolean()) {
return Expr.literal(pr.getAsBoolean());
}
}
throw new JsonParseException("Invalid data type for condition");
}
private Expr.BoolExpr merge(List<Expr.BoolExpr> expressions, Token token) {
Expr.BoolExpr current = expressions.get(0);
for (Expr.BoolExpr expr : expressions.subList(1, expressions.size())) {
current = switch (token) {
case EqualEqual -> new Equal(current, expr);
case BangEqual -> new Not(new Equal(current, expr));
default -> new LogicBiExpr(current, expr, token);
};
}
return current;
}
private Expr.BoolExpr rpoBooleanCondition(String name) {
if (name.startsWith("modversion:")) {
String code = name.substring("modversion:".length());
String mod = code.substring(0, code.indexOf(':'));
String predicate = code.substring(code.indexOf(':') + 1);
return new Call(new Variable("version"), List.of(
Expr.literal(mod).asObjectExpr(),
Expr.literal(predicate).asObjectExpr()
)).asBoolExpr();
}
Expr.ObjectExpr e = null;
String[] arr = name.split("[:.]");
for (int i = 0; i < arr.length; i++) {
if (i == 0) e = new Variable(arr[i]);
else e = new Get(e, Expr.literal(arr[i]));
}
if (e == null) throw new JsonParseException("Invalid RPO condition: \"" + name + "\"");
return e.asBoolExpr();
}
}

View File

@ -1,65 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.respackopts.model.condition.*;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class ConditionDeserializer implements JsonDeserializer<Condition> {
private static final AndConditionFactory and = new AndConditionFactory();
private static final Set<CollectingConditionFactory> factories = Set.of(
and,
new EqualityConditionFactory(),
new NorConditionFactory(),
new OrConditionFactory(),
new XorConditionFactory()
);
private static final Type conditionListType = new TypeToken<List<Condition>>(){}.getType();
@Override
public Condition deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject()) {
JsonObject jo = json.getAsJsonObject();
if (jo.size() != 1)
throw new JsonParseException("More than one key in a condition object");
for (Map.Entry<String, JsonElement> entry : jo.entrySet()) {
String name = entry.getKey();
if (name.equals(ResourceConditions.CONDITIONS_KEY)) {
if (entry.getValue() instanceof JsonObject fabricCondition) {
return new FabricCondition(fabricCondition);
}
else throw new JsonParseException("Expected " + ResourceConditions.CONDITIONS_KEY + " to be an object condition");
}
for (CollectingConditionFactory factory : factories) {
if (factory.getNames().contains(name))
return factory.build(context.deserialize(entry.getValue(), conditionListType));
}
throw new JsonParseException("Unknown condition type: " + name);
}
}
else if (json.isJsonArray()) {
return and.build(context.deserialize(json, conditionListType));
}
else if (json.isJsonPrimitive()) {
JsonPrimitive pr = json.getAsJsonPrimitive();
if (pr.isString()) {
String name = pr.getAsString();
if (name.toLowerCase(Locale.ROOT).equals("true"))
return new BooleanCondition(true);
if (name.toLowerCase(Locale.ROOT).equals("false"))
return new BooleanCondition(false);
return new RpoBooleanCondition(name);
}
else if (pr.isBoolean()) {
return new BooleanCondition(pr.getAsBoolean());
}
}
throw new JsonParseException("Invalid data type for condition");
}
}

View File

@ -0,0 +1,41 @@
package io.gitlab.jfronny.respackopts.gson;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.compiler.*;
import java.lang.reflect.Type;
import java.util.*;
public class ExprDeserializer implements JsonDeserializer<Expr<?>> {
private static final Map<String, Expr<?>> compiledScripts = new HashMap<>();
@Override
public Expr<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
String s = json.getAsJsonPrimitive().getAsString();
if (compiledScripts.containsKey(s))
return compiledScripts.get(s);
try {
Expr<?> expr = Parser.parse(AttachmentHolder.getAttachedVersion() <= 7
? StarScriptIngester.starScriptToMu(s)
: s);
compiledScripts.put(s, expr);
return expr;
} catch (Parser.ParseException e) {
throw new JsonParseException("Could not create script", e);
}
}
else {
throw new JsonParseException("Could not parse script: Expected string");
}
}
public static class StringX implements JsonDeserializer<Expr.StringExpr> {
@Override
public Expr.StringExpr deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Expr<?> expr = jsonDeserializationContext.deserialize(jsonElement, Expr.class);
return expr.asStringExpr();
}
}
}

View File

@ -1,41 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
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.Respackopts;
import meteordevelopment.starscript.Script;
import meteordevelopment.starscript.compiler.Compiler;
import meteordevelopment.starscript.compiler.Parser;
import meteordevelopment.starscript.utils.Error;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class ScriptDeserializer implements JsonDeserializer<Script> {
private static final Map<String, Script> compiledScripts = new HashMap<>();
@Override
public Script deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
String s = json.getAsJsonPrimitive().getAsString();
if (compiledScripts.containsKey(s))
return compiledScripts.get(s);
Parser.Result result = Parser.parse(s);
if (result.hasErrors()) {
for (Error error : result.errors) {
Respackopts.LOGGER.error(error.toString());
}
throw new JsonParseException("Could not parse script: See errors above");
}
Script ss = Compiler.compile(result);
compiledScripts.put(s, ss);
return ss;
}
else {
throw new JsonParseException("Could not parse script: Expected string");
}
}
}

View File

@ -1,68 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
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.Respackopts;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class SingleEntryListTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != List.class
|| !(type instanceof ParameterizedType pt))
return null;
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Using SingleEntryListTypeAdapter for " + typeToken);
Type elementType = pt.getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) createAdapter(elementAdapter);
}
private <T> TypeAdapter<List<T>> createAdapter(final TypeAdapter<T> elementAdapter) {
return new TypeAdapter<>() {
@Override
public void write(JsonWriter out, List<T> value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginArray();
for (T entry : value) {
elementAdapter.write(out, entry);
}
out.endArray();
}
@Override
public List<T> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
List<T> list = new ArrayList<>();
if (in.peek() == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
list.add(elementAdapter.read(in));
}
in.endArray();
}
else list.add(elementAdapter.read(in));
return list;
}
};
}
}

View File

@ -1,68 +0,0 @@
package io.gitlab.jfronny.respackopts.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
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.Respackopts;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;
public class SingleEntrySetTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Set.class
|| !(type instanceof ParameterizedType pt))
return null;
if (Respackopts.CONFIG.debugLogs)
Respackopts.LOGGER.info("Using SingleEntrySetTypeAdapter for " + typeToken);
Type elementType = pt.getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) createAdapter(elementAdapter);
}
private <T> TypeAdapter<Set<T>> createAdapter(final TypeAdapter<T> elementAdapter) {
return new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Set<T> value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginArray();
for (T entry : value) {
elementAdapter.write(out, entry);
}
out.endArray();
}
@Override
public Set<T> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Set<T> list = new LinkedHashSet<>();
if (in.peek() == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
list.add(elementAdapter.read(in));
}
in.endArray();
}
else list.add(elementAdapter.read(in));
return list;
}
};
}
}

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBooleanEntry;
import java.lang.reflect.Type;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.tree.*;
import io.gitlab.jfronny.respackopts.model.enums.PackReloadType;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.tree.ConfigEnumEntry;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.respackopts.gson.entry;
import com.google.gson.*;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.respackopts.model.tree.ConfigNumericEntry;
import io.gitlab.jfronny.respackopts.model.enums.NumericEntryType;

View File

@ -10,7 +10,7 @@ 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.TranslatableText;
import net.minecraft.text.*;
public class ModMenuCompat implements ModMenuApi {
@Override
@ -20,7 +20,7 @@ public class ModMenuCompat implements ModMenuApi {
ConfigBuilder builder;
builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(new TranslatableText("respackopts.mainconfig"));
.setTitle(Text.translatable("respackopts.mainconfig"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
PackReloadType.Aggregator agg = new PackReloadType.Aggregator();
builder.setSavingRunnable(() -> {
@ -34,21 +34,21 @@ public class ModMenuCompat implements ModMenuApi {
});
//Respackopts config screen
ConfigFile defaultConfig = new ConfigFile();
ConfigCategory mainConfig = builder.getOrCreateCategory(new TranslatableText("respackopts.mainconfig"));
ConfigCategory mainConfig = builder.getOrCreateCategory(Text.translatable("respackopts.mainconfig"));
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugCommands"), Respackopts.CONFIG.debugCommands)
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.debugCommands"), Respackopts.CONFIG.debugCommands)
.setDefaultValue(defaultConfig.debugCommands)
.setSaveConsumer(b -> Respackopts.CONFIG.debugCommands = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.debugLogs"), Respackopts.CONFIG.debugLogs)
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.debugLogs"), Respackopts.CONFIG.debugLogs)
.setDefaultValue(defaultConfig.debugLogs)
.setSaveConsumer(b -> Respackopts.CONFIG.debugLogs = b)
.build()
);
mainConfig.addEntry(
entryBuilder.startBooleanToggle(new TranslatableText("respackopts.mainconfig.dashloaderCompat"), Respackopts.CONFIG.dashloaderCompat)
entryBuilder.startBooleanToggle(Text.translatable("respackopts.mainconfig.dashloaderCompat"), Respackopts.CONFIG.dashloaderCompat)
.requireRestart()
.setDefaultValue(defaultConfig.dashloaderCompat)
.setSaveConsumer(b -> Respackopts.CONFIG.dashloaderCompat = b)
@ -56,14 +56,14 @@ public class ModMenuCompat implements ModMenuApi {
);
//Pack config screens
MetaCache.forEach((key, state) -> {
ConfigCategory config = builder.getOrCreateCategory(new TranslatableText((state.metadata().version >= 5 ? "rpo." : "respackopts.title.") + state.packId()));
ConfigCategory config = builder.getOrCreateCategory(Text.translatable((state.metadata().version >= 5 ? "rpo." : "respackopts.title.") + state.packId()));
Respackopts.GUI_FACTORY.buildCategory(state.configBranch(), state.packId(), config::addEntry, agg, entryBuilder, "");
});
return builder.build();
}
catch (Throwable t) {
t.printStackTrace();
return new FatalErrorScreen(new TranslatableText("respackopts.loadFailed"), new TranslatableText("respackopts.loadError"));
return new FatalErrorScreen(Text.translatable("respackopts.loadFailed"), Text.translatable("respackopts.loadError"));
}
};
}

View File

@ -31,7 +31,7 @@ public abstract class ResourcePackEntryMixin {
@Inject(at = @At("TAIL"), method = "render(Lnet/minecraft/client/util/math/MatrixStack;IIIIIIIZF)V")
private void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta, CallbackInfo info) {
if (this.isSelectable() && MetaCache.getKeyByDisplayName(pack.getDisplayName().asString()) != null) {
if (this.isSelectable() && MetaCache.getKeyByDisplayName(pack.getDisplayName().getString()) != null) {
int d = mouseX - x;
d = widget.getRowWidth() - d;
int e = mouseY - y;
@ -44,8 +44,7 @@ public abstract class ResourcePackEntryMixin {
public void mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> info) {
if (!info.getReturnValue()) {
if (this.isSelectable()) {
String displayName = pack.getDisplayName().asString();
CacheKey dataLocation = MetaCache.getKeyByDisplayName(displayName);
CacheKey dataLocation = MetaCache.getKeyByDisplayName(pack.getDisplayName().getString());
if (dataLocation != null && rpo$selected) {
info.setReturnValue(true);
MinecraftClient c = MinecraftClient.getInstance();

View File

@ -38,7 +38,7 @@ public class ResourcePackManagerMixin {
}
if (packConfType != null) {
try (InputStream is = rpi.open(packConfType, Respackopts.CONF_ID); InputStreamReader isr = new InputStreamReader(is)) {
String displayName = v.getDisplayName().asString();
String displayName = v.getDisplayName().getString();
String packName = rpi.getName();
PackMeta conf = Respackopts.GSON.fromJson(isr, PackMeta.class);
if (Respackopts.CONFIG.debugLogs)

View File

@ -1,15 +1,15 @@
package io.gitlab.jfronny.respackopts.model;
import com.google.gson.annotations.SerializedName;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import io.gitlab.jfronny.respackopts.model.condition.Condition;
import com.google.gson.annotations.*;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import io.gitlab.jfronny.muscript.compiler.*;
public class DirRpo {
@SerializedName(value = "condition", alternate = {"conditions"})
public Condition condition;
public Expr.BoolExpr condition;
@SerializedName(value = "fallback", alternate = {"fallbacks"})
public String fallback;
@GsonHidden
@Ignore
public String path;
}

View File

@ -1,21 +1,19 @@
package io.gitlab.jfronny.respackopts.model;
import com.google.gson.annotations.SerializedName;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import io.gitlab.jfronny.respackopts.model.condition.Condition;
import meteordevelopment.starscript.Script;
import com.google.gson.annotations.*;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import io.gitlab.jfronny.muscript.compiler.*;
import java.util.Map;
import java.util.Set;
import java.util.*;
public class FileRpo {
@SerializedName(value = "condition", alternate = {"conditions"})
public Condition condition;
public Expr.BoolExpr condition;
@SerializedName(value = "fallback", alternate = {"fallbacks"})
public Set<String> fallbacks;
@SerializedName(value = "expansion", alternate = {"expansions"})
public Map<String, Script> expansions;
public Map<String, Expr.StringExpr> expansions;
@GsonHidden
@Ignore
public String path;
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.respackopts.model;
public interface ThrowingBiConsumer<T, U, TEx extends Throwable> {
void accept(T var1, U var2) throws TEx;
}

View File

@ -1,5 +1,7 @@
package io.gitlab.jfronny.respackopts.model.cache;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.model.DirRpo;
import io.gitlab.jfronny.respackopts.model.FileRpo;
import io.gitlab.jfronny.respackopts.model.PackMeta;
@ -15,9 +17,17 @@ public record CachedPackState(
ConfigBranch configBranch,
PackMeta metadata,
Map<String, FileRpo> cachedFileRPOs,
Map<String, DirRpo> cachedDirRPOs
Map<String, DirRpo> cachedDirRPOs,
ExpressionParameter expressionParameter
) {
public CachedPackState(CacheKey key, PackMeta meta, ConfigBranch branch) {
this(meta.id, key.displayName(), key.packName(), branch, meta, new HashMap<>(), new HashMap<>());
this(meta.id,
key.displayName(),
key.packName(),
branch,
meta,
new HashMap<>(),
new HashMap<>(),
new ExpressionParameter(new HashMap<>(branch.getOptic().getValue())));
}
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public class AndConditionFactory implements CollectingConditionFactory {
@Override
public Condition build(List<Condition> conditions) {
return packId -> {
for (Condition condition : conditions) {
if (!condition.evaluate(packId))
return false;
}
return true;
};
}
@Override
public Set<String> getNames() {
return Set.of("and", "add", "&");
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
public record BooleanCondition(boolean value) implements Condition {
@Override
public boolean evaluate(String packId) {
return value;
}
}

View File

@ -1,9 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public interface CollectingConditionFactory {
Condition build(List<Condition> conditions);
Set<String> getNames();
}

View File

@ -1,7 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
public interface Condition {
boolean evaluate(String packId) throws RpoFormatException;
}

View File

@ -1,25 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public class EqualityConditionFactory implements CollectingConditionFactory {
@Override
public Condition build(List<Condition> conditions) {
return packId -> {
Boolean b = null;
for (Condition condition : conditions) {
if (b == null)
b = condition.evaluate(packId);
else if (b != condition.evaluate(packId))
return false;
}
return true;
};
}
@Override
public Set<String> getNames() {
return Set.of("==", "=", "equal", "eq");
}
}

View File

@ -1,12 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import com.google.gson.JsonObject;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
public record FabricCondition(JsonObject condition) implements Condition {
@Override
public boolean evaluate(String packId) throws RpoFormatException {
return ResourceConditions.conditionMatches(condition);
}
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public class NorConditionFactory implements CollectingConditionFactory {
@Override
public Condition build(List<Condition> conditions) {
return packId -> {
for (Condition condition : conditions) {
if (condition.evaluate(packId))
return false;
}
return true;
};
}
@Override
public Set<String> getNames() {
return Set.of("not", "nor", "!");
}
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public class OrConditionFactory implements CollectingConditionFactory {
@Override
public Condition build(List<Condition> conditions) {
return packId -> {
for (Condition condition : conditions) {
if (condition.evaluate(packId))
return true;
}
return false;
};
}
@Override
public Set<String> getNames() {
return Set.of("or", "|");
}
}

View File

@ -1,62 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import com.google.gson.JsonParseException;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.cache.CacheKey;
import io.gitlab.jfronny.respackopts.model.cache.CachedPackState;
import io.gitlab.jfronny.respackopts.util.MetaCache;
import io.gitlab.jfronny.respackopts.model.ThrowingBiConsumer;
import io.gitlab.jfronny.respackopts.util.RpoFormatException;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.VersionParsingException;
import net.fabricmc.loader.api.metadata.version.VersionPredicate;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public record RpoBooleanCondition(String name) implements Condition {
public RpoBooleanCondition {
if (name == null)
throw new JsonParseException("Condition must not be null");
}
@Override
public boolean evaluate(String packId) throws RpoFormatException {
String condition = name;
if (!condition.contains(":")) {
condition = packId + ":" + condition;
}
String sourcePack = condition.substring(0, condition.indexOf(':'));
condition = condition.substring(condition.indexOf(':') + 1);
final String finalCondition = condition;
if (sourcePack.equals("modversion")) {
// builtin conditions
String modName = condition.substring(0, condition.indexOf(':'));
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(modName);
if (container.isEmpty()) {
if (Respackopts.CONFIG.debugLogs) Respackopts.LOGGER.info("Mod not found: " + modName);
return false;
}
try {
return VersionPredicate.parse(condition.substring(condition.indexOf(':') + 1)).test(container.get().getMetadata().getVersion());
} catch (VersionParsingException e) {
throw new RpoFormatException("Could not parse version predicate", e);
}
}
AtomicReference<Boolean> result = new AtomicReference<>(null);
MetaCache.forEach((ThrowingBiConsumer<CacheKey, CachedPackState, ? extends RpoFormatException>) (key, data) -> {
if (Objects.equals(MetaCache.getId(key), sourcePack)) {
try {
result.set(data.configBranch().getBoolean(finalCondition));
} catch (RpoFormatException ex) {
throw new RpoFormatException("Could not get value", ex);
}
}
});
if (result.get() == null)
throw new RpoFormatException("Could not find pack with specified ID: " + sourcePack);
return result.get();
}
}

View File

@ -1,23 +0,0 @@
package io.gitlab.jfronny.respackopts.model.condition;
import java.util.List;
import java.util.Set;
public class XorConditionFactory implements CollectingConditionFactory {
@Override
public Condition build(List<Condition> conditions) {
return packId -> {
boolean bl = false;
for (Condition condition : conditions) {
if (condition.evaluate(packId))
bl = !bl;
}
return bl;
};
}
@Override
public Set<String> getNames() {
return Set.of("^", "xor");
}
}

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import meteordevelopment.starscript.value.Value;
import java.util.Objects;
@ -29,8 +29,8 @@ public class ConfigBooleanEntry extends ConfigEntry<Boolean> {
}
@Override
public Value buildStarScript() {
return Value.bool(getValue());
public OAny<?> getOptic() {
return (OBool) this::getValue;
}
@Override

View File

@ -1,15 +1,14 @@
package io.gitlab.jfronny.respackopts.model.tree;
import com.google.common.collect.ImmutableMap;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
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 meteordevelopment.starscript.value.Value;
import meteordevelopment.starscript.value.ValueMap;
import java.util.*;
@ -113,12 +112,12 @@ public class ConfigBranch extends ConfigEntry<Map<String, ConfigEntry<?>>> {
}
@Override
public Value buildStarScript() {
ValueMap vm = new ValueMap();
public OObject getOptic() {
Map<String, OAny<?>> map = new HashMap<>();
for (Map.Entry<String, ConfigEntry<?>> e : super.getValue().entrySet()) {
vm.set(Respackopts.sanitizeString(e.getKey()), () -> e.getValue().buildStarScript());
map.put(Respackopts.sanitizeString(e.getKey()), e.getValue().getOptic());
}
return Value.map(vm);
return OFinal.of(map);
}
@Override

View File

@ -1,11 +1,11 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.muscript.optic.*;
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 meteordevelopment.starscript.value.Value;
import java.util.Objects;
@ -102,7 +102,7 @@ public abstract class ConfigEntry<T> {
public abstract void buildShader(StringBuilder sb, String valueName);
public abstract Value buildStarScript();
public abstract OAny<?> getOptic();
public abstract AbstractConfigListEntry<?> buildEntry(GuiEntryBuilderParam guiEntryBuilderParam);

View File

@ -1,16 +1,14 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.util.GuiFactory;
import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import meteordevelopment.starscript.value.Value;
import meteordevelopment.starscript.value.ValueMap;
import net.minecraft.text.Text;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.*;
import io.gitlab.jfronny.respackopts.model.enums.*;
import io.gitlab.jfronny.respackopts.util.*;
import me.shedaniel.clothconfig2.api.*;
import net.minecraft.text.*;
import java.util.*;
import java.util.function.Function;
import java.util.function.*;
public class ConfigEnumEntry extends ConfigEntry<String> {
public List<String> values = new ArrayList<>();
@ -115,16 +113,8 @@ public class ConfigEnumEntry extends ConfigEntry<String> {
}
@Override
public Value buildStarScript() {
String selected = getValue();
if (getVersion() <= 6)
return Value.string(selected);
ValueMap map = new ValueMap();
map.set("value", Value.string(selected));
for (String value : values) {
map.set(value, Value.bool(selected.equals(value)));
}
return Value.map(map);
public OAny<?> getOptic() {
return new OEnum(values, getValue());
}
@Override

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.respackopts.model.tree;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.NumericEntryType;
@ -7,7 +8,6 @@ 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 meteordevelopment.starscript.value.Value;
import java.util.Objects;
@ -67,8 +67,8 @@ public class ConfigNumericEntry extends ConfigEntry<Double> {
}
@Override
public Value buildStarScript() {
return Value.number(getValue());
public OAny<?> getOptic() {
return (ONumber) this::getValue;
}
@Override

View File

@ -1,23 +1,21 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import io.gitlab.jfronny.respackopts.model.PackMeta;
import io.gitlab.jfronny.respackopts.model.ThrowingBiConsumer;
import io.gitlab.jfronny.respackopts.model.cache.CacheKey;
import io.gitlab.jfronny.respackopts.model.cache.CachedPackState;
import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode;
import io.gitlab.jfronny.respackopts.model.enums.PackCapability;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch;
import net.minecraft.resource.ResourcePack;
import org.jetbrains.annotations.Nullable;
import io.gitlab.jfronny.commons.throwable.*;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.*;
import io.gitlab.jfronny.respackopts.model.*;
import io.gitlab.jfronny.respackopts.model.cache.*;
import io.gitlab.jfronny.respackopts.model.enums.*;
import io.gitlab.jfronny.respackopts.model.tree.*;
import net.fabricmc.loader.api.*;
import net.fabricmc.loader.api.metadata.version.*;
import net.minecraft.resource.*;
import org.jetbrains.annotations.*;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class MetaCache {
private static final Map<CacheKey, CachedPackState> PACK_STATES = new HashMap<>();
@ -99,7 +97,6 @@ public class MetaCache {
ConfigBranch b = Respackopts.GSON.fromJson(reader, ConfigBranch.class);
if (PACK_STATES.containsKey(key))
getBranch(key).sync(b, ConfigSyncMode.CONF_LOAD);
Respackopts.STAR_SCRIPT.set(Respackopts.sanitizeString(getId(key)), b::buildStarScript);
} catch (IOException e) {
Respackopts.LOGGER.error("Failed to load " + key, e);
}
@ -134,6 +131,32 @@ public class MetaCache {
return PACK_STATES.get(key);
}
public static ExpressionParameter getParameter(@Nullable CacheKey key) {
return hydrateParameter(key == null ? new ExpressionParameter() : MetaCache.getState(key).expressionParameter());
}
public static ExpressionParameter hydrateParameter(ExpressionParameter parameter) {
MetaCache.forEach((id, state) -> {
String key = Respackopts.sanitizeString(state.packId());
if (!parameter.has(key))
parameter.set(key, state.configBranch().getOptic());
});
StandardLib.addTo(parameter);
parameter.set("version", OFinal.of(args -> {
if (args.size() != 2) throw new IllegalArgumentException("Expected 2 arguments on version but got " + args.size());
VersionPredicate predicate;
try {
predicate = VersionPredicate.parse(args.get(1).asString().getValue());
} catch (VersionParsingException e) {
throw new RuntimeException("Could not parse version predicate", e);
}
return OFinal.of(FabricLoader.getInstance().getModContainer(args.get(0).asString().getValue())
.map(c -> predicate.test(c.getMetadata().getVersion()))
.orElse(false));
}));
return parameter;
}
public static boolean hasCapability(ResourcePack pack, PackCapability capability) {
CacheKey key = getKeyByPack(pack);
if (key == null) return false;

View File

@ -1,55 +0,0 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.respackopts.Respackopts;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.*;
public class RpoCommand {
private static final ModContainer respackopts = FabricLoader.getInstance().getModContainer(Respackopts.ID).get();
public static void register() {
DISPATCHER.register(literal("rpo").then(literal("dump").then(literal("glsl").executes(ctx -> {
ctx.getSource().sendFeedback(dump(Respackopts.getShaderImportSource(), "frex.glsl"));
return 1;
}))));
DISPATCHER.register(literal("rpo").then(literal("dump").then(literal("config").executes(ctx -> {
MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.toString(), id + ".txt")));
return 1;
}))));
DISPATCHER.register(literal("rpo").then(literal("dump").executes(ctx -> {
MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.toString(), id + ".txt")));
return 1;
})));
DISPATCHER.register(literal("rpo").then(literal("version").executes(ctx -> {
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackopts.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
})));
DISPATCHER.register(literal("rpo").executes(ctx -> {
ctx.getSource().sendFeedback(new TranslatableText("respackopts.versionText", respackopts.getMetadata().getVersion(), Respackopts.META_VERSION));
return 1;
}));
}
private static final Path dumpPath = FabricLoader.getInstance().getGameDir().resolve("respackopts");
private static Text dump(String text, String fileName) {
try {
if (!Files.exists(dumpPath)) Files.createDirectories(dumpPath);
Path filePath = dumpPath.resolve(fileName);
try (BufferedWriter bw = Files.newBufferedWriter(filePath, StandardOpenOption.CREATE)) {
bw.write(text);
}
return new TranslatableText("respackopts.dumpSucceeded", filePath.toAbsolutePath());
}
catch (Throwable e) {
return new TranslatableText("respackopts.dumpFailed");
}
}
}

View File

@ -0,0 +1,110 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.gson.reflect.*;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.optic.*;
import io.gitlab.jfronny.respackopts.gson.*;
import io.gitlab.jfronny.respackopts.model.*;
import io.gitlab.jfronny.respackopts.util.*;
import org.junit.jupiter.api.*;
import java.nio.file.*;
import java.util.*;
import static io.gitlab.jfronny.respackopts.Respackopts.*;
import static org.junit.jupiter.api.Assertions.*;
class ConditionJsonSerializationTest {
@BeforeAll
static void initialize() {
Respackopts.LOGGER.info("Expected error end");
Respackopts.FALLBACK_CONF_DIR = Paths.get("./test");
Respackopts.CONFIG = new ConfigFile();
SAVE_ACTIONS.add(() -> Respackopts.LOGGER.info("Save"));
Respackopts.LOGGER.info(Respackopts.FALLBACK_CONF_DIR.toString());
}
@Test
void gsonListOrder() {
String[] expected = new String[] {"one", "two", "three", "zero", "76"};
String gson = "[\"one\", \"two\", \"three\", \"zero\", \"76\"]";
List<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<List<String>>(){}.getType());
int i = 0;
for (String s : parsed) {
assertEquals(s, expected[i++]);
}
}
@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 gsonSingleEntryList() {
String gson = "\"someText\"";
List<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<List<String>>(){}.getType());
assertEquals(parsed.size(), 1);
for (String s : parsed) {
assertEquals(s, "someText");
}
}
@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(() -> evaluateCondition("true")));
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"not\": false}")));
assertTrue(assertDoesNotThrow(() -> evaluateCondition("[true, {\"not\": false}]")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("[true, {\"not\": true}]")));
}
@Test
void gsonConditionXor() {
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"xor\": [true, false]}")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"xor\": [true, true]}")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"xor\": [false, false]}")));
}
@Test
void gsonConditionOr() {
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"or\": [true, false]}")));
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"or\": [true, true]}")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"or\": [false, false]}")));
}
@Test
void gsonConditionNor() {
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"nor\": [true, false]}")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"nor\": [true, true]}")));
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"nor\": [false, false]}")));
}
@Test
void gsonConditionAnd() {
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"and\": [true, false]}")));
assertTrue(assertDoesNotThrow(() -> evaluateCondition("{\"and\": [true, true]}")));
assertFalse(assertDoesNotThrow(() -> evaluateCondition("{\"and\": [false, false]}")));
}
private boolean evaluateCondition(String json) {
Expr.BoolExpr condition = AttachmentHolder.attach(7, () -> GSON.fromJson(json, Expr.BoolExpr.class));
return condition.get(MetaCache.hydrateParameter(new ExpressionParameter()));
}
}

View File

@ -1,103 +0,0 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.respackopts.model.ConfigFile;
import io.gitlab.jfronny.respackopts.model.condition.Condition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import static io.gitlab.jfronny.respackopts.Respackopts.SAVE_ACTIONS;
import static org.junit.jupiter.api.Assertions.*;
class ConditionSerializationTest {
@BeforeAll
static void initialize() {
Respackopts.LOGGER.info("Expected error end");
Respackopts.FALLBACK_CONF_DIR = Paths.get("./test");
Respackopts.CONFIG = new ConfigFile();
SAVE_ACTIONS.add(() -> Respackopts.LOGGER.info("Save"));
Respackopts.LOGGER.info(Respackopts.FALLBACK_CONF_DIR.toString());
}
@Test
void gsonListOrder() {
String[] expected = new String[] {"one", "two", "three", "zero", "76"};
String gson = "[\"one\", \"two\", \"three\", \"zero\", \"76\"]";
List<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<List<String>>(){}.getType());
int i = 0;
for (String s : parsed) {
assertEquals(s, expected[i++]);
}
}
@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 gsonSingleEntryList() {
String gson = "\"someText\"";
List<String> parsed = Respackopts.GSON.fromJson(gson, new TypeToken<List<String>>(){}.getType());
assertEquals(parsed.size(), 1);
for (String s : parsed) {
assertEquals(s, "someText");
}
}
@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,18 +1,11 @@
package io.gitlab.jfronny.respackopts;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.muscript.compiler.*;
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.model.condition.Condition;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBooleanEntry;
import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch;
import io.gitlab.jfronny.respackopts.model.tree.ConfigEnumEntry;
import io.gitlab.jfronny.respackopts.model.tree.ConfigNumericEntry;
import meteordevelopment.starscript.Script;
import io.gitlab.jfronny.respackopts.gson.entry.*;
import io.gitlab.jfronny.respackopts.model.*;
import io.gitlab.jfronny.respackopts.model.tree.*;
public class TemplateTree {
private static final Gson GSON = new GsonBuilder()
@ -20,10 +13,10 @@ public class TemplateTree {
.registerTypeAdapter(ConfigNumericEntry.class, new NumericEntrySerializer())
.registerTypeAdapter(ConfigBooleanEntry.class, new BooleanEntrySerializer())
.registerTypeAdapter(ConfigBranch.class, new ConfigBranchSerializer())
.registerTypeAdapter(Script.class, new ScriptDeserializer())
.registerTypeAdapter(Condition.class, new ConditionDeserializer())
.registerTypeAdapterFactory(new SingleEntrySetTypeAdapterFactory())
.registerTypeAdapterFactory(new SingleEntryListTypeAdapterFactory())
.registerTypeAdapter(Expr.class, new ExprDeserializer())
.registerTypeAdapter(Expr.StringExpr.class, new ExprDeserializer.StringX())
.registerTypeAdapter(Expr.BoolExpr.class, new BoolExprDeserializer())
.setLenient()
.setPrettyPrinting()
.create();