From f825b73e3896aa5ce70c2843d60e6fd3095cc44d Mon Sep 17 00:00:00 2001 From: JFronny <33260128+jfronny@users.noreply.github.com> Date: Mon, 4 Jan 2021 16:26:13 +0100 Subject: [PATCH] Proper conditions --- README.md | 1 + .../lumi/assets/minecraft/lang/en_us.json.rpo | 7 ++- .../lumi/assets/respackopts/conf.json | 3 +- .../jfronny/respackopts/Respackopts.java | 23 ------- .../respackopts/conditions/AndCondition.java | 28 +++++++++ .../respackopts/conditions/Condition.java | 10 +++ .../conditions/ConditionEvaluator.java | 61 +++++++++++++++++++ .../conditions/EqualityCondition.java | 34 +++++++++++ .../respackopts/conditions/NorCondition.java | 28 +++++++++ .../respackopts/conditions/OrCondition.java | 27 ++++++++ .../conditions/ResourcePackFilter.java | 15 +---- .../respackopts/conditions/XorCondition.java | 28 +++++++++ .../respackopts/data/RpoResourceEntry.java | 5 -- .../respackopts/integration/LibCDCompat.java | 23 ++++--- 14 files changed, 243 insertions(+), 50 deletions(-) create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/AndCondition.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/Condition.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/ConditionEvaluator.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/EqualityCondition.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/NorCondition.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/OrCondition.java create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/conditions/XorCondition.java delete mode 100644 src/main/java/io/gitlab/jfronny/respackopts/data/RpoResourceEntry.java diff --git a/README.md b/README.md index 8b8d304..c7a36c7 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ There have been issues with LibCD in development, so it might be smart not to us Respackopts allows creating conditional resources by creating a file named `{targetFile}.rpo`.\ This file is a json file that contains an array named "conditions". The resource will be ignored if any of the conditions are not met.\ This allows (for example) overrides for textures to only be loaded if the user enables them through the config.\ +It also allows the following operations: `and`, `equal`, `nor`/`not`, `or`, `xor` As an example can be seen [here](https://gitlab.com/JFronny/respackopts/-/tree/master/run/resourcepacks/lumi/assets/minecraft/lang) ## Mod developers All data is available in static HashMaps in `io.gitlab.jfronny.respackopts.Respackopts`.\ diff --git a/run/resourcepacks/lumi/assets/minecraft/lang/en_us.json.rpo b/run/resourcepacks/lumi/assets/minecraft/lang/en_us.json.rpo index 79544f0..944e984 100644 --- a/run/resourcepacks/lumi/assets/minecraft/lang/en_us.json.rpo +++ b/run/resourcepacks/lumi/assets/minecraft/lang/en_us.json.rpo @@ -1,5 +1,10 @@ { "conditions": [ - "lumi:subcategoryTest.enableLang" + "lumi:subcategoryTest.enableLang", + { + "not": [ + "lumi:subcategoryTest.enableLangForceDisable" + ] + } ] } \ No newline at end of file diff --git a/run/resourcepacks/lumi/assets/respackopts/conf.json b/run/resourcepacks/lumi/assets/respackopts/conf.json index 008643c..e7e4106 100644 --- a/run/resourcepacks/lumi/assets/respackopts/conf.json +++ b/run/resourcepacks/lumi/assets/respackopts/conf.json @@ -31,7 +31,8 @@ "default": 16, "max": 20 }, - "enableLang": true + "enableLang": true, + "enableLangForceDisable": false } } } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java index 8a274ca..b2d6703 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java @@ -134,27 +134,4 @@ public class Respackopts implements ClientModInitializer { } } } - - public static boolean matchStringCondition(String condition) throws SyntaxError { - if (condition == null) { - throw new SyntaxError("Condition must not be null"); - } - if (!condition.contains(":")) { - throw new SyntaxError("You must include you resource pack ID in conditions (format: pack:some.key)"); - } - AtomicBoolean found = new AtomicBoolean(false); - AtomicBoolean output = new AtomicBoolean(false); - Respackopts.resPackMetas.forEach((r, v) -> { - String sourcePack = condition.split(":")[0]; - String name = condition.substring(condition.indexOf(':') + 1); - if (Objects.equals(v.id, sourcePack)) { - found.set(true); - output.set(Respackopts.boolVals.get(sourcePack).get(name)); - } - }); - if (!found.get()) { - throw new SyntaxError("Could not find pack with specified ID"); - } - return output.get(); - } } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/AndCondition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/AndCondition.java new file mode 100644 index 0000000..196ddb7 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/AndCondition.java @@ -0,0 +1,28 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class AndCondition implements Condition { + @Override + public boolean evaluate(JsonElement node) throws SyntaxError { + if (!node.isJsonArray()) + throw new SyntaxError("\"and\" condition requires an array of conditions"); + for (JsonElement jsonElement : node.getAsJsonArray()) { + if (!ConditionEvaluator.evaluate(jsonElement)) + return false; + } + return true; + } + + @Override + public Set getKeys() { + Set strings = new LinkedHashSet<>(); + strings.add("add"); + strings.add("&"); + strings.add("conditions"); // This is also the root condition + return strings; + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/Condition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/Condition.java new file mode 100644 index 0000000..92c0819 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/Condition.java @@ -0,0 +1,10 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.Set; + +public interface Condition { + boolean evaluate(JsonElement node) throws SyntaxError; + Set getKeys(); +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/ConditionEvaluator.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/ConditionEvaluator.java new file mode 100644 index 0000000..0b9baf4 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/ConditionEvaluator.java @@ -0,0 +1,61 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; +import io.gitlab.jfronny.respackopts.Respackopts; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ConditionEvaluator { + private static Set conditions; + static { + conditions = new LinkedHashSet<>(); + conditions.add(new AndCondition()); + conditions.add(new OrCondition()); + conditions.add(new XorCondition()); + conditions.add(new EqualityCondition()); + conditions.add(new NorCondition()); + } + + public static boolean evaluate(JsonElement condition) throws SyntaxError { + if (condition.isJsonPrimitive() && condition.getAsJsonPrimitive().isString()) + return evaluate(condition.getAsString()); + if (condition.isJsonObject() && condition.getAsJsonObject().size() == 1) { + for (Map.Entry entry : condition.getAsJsonObject().entrySet()) { + for (Condition c : conditions) { + if (c.getKeys().contains(entry.getKey())) { + return c.evaluate(entry.getValue()); + } + } + throw new SyntaxError("Could not find condition: " + entry.getKey()); + } + } + throw new SyntaxError("Condition entries may only be json objects containing one key and one value or strings"); + } + + public static boolean evaluate(String condition) throws SyntaxError { + if (condition == null) { + throw new SyntaxError("Condition must not be null"); + } + if (!condition.contains(":")) { + throw new SyntaxError("You must include you resource pack ID in conditions (format: pack:some.key)"); + } + AtomicBoolean found = new AtomicBoolean(false); + AtomicBoolean output = new AtomicBoolean(false); + Respackopts.resPackMetas.forEach((r, v) -> { + String sourcePack = condition.split(":")[0]; + String name = condition.substring(condition.indexOf(':') + 1); + if (Objects.equals(v.id, sourcePack)) { + found.set(true); + output.set(Respackopts.boolVals.get(sourcePack).get(name)); + } + }); + if (!found.get()) { + throw new SyntaxError("Could not find pack with specified ID"); + } + return output.get(); + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/EqualityCondition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/EqualityCondition.java new file mode 100644 index 0000000..30f5e60 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/EqualityCondition.java @@ -0,0 +1,34 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +//TODO implement equality checking for string/number variables +public class EqualityCondition implements Condition { + @Override + public boolean evaluate(JsonElement node) throws SyntaxError { + if (!node.isJsonArray()) + throw new SyntaxError("\"equal\" condition requires an array of conditions"); + Optional v = Optional.empty(); + for (JsonElement jsonElement : node.getAsJsonArray()) { + boolean current = ConditionEvaluator.evaluate(jsonElement); + if (!v.isPresent()) + v = Optional.of(current); + if (current != v.get()) + return false; + } + return true; + } + + @Override + public Set getKeys() { + Set strings = new LinkedHashSet<>(); + strings.add("=="); + strings.add("="); + strings.add("equal"); + strings.add("eq"); + return strings; + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/NorCondition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/NorCondition.java new file mode 100644 index 0000000..4011eaa --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/NorCondition.java @@ -0,0 +1,28 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class NorCondition implements Condition { + @Override + public boolean evaluate(JsonElement node) throws SyntaxError { + if (!node.isJsonArray()) + throw new SyntaxError("\"not\"/\"nor\" condition requires an array of conditions"); + for (JsonElement jsonElement : node.getAsJsonArray()) { + if (ConditionEvaluator.evaluate(jsonElement)) + return false; + } + return true; + } + + @Override + public Set getKeys() { + Set strings = new LinkedHashSet<>(); + strings.add("not"); + strings.add("nor"); + strings.add("!"); + return strings; + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/OrCondition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/OrCondition.java new file mode 100644 index 0000000..f3e575e --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/OrCondition.java @@ -0,0 +1,27 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class OrCondition implements Condition { + @Override + public boolean evaluate(JsonElement node) throws SyntaxError { + if (!node.isJsonArray()) + throw new SyntaxError("\"or\" condition requires an array of conditions"); + for (JsonElement jsonElement : node.getAsJsonArray()) { + if (ConditionEvaluator.evaluate(jsonElement)) + return true; + } + return false; + } + + @Override + public Set getKeys() { + Set strings = new LinkedHashSet<>(); + strings.add("or"); + strings.add("|"); + return strings; + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/ResourcePackFilter.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/ResourcePackFilter.java index 6766d2e..673e124 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/conditions/ResourcePackFilter.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/ResourcePackFilter.java @@ -1,7 +1,7 @@ package io.gitlab.jfronny.respackopts.conditions; +import com.google.gson.JsonElement; import io.gitlab.jfronny.respackopts.Respackopts; -import io.gitlab.jfronny.respackopts.data.RpoResourceEntry; import java.io.InputStream; import java.io.InputStreamReader; @@ -23,18 +23,7 @@ public class ResourcePackFilter { if (!containsFileBase.test(name + Respackopts.fileExtension)) return true; try (InputStream stream = openFileBase.apply(name + Respackopts.fileExtension); Reader w = new InputStreamReader(stream)) { - RpoResourceEntry entry = Respackopts.g.fromJson(w, RpoResourceEntry.class); - if (entry.conditions != null) { - for (String condition : entry.conditions) { - if (!Respackopts.matchStringCondition(condition)) - return false; - } - return true; - } - else { - Respackopts.logger.error("Conditions null for " + name); - return true; - } + return ConditionEvaluator.evaluate(Respackopts.g.fromJson(w, JsonElement.class)); } catch (Throwable e) { e.printStackTrace(); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/conditions/XorCondition.java b/src/main/java/io/gitlab/jfronny/respackopts/conditions/XorCondition.java new file mode 100644 index 0000000..254b960 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/conditions/XorCondition.java @@ -0,0 +1,28 @@ +package io.gitlab.jfronny.respackopts.conditions; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class XorCondition implements Condition { + @Override + public boolean evaluate(JsonElement node) throws SyntaxError { + if (!node.isJsonArray()) + throw new SyntaxError("\"xor\" condition requires an array of conditions"); + boolean bl = false; + for (JsonElement jsonElement : node.getAsJsonArray()) { + if (ConditionEvaluator.evaluate(jsonElement)) + bl = !bl; + } + return bl; + } + + @Override + public Set getKeys() { + Set strings = new LinkedHashSet<>(); + strings.add("^"); + strings.add("xor"); + return strings; + } +} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/data/RpoResourceEntry.java b/src/main/java/io/gitlab/jfronny/respackopts/data/RpoResourceEntry.java deleted file mode 100644 index 241096a..0000000 --- a/src/main/java/io/gitlab/jfronny/respackopts/data/RpoResourceEntry.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.gitlab.jfronny.respackopts.data; - -public class RpoResourceEntry { - public String[] conditions; -} diff --git a/src/main/java/io/gitlab/jfronny/respackopts/integration/LibCDCompat.java b/src/main/java/io/gitlab/jfronny/respackopts/integration/LibCDCompat.java index 96ecf7d..9d4a00d 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/integration/LibCDCompat.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/integration/LibCDCompat.java @@ -1,9 +1,11 @@ package io.gitlab.jfronny.respackopts.integration; +import com.google.gson.JsonElement; import io.github.cottonmc.libcd.api.CDSyntaxError; import io.github.cottonmc.libcd.api.condition.ConditionManager; import io.github.cottonmc.libcd.api.init.ConditionInitializer; import io.gitlab.jfronny.respackopts.Respackopts; +import io.gitlab.jfronny.respackopts.conditions.ConditionEvaluator; import io.gitlab.jfronny.respackopts.conditions.SyntaxError; import net.minecraft.util.Identifier; @@ -11,15 +13,22 @@ public class LibCDCompat implements ConditionInitializer { @Override public void initConditions(ConditionManager conditionManager) { conditionManager.registerCondition(new Identifier(Respackopts.ID, "cfg"), q -> { - if (!(q instanceof String)) { - throw new CDSyntaxError("Expected string"); + if (q instanceof String) { + try { + return ConditionEvaluator.evaluate((String) q); + } catch (Throwable error) { + throw new CDSyntaxError(error.getMessage()); + } } - String s = (String) q; - try { - return Respackopts.matchStringCondition(s); - } catch (SyntaxError syntaxError) { - throw new CDSyntaxError(syntaxError.getMessage()); + else if (q instanceof JsonElement) { + try { + return ConditionEvaluator.evaluate((JsonElement) q); + } catch (Throwable error) { + throw new CDSyntaxError(error.getMessage()); + } } + else + throw new CDSyntaxError("Expected Json element or string for rpo libcd conditions"); }); } }