From 790222f0b1b871e33876d29b010c80c3927ff3f2 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Fri, 8 Sep 2023 12:27:27 +0930 Subject: [PATCH 1/6] flesh out dice --- .../dicelib/expression/ExpressionParser.java | 8 + .../expression/function/GeneSysDice.java | 347 ++++++++++++++++++ .../net/rptools/maptool/client/MapTool.java | 27 ++ .../client/fonts/EotE_Symbol-Regular_v1.otf | Bin 0 -> 4076 bytes .../client/fonts/GenesysGlyphsAndDice-3.0.otf | Bin 0 -> 22580 bytes 5 files changed, 382 insertions(+) create mode 100644 src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java create mode 100644 src/main/resources/net/rptools/maptool/client/fonts/EotE_Symbol-Regular_v1.otf create mode 100644 src/main/resources/net/rptools/maptool/client/fonts/GenesysGlyphsAndDice-3.0.otf diff --git a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java index e96e0fb8ac..0914965051 100755 --- a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java +++ b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java @@ -21,6 +21,7 @@ import net.rptools.dicelib.expression.function.ExplodeDice; import net.rptools.dicelib.expression.function.ExplodingSuccessDice; import net.rptools.dicelib.expression.function.FudgeRoll; +import net.rptools.dicelib.expression.function.GeneSysDice; import net.rptools.dicelib.expression.function.HeroKillingRoll; import net.rptools.dicelib.expression.function.HeroRoll; import net.rptools.dicelib.expression.function.If; @@ -205,6 +206,12 @@ public class ExpressionParser { new String[] {"\\b[aA][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStress($1, $2)"}, new String[] {"\\b[aA][nN][sS](\\d+)\\b", "arsMagicaStressNum($1, 0)"}, new String[] {"\\b[aA][nN][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStressNum($1, $2)"}, + + // SW FFG + new String[] {"\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)\\b", "swffg('$1')"}, + + // FFG + new String[] {"\\bffg#(([bBsSaAdDpPcC]\\d+)+)\\b", "ffg('$1')"}, }; private final Parser parser; @@ -238,6 +245,7 @@ public ExpressionParser(String[][] regexpTransforms) { parser.addFunction(new DropHighestRoll()); parser.addFunction(new KeepLowestRoll()); parser.addFunction(new ArsMagicaStress()); + parser.addFunction(new GeneSysDice()); parser.addFunction(new If()); diff --git a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java new file mode 100644 index 0000000000..0df6c6629a --- /dev/null +++ b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java @@ -0,0 +1,347 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.dicelib.expression.function; + +import com.google.gson.Gson; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import net.rptools.maptool.client.ui.theme.ThemeSupport; +import net.rptools.maptool.client.ui.theme.ThemeSupport.ThemeColor; +import net.rptools.parser.Parser; +import net.rptools.parser.ParserException; +import net.rptools.parser.VariableResolver; +import net.rptools.parser.function.AbstractFunction; + +public class GeneSysDice extends AbstractFunction { + + enum ResultType { + SUCCESS(1, 0, 0, 0, 0, 0, 0, 0, "s"), + FAILURE(0, 1, 0, 0, 0, 0, 0, 0, "f"), + ADVANTAGE(0, 0, 1, 0, 0, 0, 0, 0, "a"), + THREAT(0, 0, 0, 1, 0, 0, 0, 0, "h"), + TRIUMPH(1, 0, 0, 0, 1, 0, 0, 0, "t"), + DESPAIR(0, 1, 0, 0, 0, 1, 0, 0, "d"), + LIGHT(0, 0, 0, 0, 0, 0, 1, 0, "Z"), + DARK(0, 0, 0, 0, 0, 0, 0, 1, "z"), + NONE(0, 0, 0, 0, 0, 0, 0, 0, " "), + SUCCESS_ADVANTAGE(1, 0, 1, 0, 0, 0, 0, 0, "sa"), + ADVANTAGE_ADVANTAGE(0, 0, 2, 0, 0, 0, 0, 0, "aa"), + SUCCESS_SUCCESS(2, 0, 0, 0, 0, 0, 0, 0, "ss"), + FAILURE_THREAT(0, 1, 0, 1, 0, 0, 0, 0, "fh"), + FAILURE_FAILURE(0, 2, 0, 0, 0, 0, 0, 0, "ff"), + THREAT_THREAT(0, 0, 0, 2, 0, 0, 0, 0, "hh"), + LIGHT_LIGHT(0, 0, 0, 0, 0, 0, 2, 0, "ZZ"), + DARK_DARK(0, 0, 0, 0, 0, 0, 0, 2, "zz"); + + private final int success; + private final int failure; + private final int advantage; + private final int threat; + private final int triumph; + private final int despair; + private final int light; + private final int dark; + + private final String fontCharacters; + + ResultType( + int success, + int failure, + int advantage, + int threat, + int triumph, + int despair, + int light, + int dark, + String fontCharacters) { + this.success = success; + this.failure = failure; + this.advantage = advantage; + this.threat = threat; + this.triumph = triumph; + this.despair = despair; + this.light = light; + this.dark = dark; + this.fontCharacters = fontCharacters; + } + + public int getSuccess() { + return success; + } + + public int getFailure() { + return failure; + } + + public int getAdvantage() { + return advantage; + } + + public int getThreat() { + return threat; + } + + public int getTriumph() { + return triumph; + } + + public int getDespair() { + return despair; + } + + public int getLight() { + return light; + } + + public int getDark() { + return dark; + } + } + + private enum DiceType { + BOOST( + "b", + List.of( + ResultType.NONE, + ResultType.NONE, + ResultType.SUCCESS, + ResultType.SUCCESS_ADVANTAGE, + ResultType.ADVANTAGE_ADVANTAGE, + ResultType.ADVANTAGE)), + + SETBACK( + "s", + List.of( + ResultType.NONE, + ResultType.NONE, + ResultType.FAILURE, + ResultType.FAILURE, + ResultType.THREAT, + ResultType.THREAT)), + ABILITY( + "a", + List.of( + ResultType.NONE, + ResultType.SUCCESS, + ResultType.SUCCESS, + ResultType.SUCCESS_SUCCESS, + ResultType.ADVANTAGE, + ResultType.ADVANTAGE, + ResultType.SUCCESS_ADVANTAGE, + ResultType.ADVANTAGE_ADVANTAGE)), + DIFFICULTY( + "d", + List.of( + ResultType.NONE, + ResultType.FAILURE, + ResultType.FAILURE_FAILURE, + ResultType.THREAT, + ResultType.THREAT, + ResultType.THREAT, + ResultType.THREAT_THREAT, + ResultType.FAILURE_THREAT)), + PROFICIENCY( + "p", + List.of( + ResultType.NONE, + ResultType.SUCCESS, + ResultType.SUCCESS, + ResultType.SUCCESS_SUCCESS, + ResultType.SUCCESS_SUCCESS, + ResultType.ADVANTAGE, + ResultType.SUCCESS_ADVANTAGE, + ResultType.SUCCESS_ADVANTAGE, + ResultType.SUCCESS_ADVANTAGE, + ResultType.ADVANTAGE_ADVANTAGE, + ResultType.ADVANTAGE_ADVANTAGE, + ResultType.TRIUMPH)), + CHALLENGE( + "c", + List.of( + ResultType.NONE, + ResultType.FAILURE, + ResultType.FAILURE, + ResultType.FAILURE_FAILURE, + ResultType.FAILURE_FAILURE, + ResultType.THREAT, + ResultType.THREAT, + ResultType.FAILURE_THREAT, + ResultType.FAILURE_THREAT, + ResultType.THREAT_THREAT, + ResultType.THREAT_THREAT, + ResultType.DESPAIR)), + FORCE( + "f", + List.of( + ResultType.DARK, + ResultType.DARK, + ResultType.DARK, + ResultType.DARK, + ResultType.DARK, + ResultType.DARK, + ResultType.DARK_DARK, + ResultType.LIGHT, + ResultType.LIGHT, + ResultType.LIGHT_LIGHT, + ResultType.LIGHT_LIGHT, + ResultType.LIGHT_LIGHT)); + + private final List sides; + private final String diePattern; + + DiceType(String diePattern, List sides) { + this.sides = sides; + this.diePattern = diePattern; + } + + public String getDiePattern() { + return diePattern; + } + + public int getSides() { + return sides.size(); + } + + public ResultType roll() { + return getSide(DiceHelper.rollDice(1, sides.size()) - 1); + } + + public ResultType getSide(int side) { + return sides.get(side); + } + ; + } + + private static final Map FFG_FONT_NAME_MAP = + Map.of("swffg", "EotE Symbol", "ffg", "Genesys Glyphs and Dice"); + + private static final String BOOST_DIE_PATTERN_NAME = "boost"; + private static final String SETBACK_DIE_PATTERN_NAME = "setback"; + private static final String ABILITY_DIE_PATTERN_NAME = "ability"; + private static final String DIFFICULTY_DIE_PATTERN_NAME = "difficulty"; + private static final String PROFICIENCY_DIE_PATTERN_NAME = "proficiency"; + private static final String CHALLENGE_DIE_PATTERN_NAME = "challenge"; + private static final String FORCE_DIE_PATTERN_NAME = "force"; + + public GeneSysDice() { + super(1, 1, false, "swffg", "ffg"); + } + + @Override + public Object childEvaluate( + Parser parser, VariableResolver resolver, String functionName, List parameters) + throws ParserException { + + var diceString = parameters.get(0).toString().toLowerCase(); + + var pattern = Pattern.compile("(\\S)(\\d+)"); + var matcher = pattern.matcher(diceString); + var results = new ArrayList(); + var individualResults = new HashMap>(); + + while (matcher.find()) { + var dieType = matcher.group(1); + var dieCount = Integer.parseInt(matcher.group(2)); + var die = + Arrays.stream(DiceType.values()) + .filter(dt -> dt.diePattern.equalsIgnoreCase(dieType)) + .findFirst() + .get(); + for (int i = 0; i < dieCount; i++) { + var roll = die.roll(); + results.add(roll); + var dieName = die.name().toLowerCase(); + individualResults.putIfAbsent(dieName, new ArrayList<>()); + individualResults.get(dieName).add(roll.name().toLowerCase()); + } + } + + var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); + + var sb = new StringBuilder(); + sb.append(""); + for (var result : results) { + sb.append(result.fontCharacters); + } + sb.append(""); + + // Set variable with result + var resultMap = new HashMap(); + for (var result : results) { + var resultString = result.toString().toLowerCase(); + resultMap.put(resultString, resultMap.getOrDefault(resultString, 0) + 1); + } + + int successCount = 0; + int failureCount = 0; + int advantageCount = 0; + int threatCount = 0; + int triumphCount = 0; + int despairCount = 0; + int lightCount = 0; + int darkCount = 0; + + for (var result : results) { + successCount += result.getSuccess(); + failureCount += result.getFailure(); + advantageCount += result.getAdvantage(); + threatCount += result.getThreat(); + triumphCount += result.getTriumph(); + despairCount += result.getDespair(); + lightCount += result.getLight(); + darkCount += result.getDark(); + } + + var counters = new HashMap(); + counters.put("success", successCount); + counters.put("failure", failureCount); + counters.put("advantage", advantageCount); + counters.put("threat", threatCount); + counters.put("triumph", triumphCount); + counters.put("despair", despairCount); + if (lightCount > 0) { + counters.put("light", lightCount); + } + if (darkCount > 0) { + counters.put("dark", darkCount); + } + + var result = new HashMap(); + result.put("success", successCount - failureCount); + result.put("advantage", advantageCount - threatCount); + result.put("triumph", triumphCount); + result.put("despair", despairCount); + if (lightCount > 0 || darkCount > 0) { + result.put("force", lightCount - darkCount); + } + + var gson = new Gson().toJsonTree(resultMap).getAsJsonObject(); + gson.add("rolls", new Gson().toJsonTree(individualResults).getAsJsonObject()); + gson.add("counters", new Gson().toJsonTree(counters).getAsJsonObject()); + gson.add("result", new Gson().toJsonTree(result).getAsJsonObject()); + + resolver.setVariable("lastFFGResult", gson); + + return sb.toString(); + } +} diff --git a/src/main/java/net/rptools/maptool/client/MapTool.java b/src/main/java/net/rptools/maptool/client/MapTool.java index e740218d23..eb58d8e464 100644 --- a/src/main/java/net/rptools/maptool/client/MapTool.java +++ b/src/main/java/net/rptools/maptool/client/MapTool.java @@ -1804,6 +1804,33 @@ public static void main(String[] args) { System.exit(1); } + /* + * Load GenSys and SW RPG fonts + */ + try { + var genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); + var genFont = + Font.createFont( + Font.TRUETYPE_FONT, + Objects.requireNonNull( + MapTool.class + .getClassLoader() + .getResourceAsStream( + "net/rptools/maptool/client/fonts/GenesysGlyphsAndDice-3.0.otf"))); + genv.registerFont(genFont); + var swGenFont = + Font.createFont( + Font.TRUETYPE_FONT, + Objects.requireNonNull( + MapTool.class + .getClassLoader() + .getResourceAsStream( + "net/rptools/maptool/client/fonts/EotE_Symbol-Regular_v1.otf"))); + genv.registerFont(swGenFont); + } catch (Exception e) { + log.error("msg.error.genesysFont", e); + } + /** * This is a tweak that makes the Chinese version work better. * diff --git a/src/main/resources/net/rptools/maptool/client/fonts/EotE_Symbol-Regular_v1.otf b/src/main/resources/net/rptools/maptool/client/fonts/EotE_Symbol-Regular_v1.otf new file mode 100644 index 0000000000000000000000000000000000000000..ea1ebfcc76bedd719ef1d9f2b91ef5ff0911f259 GIT binary patch literal 4076 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 Ad;kCd literal 0 HcmV?d00001 From d1b0bf2e0de021dfbea00a6ad4e95d8d344b0b98 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Fri, 8 Sep 2023 23:18:43 +0930 Subject: [PATCH 2/6] add roll expression for retreiving results in different formats --- .../dicelib/expression/ExpressionParser.java | 16 +- .../expression/function/GeneSysDice.java | 333 ++++++++++++++++-- 2 files changed, 319 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java index 0914965051..fd58800b30 100755 --- a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java +++ b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java @@ -207,11 +207,17 @@ public class ExpressionParser { new String[] {"\\b[aA][nN][sS](\\d+)\\b", "arsMagicaStressNum($1, 0)"}, new String[] {"\\b[aA][nN][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStressNum($1, $2)"}, - // SW FFG - new String[] {"\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)\\b", "swffg('$1')"}, - - // FFG - new String[] {"\\bffg#(([bBsSaAdDpPcC]\\d+)+)\\b", "ffg('$1')"}, + // SW Genesys + new String[] {"\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)\\b", "swgenesys('$1')"}, + new String[] {"\\bsw#details\\b", "swgenesysLastDetails()"}, + new String[] {"\\bsw#rolls\\b", "swgenesysLastRolls()"}, + new String[] {"\\bsw#grouped\\b", "swgenesysLastGrouped()"}, + + // Genesys + new String[] {"\\bgs#(([bBsSaAdDpPcC]\\d+)+)\\b", "genesys('$1')"}, + new String[] {"\\bgs#details\\b", "genesysLastDetails()"}, + new String[] {"\\bgs#rolls\\b", "genesysLastRolls()"}, + new String[] {"\\bgs#grouped\\b", "genesysLastGrouped()"}, }; private final Parser parser; diff --git a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java index 0df6c6629a..1836af56c7 100644 --- a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java +++ b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java @@ -15,12 +15,15 @@ package net.rptools.dicelib.expression.function; import com.google.gson.Gson; +import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import java.util.stream.StreamSupport; import net.rptools.maptool.client.ui.theme.ThemeSupport; import net.rptools.maptool.client.ui.theme.ThemeSupport.ThemeColor; import net.rptools.parser.Parser; @@ -28,38 +31,79 @@ import net.rptools.parser.VariableResolver; import net.rptools.parser.function.AbstractFunction; +/** This class implements the dice rolling functions for genesys / starwars genesys systems. */ public class GeneSysDice extends AbstractFunction { + /** Enumeration of the possible results of a die roll. */ enum ResultType { + /** A single success. */ SUCCESS(1, 0, 0, 0, 0, 0, 0, 0, "s"), + /** A single failure. */ FAILURE(0, 1, 0, 0, 0, 0, 0, 0, "f"), + /** A single advantage. */ ADVANTAGE(0, 0, 1, 0, 0, 0, 0, 0, "a"), + /** A single threat. */ THREAT(0, 0, 0, 1, 0, 0, 0, 0, "h"), + /** A single triumph. */ TRIUMPH(1, 0, 0, 0, 1, 0, 0, 0, "t"), + /** A single despair. */ DESPAIR(0, 1, 0, 0, 0, 1, 0, 0, "d"), + /* A single light side point. */ LIGHT(0, 0, 0, 0, 0, 0, 1, 0, "Z"), + /** A single dark side point. */ DARK(0, 0, 0, 0, 0, 0, 0, 1, "z"), + /** No result. */ NONE(0, 0, 0, 0, 0, 0, 0, 0, " "), + /** A single success and a single advantage. */ SUCCESS_ADVANTAGE(1, 0, 1, 0, 0, 0, 0, 0, "sa"), + /** Two Advantages. */ ADVANTAGE_ADVANTAGE(0, 0, 2, 0, 0, 0, 0, 0, "aa"), + /** Two Successes. */ SUCCESS_SUCCESS(2, 0, 0, 0, 0, 0, 0, 0, "ss"), + /* A single failure and a single threat. */ FAILURE_THREAT(0, 1, 0, 1, 0, 0, 0, 0, "fh"), + /** Two Failures. */ FAILURE_FAILURE(0, 2, 0, 0, 0, 0, 0, 0, "ff"), + /** Two Threats. */ THREAT_THREAT(0, 0, 0, 2, 0, 0, 0, 0, "hh"), + /** Two Light Side Points. */ LIGHT_LIGHT(0, 0, 0, 0, 0, 0, 2, 0, "ZZ"), + /** Two Dark Side Points. */ DARK_DARK(0, 0, 0, 0, 0, 0, 0, 2, "zz"); + /** The number of successes this result represents. */ private final int success; + /** The number of failures this result represents. */ private final int failure; + /** The number of advantages this result represents. */ private final int advantage; + /** The number of threats this result represents. */ private final int threat; + /** The number of triumphs this result represents. */ private final int triumph; + /** The number of despairs this result represents. */ private final int despair; + /** The number of light side points this result represents. */ private final int light; + /** The number of dark side points this result represents. */ private final int dark; + /** The font characters that represent this result. */ private final String fontCharacters; + /** + * Constructor. + * + * @param success the number of successes this result represents. + * @param failure the number of failures this result represents. + * @param advantage the number of advantages this result represents. + * @param threat the number of threats this result represents. + * @param triumph the number of triumphs this result represents. + * @param despair the number of despairs this result represents. + * @param light the number of light side points this result represents. + * @param dark the number of dark side points this result represents. + * @param fontCharacters the font characters that represent this result. + */ ResultType( int success, int failure, @@ -81,42 +125,85 @@ enum ResultType { this.fontCharacters = fontCharacters; } + /** + * Get the number of successes this result represents. + * + * @return the number of successes this result represents. + */ public int getSuccess() { return success; } + /** + * Get the number of failures this result represents. + * + * @return the number of failures this result represents. + */ public int getFailure() { return failure; } + /** + * Get the number of advantages this result represents. + * + * @return the number of advantages this result represents. + */ public int getAdvantage() { return advantage; } + /** + * Get the number of threats this result represents. + * + * @return the number of threats this result represents. + */ public int getThreat() { return threat; } + /** + * Get the number of triumphs this result represents. + * + * @return the number of triumphs this result represents. + */ public int getTriumph() { return triumph; } + /** + * Get the number of despairs this result represents. + * + * @return the number of despairs this result represents. + */ public int getDespair() { return despair; } + /** + * Get the number of light side points this result represents. + * + * @return the number of light side points this result represents. + */ public int getLight() { return light; } + /** + * Get the number of dark side points this result represents. + * + * @return the number of dark side points this result represents. + */ public int getDark() { return dark; } } + /** Enumeration of the possible dice types. */ private enum DiceType { + /** The boost die. */ BOOST( "b", + 0, List.of( ResultType.NONE, ResultType.NONE, @@ -125,8 +212,10 @@ private enum DiceType { ResultType.ADVANTAGE_ADVANTAGE, ResultType.ADVANTAGE)), + /** The setback die. */ SETBACK( "s", + 1, List.of( ResultType.NONE, ResultType.NONE, @@ -134,8 +223,10 @@ private enum DiceType { ResultType.FAILURE, ResultType.THREAT, ResultType.THREAT)), + /** The ability die. */ ABILITY( "a", + 2, List.of( ResultType.NONE, ResultType.SUCCESS, @@ -145,8 +236,10 @@ private enum DiceType { ResultType.ADVANTAGE, ResultType.SUCCESS_ADVANTAGE, ResultType.ADVANTAGE_ADVANTAGE)), + /** The difficulty die. */ DIFFICULTY( "d", + 3, List.of( ResultType.NONE, ResultType.FAILURE, @@ -156,8 +249,10 @@ private enum DiceType { ResultType.THREAT, ResultType.THREAT_THREAT, ResultType.FAILURE_THREAT)), + /** The proficiency die. */ PROFICIENCY( "p", + 4, List.of( ResultType.NONE, ResultType.SUCCESS, @@ -171,8 +266,10 @@ private enum DiceType { ResultType.ADVANTAGE_ADVANTAGE, ResultType.ADVANTAGE_ADVANTAGE, ResultType.TRIUMPH)), + /* The challenge die. */ CHALLENGE( "c", + 5, List.of( ResultType.NONE, ResultType.FAILURE, @@ -186,8 +283,10 @@ private enum DiceType { ResultType.THREAT_THREAT, ResultType.THREAT_THREAT, ResultType.DESPAIR)), + /** The force die. */ FORCE( "f", + 6, List.of( ResultType.DARK, ResultType.DARK, @@ -202,45 +301,97 @@ private enum DiceType { ResultType.LIGHT_LIGHT, ResultType.LIGHT_LIGHT)); + /** The sides of the die. */ private final List sides; + + /** The pattern used to represent this die. */ private final String diePattern; - DiceType(String diePattern, List sides) { + /** The sort order of the die when grouped, */ + private final int groupSort; + + /** + * Constructor. + * + * @param diePattern the pattern used to represent this die. + * @param groupSort the sort order of the die when grouped. + * @param sides the sides of the die. + */ + DiceType(String diePattern, int groupSort, List sides) { this.sides = sides; + this.groupSort = groupSort; this.diePattern = diePattern; } + /** + * Get the pattern used to represent this die. + * + * @return the pattern used to represent this die. + */ public String getDiePattern() { return diePattern; } + /** + * Get the number of sides on this die. + * + * @return the number of sides on this die. + */ public int getSides() { return sides.size(); } + /** + * Roll the die. + * + * @return the result of the roll. + */ public ResultType roll() { return getSide(DiceHelper.rollDice(1, sides.size()) - 1); } + /** + * Get the result of a roll of the die. + * + * @param side the side of the die to get the result for. + * @return the result of the roll. + */ public ResultType getSide(int side) { return sides.get(side); } - ; + + /** + * Get the sort order of the die when grouped. + * + * @return the sort order of the die when grouped. + */ + public int getGroupSort() { + return groupSort; + } } - private static final Map FFG_FONT_NAME_MAP = - Map.of("swffg", "EotE Symbol", "ffg", "Genesys Glyphs and Dice"); + /** Map of font names for the different systems. */ + private static final Map GS_FONT_NAME_MAP = + Map.of("swgenesys", "EotE Symbol", "genesys", "Genesys Glyphs and Dice"); - private static final String BOOST_DIE_PATTERN_NAME = "boost"; - private static final String SETBACK_DIE_PATTERN_NAME = "setback"; - private static final String ABILITY_DIE_PATTERN_NAME = "ability"; - private static final String DIFFICULTY_DIE_PATTERN_NAME = "difficulty"; - private static final String PROFICIENCY_DIE_PATTERN_NAME = "proficiency"; - private static final String CHALLENGE_DIE_PATTERN_NAME = "challenge"; - private static final String FORCE_DIE_PATTERN_NAME = "force"; + /** Map of variable names for the different systems. */ + private static final Map ROLL_VARIABLE_MAP = + Map.of("swgenesys", "#lastSWResult", "genesys", "#lastGSResult"); + /** Constructor. */ public GeneSysDice() { - super(1, 1, false, "swffg", "ffg"); + super( + 0, + 1, + false, + "swgenesys", + "genesys", + "swgenesyslastdetails", + "genesyslastdetails", + "swgenesyslastrolls", + "genesyslastrolls", + "swgenesyslastgrouped", + "genesyslastgrouped"); } @Override @@ -248,6 +399,126 @@ public Object childEvaluate( Parser parser, VariableResolver resolver, String functionName, List parameters) throws ParserException { + return switch (functionName.toLowerCase()) { + case "swgenesys", "genesys" -> performRoll(functionName, resolver, parameters); + case "swgenesyslastdetails", "genesyslastdetails" -> returnDetails( + functionName.toLowerCase().replace("lastdetails", ""), resolver); + case "swgenesyslastrolls", "genesyslastrolls" -> renderRolls( + functionName.toLowerCase().replace("lastrolls", ""), resolver); + case "swgenesyslastgrouped", "genesyslastgrouped" -> renderGrouped( + functionName.toLowerCase().replace("lastgrouped", ""), resolver); + default -> // Should never happen + throw new ParserException("Invalid function name: " + functionName); + }; + } + + /** + * Render the results of a roll. + * + * @param functionName the name of the function. + * @param resolver the variable resolver. + * @return the rendered results. + * @throws ParserException if an error occurs. + */ + private String renderGrouped(String functionName, VariableResolver resolver) + throws ParserException { + var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); + var rollsMap = new HashMap>(); + for (var entry : rollResults.get("rolls").getAsJsonObject().entrySet()) { + var dieName = entry.getKey(); + var rolls = + StreamSupport.stream(entry.getValue().getAsJsonArray().spliterator(), false) + .map(r -> r.getAsString().toLowerCase()) + .toList(); + rollsMap.put(dieName, rolls); + } + return renderGrouped(functionName, rollsMap); + } + + /** + * Render the results of a roll to a string. + * + * @param functionName the name of the function. + * @param rolls the rolls to render. + * @return the rendered results. + */ + private String renderGrouped(String functionName, Map> rolls) { + var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); + var sb = new StringBuilder(); + sb.append(""); + var dieTypes = + Arrays.stream(DiceType.values()) + .sorted(Comparator.comparingInt(DiceType::getGroupSort)) + .toList(); + boolean first = true; + for (var dt : dieTypes) { + var dieName = dt.name().toLowerCase(); + if (rolls.containsKey(dieName)) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(dieName).append(": "); + var dieRolls = + rolls.get(dieName).stream().map(r -> ResultType.valueOf(r.toUpperCase())).toList(); + sb.append(renderRolls(functionName, dieRolls)); + } + sb.append(""); + } + return sb.toString(); + } + + /** + * Render the results of a roll to a string. + * + * @param functionName the name of the function. + * @param resolver the variable resolver. + * @return the rendered results. + * @throws ParserException if an error occurs. + */ + private String renderRolls(String functionName, VariableResolver resolver) + throws ParserException { + var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); + var rolls = + StreamSupport.stream( + rollResults + .get("rolls") + .getAsJsonObject() + .get("all") + .getAsJsonArray() + .spliterator(), + false) + .map(r -> ResultType.valueOf(r.getAsString().toUpperCase())) + .toList(); + return renderRolls(functionName, rolls); + } + + /** + * Return the details of the last roll. + * + * @param functionName the name of the function. + * @param resolver the variable resolver. + * @return the details of the last roll. + * @throws ParserException if an error occurs. + */ + private JsonObject returnDetails(String functionName, VariableResolver resolver) + throws ParserException { + return (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); + } + + /** + * Perform a roll. + * + * @param functionName the name of the function. + * @param resolver the variable resolver. + * @param parameters the parameters to the function. + * @return the rendered results. + * @throws ParserException if an error occurs. + */ + private String performRoll( + String functionName, VariableResolver resolver, List parameters) + throws ParserException { + var diceString = parameters.get(0).toString().toLowerCase(); var pattern = Pattern.compile("(\\S)(\\d+)"); @@ -272,18 +543,7 @@ public Object childEvaluate( } } - var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); - - var sb = new StringBuilder(); - sb.append(""); - for (var result : results) { - sb.append(result.fontCharacters); - } - sb.append(""); + individualResults.put("all", results.stream().map(r -> r.name().toLowerCase()).toList()); // Set variable with result var resultMap = new HashMap(); @@ -340,8 +600,31 @@ public Object childEvaluate( gson.add("counters", new Gson().toJsonTree(counters).getAsJsonObject()); gson.add("result", new Gson().toJsonTree(result).getAsJsonObject()); - resolver.setVariable("lastFFGResult", gson); + resolver.setVariable(ROLL_VARIABLE_MAP.get(functionName), gson); + return renderRolls(functionName, results); + } + + /** + * Render the results of a roll to a string. + * + * @param functionName the name of the function. + * @param results the results to render. + * @return the rendered results. + */ + private String renderRolls(String functionName, List results) { + var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); + + var sb = new StringBuilder(); + sb.append(""); + for (var result : results) { + sb.append(result.fontCharacters); + } + sb.append(""); return sb.toString(); } } From 152662217207d294b62cd1e541b2d5c881b9516f Mon Sep 17 00:00:00 2001 From: cwisniew Date: Sat, 9 Sep 2023 23:07:32 +0930 Subject: [PATCH 3/6] implement formatting options --- .../dicelib/expression/ExpressionParser.java | 16 +- .../expression/function/GeneSysDice.java | 222 ++++++++++++++---- 2 files changed, 181 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java index fd58800b30..84f45dc425 100755 --- a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java +++ b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java @@ -208,16 +208,16 @@ public class ExpressionParser { new String[] {"\\b[aA][nN][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStressNum($1, $2)"}, // SW Genesys - new String[] {"\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)\\b", "swgenesys('$1')"}, - new String[] {"\\bsw#details\\b", "swgenesysLastDetails()"}, - new String[] {"\\bsw#rolls\\b", "swgenesysLastRolls()"}, - new String[] {"\\bsw#grouped\\b", "swgenesysLastGrouped()"}, + new String[] { + "\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)(\\.(([eEgGdDjJ])+))?\\b", "swgenesys('$1', '$4')" + }, + new String[] {"\\bsw#last(\\.(([eEgGdDjJ])+))*\\b", "swgenesysLast('last', '$2')"}, // Genesys - new String[] {"\\bgs#(([bBsSaAdDpPcC]\\d+)+)\\b", "genesys('$1')"}, - new String[] {"\\bgs#details\\b", "genesysLastDetails()"}, - new String[] {"\\bgs#rolls\\b", "genesysLastRolls()"}, - new String[] {"\\bgs#grouped\\b", "genesysLastGrouped()"}, + new String[] { + "\\bgs#(([bBsSaAdDpPcCjJ]\\d+)+)(\\.(([eEgGdD])+))?\\b", "genesys('$1', " + "'$4')" + }, + new String[] {"\\bgs#last(\\.(([eEgGdDj])+))*\\b", "genesysLast('last', '$2')"} }; private final Parser parser; diff --git a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java index 1836af56c7..0283dd085b 100644 --- a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java +++ b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java @@ -20,8 +20,10 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.StreamSupport; import net.rptools.maptool.client.ui.theme.ThemeSupport; @@ -37,39 +39,39 @@ public class GeneSysDice extends AbstractFunction { /** Enumeration of the possible results of a die roll. */ enum ResultType { /** A single success. */ - SUCCESS(1, 0, 0, 0, 0, 0, 0, 0, "s"), + SUCCESS(1, 0, 0, 0, 0, 0, 0, 0, "s", 1), /** A single failure. */ - FAILURE(0, 1, 0, 0, 0, 0, 0, 0, "f"), + FAILURE(0, 1, 0, 0, 0, 0, 0, 0, "f", 2), /** A single advantage. */ - ADVANTAGE(0, 0, 1, 0, 0, 0, 0, 0, "a"), + ADVANTAGE(0, 0, 1, 0, 0, 0, 0, 0, "a", 3), /** A single threat. */ - THREAT(0, 0, 0, 1, 0, 0, 0, 0, "h"), + THREAT(0, 0, 0, 1, 0, 0, 0, 0, "h", 4), /** A single triumph. */ - TRIUMPH(1, 0, 0, 0, 1, 0, 0, 0, "t"), + TRIUMPH(1, 0, 0, 0, 1, 0, 0, 0, "t", 5), /** A single despair. */ - DESPAIR(0, 1, 0, 0, 0, 1, 0, 0, "d"), + DESPAIR(0, 1, 0, 0, 0, 1, 0, 0, "d", 6), /* A single light side point. */ - LIGHT(0, 0, 0, 0, 0, 0, 1, 0, "Z"), + LIGHT(0, 0, 0, 0, 0, 0, 1, 0, "Z", 7), /** A single dark side point. */ - DARK(0, 0, 0, 0, 0, 0, 0, 1, "z"), + DARK(0, 0, 0, 0, 0, 0, 0, 1, "z", 8), /** No result. */ - NONE(0, 0, 0, 0, 0, 0, 0, 0, " "), + NONE(0, 0, 0, 0, 0, 0, 0, 0, " ", 99), /** A single success and a single advantage. */ - SUCCESS_ADVANTAGE(1, 0, 1, 0, 0, 0, 0, 0, "sa"), + SUCCESS_ADVANTAGE(1, 0, 1, 0, 0, 0, 0, 0, "sa", 99), /** Two Advantages. */ - ADVANTAGE_ADVANTAGE(0, 0, 2, 0, 0, 0, 0, 0, "aa"), + ADVANTAGE_ADVANTAGE(0, 0, 2, 0, 0, 0, 0, 0, "aa", 99), /** Two Successes. */ - SUCCESS_SUCCESS(2, 0, 0, 0, 0, 0, 0, 0, "ss"), + SUCCESS_SUCCESS(2, 0, 0, 0, 0, 0, 0, 0, "ss", 99), /* A single failure and a single threat. */ - FAILURE_THREAT(0, 1, 0, 1, 0, 0, 0, 0, "fh"), + FAILURE_THREAT(0, 1, 0, 1, 0, 0, 0, 0, "fh", 99), /** Two Failures. */ - FAILURE_FAILURE(0, 2, 0, 0, 0, 0, 0, 0, "ff"), + FAILURE_FAILURE(0, 2, 0, 0, 0, 0, 0, 0, "ff", 99), /** Two Threats. */ - THREAT_THREAT(0, 0, 0, 2, 0, 0, 0, 0, "hh"), + THREAT_THREAT(0, 0, 0, 2, 0, 0, 0, 0, "hh", 99), /** Two Light Side Points. */ - LIGHT_LIGHT(0, 0, 0, 0, 0, 0, 2, 0, "ZZ"), + LIGHT_LIGHT(0, 0, 0, 0, 0, 0, 2, 0, "ZZ", 99), /** Two Dark Side Points. */ - DARK_DARK(0, 0, 0, 0, 0, 0, 0, 2, "zz"); + DARK_DARK(0, 0, 0, 0, 0, 0, 0, 2, "zz", 99); /** The number of successes this result represents. */ private final int success; @@ -91,6 +93,9 @@ enum ResultType { /** The font characters that represent this result. */ private final String fontCharacters; + /** The sort order of the result when grouped. */ + private final int groupSort; + /** * Constructor. * @@ -103,6 +108,7 @@ enum ResultType { * @param light the number of light side points this result represents. * @param dark the number of dark side points this result represents. * @param fontCharacters the font characters that represent this result. + * @param groupSort the sort order of the result when grouped. */ ResultType( int success, @@ -113,7 +119,8 @@ enum ResultType { int despair, int light, int dark, - String fontCharacters) { + String fontCharacters, + int groupSort) { this.success = success; this.failure = failure; this.advantage = advantage; @@ -123,6 +130,7 @@ enum ResultType { this.light = light; this.dark = dark; this.fontCharacters = fontCharacters; + this.groupSort = groupSort; } /** @@ -196,6 +204,15 @@ public int getLight() { public int getDark() { return dark; } + + /** + * Get the font characters that represent this result. + * + * @return the font characters that represent this result. + */ + public int getGroupSort() { + return groupSort; + } } /** Enumeration of the possible dice types. */ @@ -370,6 +387,34 @@ public int getGroupSort() { } } + /** Enumeration of the possible options for the dice roll. */ + private enum Options { + /** Group the rolls by die type. */ + FORMAT_GROUP_DICE("d"), + /** Group the rolls by result. */ + FORMAT_GROUP_RESULTS("g"), + /** print out the rolls in expanded format. */ + EXPANDED_FORMAT("e"), + /** Return the results as JSON. */ + FORMAT_JSON("j"); + + /** The string used to represent this option. */ + private final String optionString; + + Options(String optionString) { + this.optionString = optionString; + } + + /** + * Get the string used to represent this option. + * + * @return the string used to represent this option. + */ + public String getOptionString() { + return optionString; + } + } + /** Map of font names for the different systems. */ private static final Map GS_FONT_NAME_MAP = Map.of("swgenesys", "EotE Symbol", "genesys", "Genesys Glyphs and Dice"); @@ -380,18 +425,7 @@ public int getGroupSort() { /** Constructor. */ public GeneSysDice() { - super( - 0, - 1, - false, - "swgenesys", - "genesys", - "swgenesyslastdetails", - "genesyslastdetails", - "swgenesyslastrolls", - "genesyslastrolls", - "swgenesyslastgrouped", - "genesyslastgrouped"); + super(1, 2, false, "swgenesys", "genesys", "swgenesyslast", "genesyslast"); } @Override @@ -399,28 +433,55 @@ public Object childEvaluate( Parser parser, VariableResolver resolver, String functionName, List parameters) throws ParserException { + var options = new HashSet(); + if (parameters.size() > 1) { + var optionsString = parameters.get(1).toString().toLowerCase(); + for (var option : Options.values()) { + if (optionsString.contains(option.getOptionString())) { + options.add(option); + } + } + } + return switch (functionName.toLowerCase()) { - case "swgenesys", "genesys" -> performRoll(functionName, resolver, parameters); - case "swgenesyslastdetails", "genesyslastdetails" -> returnDetails( - functionName.toLowerCase().replace("lastdetails", ""), resolver); - case "swgenesyslastrolls", "genesyslastrolls" -> renderRolls( - functionName.toLowerCase().replace("lastrolls", ""), resolver); - case "swgenesyslastgrouped", "genesyslastgrouped" -> renderGrouped( - functionName.toLowerCase().replace("lastgrouped", ""), resolver); + case "swgenesys", "genesys" -> performRoll(functionName, resolver, parameters, options); + case "swgenesyslast", "genesyslast" -> render( + functionName.toLowerCase().replace("last", ""), resolver, options); default -> // Should never happen throw new ParserException("Invalid function name: " + functionName); }; } + /** + * Render the results of the roll string saved in the variables. + * + * @param functionName the name of the function. + * @param resolver the variable resolver. + * @param options the options for the roll. + * @return the rendered results. + * @throws ParserException if an error occurs. + */ + private Object render(String functionName, VariableResolver resolver, Set options) + throws ParserException { + if (options.contains(Options.FORMAT_JSON)) { + return returnDetails(functionName, resolver); + } else if (options.contains(Options.FORMAT_GROUP_DICE)) { + return renderGrouped(functionName, resolver, options); + } else { + return renderRolls(functionName, resolver, options); + } + } + /** * Render the results of a roll. * * @param functionName the name of the function. * @param resolver the variable resolver. + * @param options the options for the roll. * @return the rendered results. * @throws ParserException if an error occurs. */ - private String renderGrouped(String functionName, VariableResolver resolver) + private String renderGrouped(String functionName, VariableResolver resolver, Set options) throws ParserException { var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); var rollsMap = new HashMap>(); @@ -432,20 +493,36 @@ private String renderGrouped(String functionName, VariableResolver resolver) .toList(); rollsMap.put(dieName, rolls); } - return renderGrouped(functionName, rollsMap); + return renderGrouped( + functionName, rollResults.get("diceString").getAsString(), rollsMap, options); } /** * Render the results of a roll to a string. * * @param functionName the name of the function. + * @param diceString the string representing the dice rolled. * @param rolls the rolls to render. + * @param options the options for the roll. * @return the rendered results. */ - private String renderGrouped(String functionName, Map> rolls) { + private String renderGrouped( + String functionName, + String diceString, + Map> rolls, + Set options) { var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); var sb = new StringBuilder(); + + // We don't want to pass the expanded format option to the renderRolls method as we deal with + // it here. + var passedOptions = new HashSet<>(options); + passedOptions.remove(Options.EXPANDED_FORMAT); + sb.append(""); + if (options.contains(Options.EXPANDED_FORMAT)) { + sb.append(diceString).append(" = "); + } var dieTypes = Arrays.stream(DiceType.values()) .sorted(Comparator.comparingInt(DiceType::getGroupSort)) @@ -461,10 +538,10 @@ private String renderGrouped(String functionName, Map> roll sb.append(dieName).append(": "); var dieRolls = rolls.get(dieName).stream().map(r -> ResultType.valueOf(r.toUpperCase())).toList(); - sb.append(renderRolls(functionName, dieRolls)); + sb.append(renderRolls(functionName, diceString, dieRolls, passedOptions)); } - sb.append(""); } + sb.append(""); return sb.toString(); } @@ -473,10 +550,11 @@ private String renderGrouped(String functionName, Map> roll * * @param functionName the name of the function. * @param resolver the variable resolver. + * @param options the options for the roll. * @return the rendered results. * @throws ParserException if an error occurs. */ - private String renderRolls(String functionName, VariableResolver resolver) + private String renderRolls(String functionName, VariableResolver resolver, Set options) throws ParserException { var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); var rolls = @@ -490,7 +568,7 @@ private String renderRolls(String functionName, VariableResolver resolver) false) .map(r -> ResultType.valueOf(r.getAsString().toUpperCase())) .toList(); - return renderRolls(functionName, rolls); + return renderRolls(functionName, rollResults.get("diceString").getAsString(), rolls, options); } /** @@ -512,11 +590,12 @@ private JsonObject returnDetails(String functionName, VariableResolver resolver) * @param functionName the name of the function. * @param resolver the variable resolver. * @param parameters the parameters to the function. + * @param options the options for the roll. * @return the rendered results. * @throws ParserException if an error occurs. */ - private String performRoll( - String functionName, VariableResolver resolver, List parameters) + private Object performRoll( + String functionName, VariableResolver resolver, List parameters, Set options) throws ParserException { var diceString = parameters.get(0).toString().toLowerCase(); @@ -599,32 +678,77 @@ private String performRoll( gson.add("rolls", new Gson().toJsonTree(individualResults).getAsJsonObject()); gson.add("counters", new Gson().toJsonTree(counters).getAsJsonObject()); gson.add("result", new Gson().toJsonTree(result).getAsJsonObject()); + gson.addProperty("diceString", diceString); resolver.setVariable(ROLL_VARIABLE_MAP.get(functionName), gson); - return renderRolls(functionName, results); + if (options.contains(Options.FORMAT_JSON)) { + return returnDetails(functionName, resolver); + } else if (options.contains(Options.FORMAT_GROUP_DICE)) { + return renderGrouped(functionName, diceString, individualResults, options); + } else { + return renderRolls(functionName, diceString, results, options); + } } /** * Render the results of a roll to a string. * * @param functionName the name of the function. + * @param diceString the string representing the dice rolled. * @param results the results to render. + * @param options the options for the roll. * @return the rendered results. */ - private String renderRolls(String functionName, List results) { + private String renderRolls( + String functionName, String diceString, List results, Set options) { var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); + List toDisplay = results; + if (options.contains(Options.FORMAT_GROUP_RESULTS)) { + toDisplay = + results.stream() + .map(this::explode) + .flatMap(List::stream) + .filter(rt -> rt != ResultType.NONE) + .sorted(Comparator.comparingInt(ResultType::getGroupSort)) + .toList(); + } var sb = new StringBuilder(); + sb.append(""); + if (options.contains(Options.EXPANDED_FORMAT)) { + sb.append(diceString).append(" = "); + } sb.append(""); - for (var result : results) { + for (var result : toDisplay) { sb.append(result.fontCharacters); } sb.append(""); + sb.append(""); return sb.toString(); } + + /** + * Explode the result type by expanding doubles to their component parts.` + * + * @param resultType the result type to simplify. + * @return the exploded result type. + */ + private List explode(ResultType resultType) { + return switch (resultType) { + case SUCCESS_ADVANTAGE -> List.of(ResultType.SUCCESS, ResultType.ADVANTAGE); + case ADVANTAGE_ADVANTAGE -> List.of(ResultType.ADVANTAGE, ResultType.ADVANTAGE); + case SUCCESS_SUCCESS -> List.of(ResultType.SUCCESS, ResultType.SUCCESS); + case FAILURE_THREAT -> List.of(ResultType.FAILURE, ResultType.THREAT); + case FAILURE_FAILURE -> List.of(ResultType.FAILURE, ResultType.FAILURE); + case THREAT_THREAT -> List.of(ResultType.THREAT, ResultType.THREAT); + case LIGHT_LIGHT -> List.of(ResultType.LIGHT, ResultType.LIGHT); + case DARK_DARK -> List.of(ResultType.DARK, ResultType.DARK); + default -> List.of(resultType); + }; + } } From 0c7991869c921a19ef3fda2fd1f20abf9bda8a39 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Sat, 23 Sep 2023 22:31:32 +0930 Subject: [PATCH 4/6] add lib for advanced rolls --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 170458b472..621951a279 100644 --- a/build.gradle +++ b/build.gradle @@ -496,6 +496,14 @@ dependencies { implementation 'com.github.jknack:handlebars:4.3.1' implementation 'com.github.jknack:handlebars-helpers:4.3.1' + + // For advanced dice roller + implementation 'com.github.RPTools:advanced-dice-roller:0.1.4' + + + + + } From 8356de8f79b945de21d4bf05ec6cd011a16ba21f Mon Sep 17 00:00:00 2001 From: cwisniew Date: Fri, 29 Sep 2023 19:39:21 +0930 Subject: [PATCH 5/6] add roll types --- build.gradle | 2 +- .../dicelib/expression/ExpressionParser.java | 41 ++- .../function/advanced/AdvancedDiceRolls.java | 51 ++++ .../function/advanced/GenesysDiceRolls.java | 261 ++++++++++++++++++ .../client/MapToolVariableResolver.java | 2 +- .../rptools/maptool/language/i18n.properties | 12 +- 6 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/rptools/dicelib/expression/function/advanced/AdvancedDiceRolls.java create mode 100644 src/main/java/net/rptools/dicelib/expression/function/advanced/GenesysDiceRolls.java diff --git a/build.gradle b/build.gradle index 621951a279..f504291dd4 100644 --- a/build.gradle +++ b/build.gradle @@ -498,7 +498,7 @@ dependencies { implementation 'com.github.jknack:handlebars-helpers:4.3.1' // For advanced dice roller - implementation 'com.github.RPTools:advanced-dice-roller:0.1.4' + implementation 'com.github.RPTools:advanced-dice-roller:1.0.3' diff --git a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java index 84f45dc425..33ad23b422 100755 --- a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java +++ b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java @@ -14,6 +14,8 @@ */ package net.rptools.dicelib.expression; +import java.util.List; +import java.util.regex.Pattern; import net.rptools.dicelib.expression.function.ArsMagicaStress; import net.rptools.dicelib.expression.function.CountSuccessDice; import net.rptools.dicelib.expression.function.DropHighestRoll; @@ -37,9 +39,11 @@ import net.rptools.dicelib.expression.function.ShadowRun5Dice; import net.rptools.dicelib.expression.function.ShadowRun5ExplodeDice; import net.rptools.dicelib.expression.function.UbiquityRoll; +import net.rptools.dicelib.expression.function.advanced.AdvancedDiceRolls; import net.rptools.parser.*; import net.rptools.parser.transform.RegexpStringTransformer; import net.rptools.parser.transform.StringLiteralTransformer; +import org.javatuples.Pair; public class ExpressionParser { private static String[][] DICE_PATTERNS = @@ -206,22 +210,15 @@ public class ExpressionParser { new String[] {"\\b[aA][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStress($1, $2)"}, new String[] {"\\b[aA][nN][sS](\\d+)\\b", "arsMagicaStressNum($1, 0)"}, new String[] {"\\b[aA][nN][sS](\\d+)[bB]#([+-]?\\d+)\\b", "arsMagicaStressNum($1, $2)"}, - - // SW Genesys - new String[] { - "\\bsw#(([bBsSaAdDpPcCfF]\\d+)+)(\\.(([eEgGdDjJ])+))?\\b", "swgenesys('$1', '$4')" - }, - new String[] {"\\bsw#last(\\.(([eEgGdDjJ])+))*\\b", "swgenesysLast('last', '$2')"}, - - // Genesys - new String[] { - "\\bgs#(([bBsSaAdDpPcCjJ]\\d+)+)(\\.(([eEgGdD])+))?\\b", "genesys('$1', " + "'$4')" - }, - new String[] {"\\bgs#last(\\.(([eEgGdDj])+))*\\b", "genesysLast('last', '$2')"} }; private final Parser parser; + private final List> preprocessPatterns = + List.of( + new Pair<>(Pattern.compile("([A-z]+)!\"([^\"]*)\""), "advancedRoll('$1', " + "'$2')"), + new Pair<>(Pattern.compile("([A-z]+)!'([^']*)'"), "advancedRoll('$1', " + "'$2')")); + public ExpressionParser() { this(DICE_PATTERNS); } @@ -252,6 +249,7 @@ public ExpressionParser(String[][] regexpTransforms) { parser.addFunction(new KeepLowestRoll()); parser.addFunction(new ArsMagicaStress()); parser.addFunction(new GeneSysDice()); + parser.addFunction(new AdvancedDiceRolls()); parser.addFunction(new If()); @@ -291,6 +289,9 @@ public Result evaluate(String expression, VariableResolver resolver, boolean mak } RunData.setCurrent(newRunData); + // Some patterns need pre-processing before the parser is called otherwise the parser + // creation will fail + expression = preProcess(expression); synchronized (parser) { final Expression xp = makeDeterministic @@ -306,4 +307,20 @@ public Result evaluate(String expression, VariableResolver resolver, boolean mak return ret; } + + /** + * Pre-process the expression before it is parsed. This is used to convert some patterns into + * function calls that the parser can handle. + * + * @param expression The expression to pre-process + * @return The pre-processed expression + */ + private String preProcess(String expression) { + for (Pair p : preprocessPatterns) { + if (p.getValue0().matcher(expression).find()) { + return p.getValue0().matcher(expression).replaceAll(p.getValue1()); + } + } + return expression; + } } diff --git a/src/main/java/net/rptools/dicelib/expression/function/advanced/AdvancedDiceRolls.java b/src/main/java/net/rptools/dicelib/expression/function/advanced/AdvancedDiceRolls.java new file mode 100644 index 0000000000..b1acc165dd --- /dev/null +++ b/src/main/java/net/rptools/dicelib/expression/function/advanced/AdvancedDiceRolls.java @@ -0,0 +1,51 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.dicelib.expression.function.advanced; + +import java.util.List; +import net.rptools.dicelib.expression.function.advanced.GenesysDiceRolls.DiceType; +import net.rptools.maptool.language.I18N; +import net.rptools.parser.Parser; +import net.rptools.parser.ParserException; +import net.rptools.parser.VariableResolver; +import net.rptools.parser.function.AbstractFunction; + +/** Function to roll dice using the advanced dice roller. */ +public class AdvancedDiceRolls extends AbstractFunction { + + /** Constructor. */ + public AdvancedDiceRolls() { + super(2, 2, false, "advancedRoll"); + } + + @Override + public Object childEvaluate( + Parser parser, VariableResolver resolver, String functionName, List parameters) + throws ParserException { + String diceName = parameters.get(0).toString().toLowerCase(); + String diceExpression = parameters.get(1).toString(); + + try { + return switch (diceName) { + case "sw" -> new GenesysDiceRolls().roll(DiceType.StarWars, diceExpression, resolver); + case "gs" -> new GenesysDiceRolls().roll(DiceType.Genesys, diceExpression, resolver); + default -> throw new ParserException( + I18N.getText("advanced.roll.unknownDiceType", diceName)); + }; + } catch (IllegalArgumentException e) { + throw new ParserException(e.getMessage()); + } + } +} diff --git a/src/main/java/net/rptools/dicelib/expression/function/advanced/GenesysDiceRolls.java b/src/main/java/net/rptools/dicelib/expression/function/advanced/GenesysDiceRolls.java new file mode 100644 index 0000000000..f267147026 --- /dev/null +++ b/src/main/java/net/rptools/dicelib/expression/function/advanced/GenesysDiceRolls.java @@ -0,0 +1,261 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.dicelib.expression.function.advanced; + +import java.util.Map; +import javax.swing.JOptionPane; +import net.rptools.maptool.advanceddice.genesys.GenesysDiceResult; +import net.rptools.maptool.advanceddice.genesys.GenesysDiceRoller; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; +import net.rptools.maptool.client.ui.theme.ThemeSupport; +import net.rptools.maptool.client.ui.theme.ThemeSupport.ThemeColor; +import net.rptools.maptool.language.I18N; +import net.rptools.parser.ParserException; +import net.rptools.parser.VariableResolver; + +/** Function to roll dice using the advanced dice roller. */ +public class GenesysDiceRolls { + + /** Enum to represent the different dice types. */ + public enum DiceType { + StarWars("sw"), + Genesys("gs"); + + /** The variable prefix for the dice type. */ + public final String variablePrefix; + + /** + * Constructor. + * + * @param variablePrefix the prefix to use for variables. + */ + DiceType(String variablePrefix) { + this.variablePrefix = variablePrefix; + } + + /** + * Get the variable prefix for the dice type. + * + * @return the variable prefix. + */ + public String getVariablePrefix() { + return variablePrefix; + } + } + + /** Map of font names for the different systems. */ + private static final Map GS_FONT_NAME_MAP = + Map.of(DiceType.StarWars, "EotE Symbol", DiceType.Genesys, "Genesys Glyphs and Dice"); + + /** + * Roll the given dice string using genesys/starwars dice roll parser. + * + * @param diceType the type of dice to roll. + * @param diceExpression the expression to roll. + * @param resolver the variable resolver. + * @return the result of the roll. + * @throws ParserException if there is an error parsing the expression. + */ + Object roll(DiceType diceType, String diceExpression, VariableResolver resolver) + throws ParserException { + var roller = new GenesysDiceRoller(); + try { + var result = + roller.roll( + diceExpression, + n -> getVariable(resolver, n), + n -> getProperty(resolver, n), + n -> getPromptedValue(resolver, n)); + + if (result.hasErrors()) { + var errorSb = new StringBuilder(); + for (var error : result.getErrors()) { + String msg; + int ind = error.charPositionInLine(); + if (result.getRollString().length() > ind + 3) { + msg = result.getRollString().substring(ind, ind + 3) + "..."; + } else { + msg = result.getRollString().substring(ind); + } + var errorText = I18N.getText("advanced.roll.parserError", error.line(), ind + 1, msg); + errorSb.append(errorText).append("
"); + } + throw new ParserException(errorSb.toString()); + } + var varPrefix = diceType.getVariablePrefix() + ".lastRoll"; + setVars(resolver, varPrefix, result); + + return formatResults(diceType, result); + + } catch (IllegalArgumentException e) { + throw new ParserException(e.getMessage()); + } + } + + /** + * Format the results of the roll. + * + * @param diceType the type of dice. + * @param result the result of the roll. + * @return the formatted results. + */ + private String formatResults(DiceType diceType, GenesysDiceResult result) { + var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); + + var sb = new StringBuilder(); + sb.append(""); + sb.append(""); + for (var dice : result.getRolls()) { + sb.append(dice.resultType().getFontCharacters()); + } + sb.append(""); + sb.append(""); + return sb.toString(); + } + + /** + * Set the variables for the result. + * + * @param resolver the variable resolver. + * @param varPrefix the variable prefix. + * @param result the result. + * @throws ParserException if there is an error setting the variables. + */ + private void setVars(VariableResolver resolver, String varPrefix, GenesysDiceResult result) + throws ParserException { + resolver.setVariable(varPrefix + ".expression", result.getRollString()); + resolver.setVariable(varPrefix + ".success", result.getSuccessCount()); + resolver.setVariable(varPrefix + ".failure", result.getFailureCount()); + resolver.setVariable(varPrefix + ".advantage", result.getAdvantageCount()); + resolver.setVariable(varPrefix + ".threat", result.getThreatCount()); + resolver.setVariable(varPrefix + ".triumph", result.getTriumphCount()); + resolver.setVariable(varPrefix + ".despair", result.getDespairCount()); + resolver.setVariable(varPrefix + ".light", result.getLightCount()); + resolver.setVariable(varPrefix + ".dark", result.getDarkCount()); + + for (var group : result.getGroupNames()) { + var groupResult = result.getGroup(group); + setVars(resolver, varPrefix + ".group." + group, groupResult); + } + } + + /** + * Get the variable value. + * + * @param resolver the variable resolver. + * @param name the name of the variable. + * @return the value of the variable. + */ + private int getVariable(VariableResolver resolver, String name) { + if (!resolver.getVariables().contains(name.toLowerCase())) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.unknownVariable", name)); + } + + try { + var value = resolver.getVariable(name); + var result = integerResult(value, false); + + if (result == null) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.variableNotNumber", name)); + } + + return result; + } catch (ParserException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get the property value for the token in context. + * + * @param resolver the variable resolver. + * @param name the name of the property. + * @return the value of the property. + */ + private int getProperty(VariableResolver resolver, String name) { + var mtResolver = (MapToolVariableResolver) resolver; + var token = mtResolver.getTokenInContext(); + if (token == null) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.noTokenInContext")); + } + var value = token.getProperty(name); + + if (value == null) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.unknownProperty", name)); + } + + var result = integerResult(value, true); + if (result == null) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.propertyNotNumber", name)); + } + + return result; + } + + /** + * Prompt the user for a value. + * + * @param resolver the variable resolver. + * @param name the name of the value. + * @return the value. + */ + private int getPromptedValue(VariableResolver resolver, String name) { + var option = + JOptionPane.showInputDialog( + MapTool.getFrame(), + I18N.getText("lineParser.dialogValueFor", name), + I18N.getText("lineParser.dialogTitleNoToken"), + JOptionPane.QUESTION_MESSAGE, + null, + null, + 1); + try { + return Integer.parseInt(option.toString()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(I18N.getText("advanced.roll.inputNotNumber", name)); + } + } + + /** + * Get the integer result. + * + * @param value the value. + * @param parseString attempt to parse string value of object if it is not a number. + * @return the integer result. + */ + private Integer integerResult(Object value, boolean parseString) { + if (value instanceof Integer i) { + return i; + } + + if (value instanceof Number n) { + return n.intValue(); + } + + if (parseString) { + try { + return Integer.parseInt(value.toString()); + } catch (NumberFormatException e) { + return null; + } + } + + return null; + } +} diff --git a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java index f1489caf77..ed31fbcccb 100644 --- a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java +++ b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java @@ -276,7 +276,7 @@ public Object getVariable(String name, VariableModifiers mods) throws ParserExce result = JOptionPane.showInputDialog( MapTool.getFrame(), - I18N.getText("lineParser.dialogValueFor") + " " + name, + I18N.getText("lineParser.dialogValueFor", name), DialogTitle, JOptionPane.QUESTION_MESSAGE, null, diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 2d7975a06f..9da807e8fb 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -1654,7 +1654,7 @@ lineParser.countNonNeg = Count option requires a non negative numbe # Notice there are no double quotes around {0}. lineParser.dialogTitle = Input Value for {0}. lineParser.dialogTitleNoToken = Input Value. -lineParser.dialogValueFor = Value For +lineParser.dialogValueFor = Value For "{0}" lineParser.duplicateLibTokens = Duplicate "{0}" tokens found. lineParser.emptyTokenName = Cannot assign a blank or empty string to the variable token.name lineParser.errorBodyRoll = Error in body of roll. @@ -2808,3 +2808,13 @@ Label.label=Label: # StatSheet token.statSheet.legacyStatSheetDescription = Legacy (pre 1.14) Stat Sheet token.statSheet.useDefault = Default Stat Sheet for Property Type + +# Advanced Dice Rolls +advanced.roll.parserError = Dice Roll String Error line {0} column {1} "{2}". +advanced.roll.unknownDiceType = Unknown Dice Roll Type {0}. +advanced.roll.unknownVariable = Unknown Variable {0}. +advanced.roll.variableNotNumber = Variable {0} is not a number. +advanced.roll.unknownProperty = Unknown Property {0}. +advanced.roll.propertyNotNumber = Property {0} is not a number. +advanced.roll.noTokenInContext = No token in context. +advanced.roll.inputNotNumber = Input {0} is not a number. \ No newline at end of file From 3ff483409e46447c0aea4507dedeb5c428fa3085 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Fri, 29 Sep 2023 20:06:17 +0930 Subject: [PATCH 6/6] remove redundant code --- .../dicelib/expression/ExpressionParser.java | 2 - .../expression/function/GeneSysDice.java | 754 ------------------ 2 files changed, 756 deletions(-) delete mode 100644 src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java diff --git a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java index 33ad23b422..1653aa10d3 100755 --- a/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java +++ b/src/main/java/net/rptools/dicelib/expression/ExpressionParser.java @@ -23,7 +23,6 @@ import net.rptools.dicelib.expression.function.ExplodeDice; import net.rptools.dicelib.expression.function.ExplodingSuccessDice; import net.rptools.dicelib.expression.function.FudgeRoll; -import net.rptools.dicelib.expression.function.GeneSysDice; import net.rptools.dicelib.expression.function.HeroKillingRoll; import net.rptools.dicelib.expression.function.HeroRoll; import net.rptools.dicelib.expression.function.If; @@ -248,7 +247,6 @@ public ExpressionParser(String[][] regexpTransforms) { parser.addFunction(new DropHighestRoll()); parser.addFunction(new KeepLowestRoll()); parser.addFunction(new ArsMagicaStress()); - parser.addFunction(new GeneSysDice()); parser.addFunction(new AdvancedDiceRolls()); parser.addFunction(new If()); diff --git a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java b/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java deleted file mode 100644 index 0283dd085b..0000000000 --- a/src/main/java/net/rptools/dicelib/expression/function/GeneSysDice.java +++ /dev/null @@ -1,754 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.dicelib.expression.function; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.StreamSupport; -import net.rptools.maptool.client.ui.theme.ThemeSupport; -import net.rptools.maptool.client.ui.theme.ThemeSupport.ThemeColor; -import net.rptools.parser.Parser; -import net.rptools.parser.ParserException; -import net.rptools.parser.VariableResolver; -import net.rptools.parser.function.AbstractFunction; - -/** This class implements the dice rolling functions for genesys / starwars genesys systems. */ -public class GeneSysDice extends AbstractFunction { - - /** Enumeration of the possible results of a die roll. */ - enum ResultType { - /** A single success. */ - SUCCESS(1, 0, 0, 0, 0, 0, 0, 0, "s", 1), - /** A single failure. */ - FAILURE(0, 1, 0, 0, 0, 0, 0, 0, "f", 2), - /** A single advantage. */ - ADVANTAGE(0, 0, 1, 0, 0, 0, 0, 0, "a", 3), - /** A single threat. */ - THREAT(0, 0, 0, 1, 0, 0, 0, 0, "h", 4), - /** A single triumph. */ - TRIUMPH(1, 0, 0, 0, 1, 0, 0, 0, "t", 5), - /** A single despair. */ - DESPAIR(0, 1, 0, 0, 0, 1, 0, 0, "d", 6), - /* A single light side point. */ - LIGHT(0, 0, 0, 0, 0, 0, 1, 0, "Z", 7), - /** A single dark side point. */ - DARK(0, 0, 0, 0, 0, 0, 0, 1, "z", 8), - /** No result. */ - NONE(0, 0, 0, 0, 0, 0, 0, 0, " ", 99), - /** A single success and a single advantage. */ - SUCCESS_ADVANTAGE(1, 0, 1, 0, 0, 0, 0, 0, "sa", 99), - /** Two Advantages. */ - ADVANTAGE_ADVANTAGE(0, 0, 2, 0, 0, 0, 0, 0, "aa", 99), - /** Two Successes. */ - SUCCESS_SUCCESS(2, 0, 0, 0, 0, 0, 0, 0, "ss", 99), - /* A single failure and a single threat. */ - FAILURE_THREAT(0, 1, 0, 1, 0, 0, 0, 0, "fh", 99), - /** Two Failures. */ - FAILURE_FAILURE(0, 2, 0, 0, 0, 0, 0, 0, "ff", 99), - /** Two Threats. */ - THREAT_THREAT(0, 0, 0, 2, 0, 0, 0, 0, "hh", 99), - /** Two Light Side Points. */ - LIGHT_LIGHT(0, 0, 0, 0, 0, 0, 2, 0, "ZZ", 99), - /** Two Dark Side Points. */ - DARK_DARK(0, 0, 0, 0, 0, 0, 0, 2, "zz", 99); - - /** The number of successes this result represents. */ - private final int success; - /** The number of failures this result represents. */ - private final int failure; - /** The number of advantages this result represents. */ - private final int advantage; - /** The number of threats this result represents. */ - private final int threat; - /** The number of triumphs this result represents. */ - private final int triumph; - /** The number of despairs this result represents. */ - private final int despair; - /** The number of light side points this result represents. */ - private final int light; - /** The number of dark side points this result represents. */ - private final int dark; - - /** The font characters that represent this result. */ - private final String fontCharacters; - - /** The sort order of the result when grouped. */ - private final int groupSort; - - /** - * Constructor. - * - * @param success the number of successes this result represents. - * @param failure the number of failures this result represents. - * @param advantage the number of advantages this result represents. - * @param threat the number of threats this result represents. - * @param triumph the number of triumphs this result represents. - * @param despair the number of despairs this result represents. - * @param light the number of light side points this result represents. - * @param dark the number of dark side points this result represents. - * @param fontCharacters the font characters that represent this result. - * @param groupSort the sort order of the result when grouped. - */ - ResultType( - int success, - int failure, - int advantage, - int threat, - int triumph, - int despair, - int light, - int dark, - String fontCharacters, - int groupSort) { - this.success = success; - this.failure = failure; - this.advantage = advantage; - this.threat = threat; - this.triumph = triumph; - this.despair = despair; - this.light = light; - this.dark = dark; - this.fontCharacters = fontCharacters; - this.groupSort = groupSort; - } - - /** - * Get the number of successes this result represents. - * - * @return the number of successes this result represents. - */ - public int getSuccess() { - return success; - } - - /** - * Get the number of failures this result represents. - * - * @return the number of failures this result represents. - */ - public int getFailure() { - return failure; - } - - /** - * Get the number of advantages this result represents. - * - * @return the number of advantages this result represents. - */ - public int getAdvantage() { - return advantage; - } - - /** - * Get the number of threats this result represents. - * - * @return the number of threats this result represents. - */ - public int getThreat() { - return threat; - } - - /** - * Get the number of triumphs this result represents. - * - * @return the number of triumphs this result represents. - */ - public int getTriumph() { - return triumph; - } - - /** - * Get the number of despairs this result represents. - * - * @return the number of despairs this result represents. - */ - public int getDespair() { - return despair; - } - - /** - * Get the number of light side points this result represents. - * - * @return the number of light side points this result represents. - */ - public int getLight() { - return light; - } - - /** - * Get the number of dark side points this result represents. - * - * @return the number of dark side points this result represents. - */ - public int getDark() { - return dark; - } - - /** - * Get the font characters that represent this result. - * - * @return the font characters that represent this result. - */ - public int getGroupSort() { - return groupSort; - } - } - - /** Enumeration of the possible dice types. */ - private enum DiceType { - /** The boost die. */ - BOOST( - "b", - 0, - List.of( - ResultType.NONE, - ResultType.NONE, - ResultType.SUCCESS, - ResultType.SUCCESS_ADVANTAGE, - ResultType.ADVANTAGE_ADVANTAGE, - ResultType.ADVANTAGE)), - - /** The setback die. */ - SETBACK( - "s", - 1, - List.of( - ResultType.NONE, - ResultType.NONE, - ResultType.FAILURE, - ResultType.FAILURE, - ResultType.THREAT, - ResultType.THREAT)), - /** The ability die. */ - ABILITY( - "a", - 2, - List.of( - ResultType.NONE, - ResultType.SUCCESS, - ResultType.SUCCESS, - ResultType.SUCCESS_SUCCESS, - ResultType.ADVANTAGE, - ResultType.ADVANTAGE, - ResultType.SUCCESS_ADVANTAGE, - ResultType.ADVANTAGE_ADVANTAGE)), - /** The difficulty die. */ - DIFFICULTY( - "d", - 3, - List.of( - ResultType.NONE, - ResultType.FAILURE, - ResultType.FAILURE_FAILURE, - ResultType.THREAT, - ResultType.THREAT, - ResultType.THREAT, - ResultType.THREAT_THREAT, - ResultType.FAILURE_THREAT)), - /** The proficiency die. */ - PROFICIENCY( - "p", - 4, - List.of( - ResultType.NONE, - ResultType.SUCCESS, - ResultType.SUCCESS, - ResultType.SUCCESS_SUCCESS, - ResultType.SUCCESS_SUCCESS, - ResultType.ADVANTAGE, - ResultType.SUCCESS_ADVANTAGE, - ResultType.SUCCESS_ADVANTAGE, - ResultType.SUCCESS_ADVANTAGE, - ResultType.ADVANTAGE_ADVANTAGE, - ResultType.ADVANTAGE_ADVANTAGE, - ResultType.TRIUMPH)), - /* The challenge die. */ - CHALLENGE( - "c", - 5, - List.of( - ResultType.NONE, - ResultType.FAILURE, - ResultType.FAILURE, - ResultType.FAILURE_FAILURE, - ResultType.FAILURE_FAILURE, - ResultType.THREAT, - ResultType.THREAT, - ResultType.FAILURE_THREAT, - ResultType.FAILURE_THREAT, - ResultType.THREAT_THREAT, - ResultType.THREAT_THREAT, - ResultType.DESPAIR)), - /** The force die. */ - FORCE( - "f", - 6, - List.of( - ResultType.DARK, - ResultType.DARK, - ResultType.DARK, - ResultType.DARK, - ResultType.DARK, - ResultType.DARK, - ResultType.DARK_DARK, - ResultType.LIGHT, - ResultType.LIGHT, - ResultType.LIGHT_LIGHT, - ResultType.LIGHT_LIGHT, - ResultType.LIGHT_LIGHT)); - - /** The sides of the die. */ - private final List sides; - - /** The pattern used to represent this die. */ - private final String diePattern; - - /** The sort order of the die when grouped, */ - private final int groupSort; - - /** - * Constructor. - * - * @param diePattern the pattern used to represent this die. - * @param groupSort the sort order of the die when grouped. - * @param sides the sides of the die. - */ - DiceType(String diePattern, int groupSort, List sides) { - this.sides = sides; - this.groupSort = groupSort; - this.diePattern = diePattern; - } - - /** - * Get the pattern used to represent this die. - * - * @return the pattern used to represent this die. - */ - public String getDiePattern() { - return diePattern; - } - - /** - * Get the number of sides on this die. - * - * @return the number of sides on this die. - */ - public int getSides() { - return sides.size(); - } - - /** - * Roll the die. - * - * @return the result of the roll. - */ - public ResultType roll() { - return getSide(DiceHelper.rollDice(1, sides.size()) - 1); - } - - /** - * Get the result of a roll of the die. - * - * @param side the side of the die to get the result for. - * @return the result of the roll. - */ - public ResultType getSide(int side) { - return sides.get(side); - } - - /** - * Get the sort order of the die when grouped. - * - * @return the sort order of the die when grouped. - */ - public int getGroupSort() { - return groupSort; - } - } - - /** Enumeration of the possible options for the dice roll. */ - private enum Options { - /** Group the rolls by die type. */ - FORMAT_GROUP_DICE("d"), - /** Group the rolls by result. */ - FORMAT_GROUP_RESULTS("g"), - /** print out the rolls in expanded format. */ - EXPANDED_FORMAT("e"), - /** Return the results as JSON. */ - FORMAT_JSON("j"); - - /** The string used to represent this option. */ - private final String optionString; - - Options(String optionString) { - this.optionString = optionString; - } - - /** - * Get the string used to represent this option. - * - * @return the string used to represent this option. - */ - public String getOptionString() { - return optionString; - } - } - - /** Map of font names for the different systems. */ - private static final Map GS_FONT_NAME_MAP = - Map.of("swgenesys", "EotE Symbol", "genesys", "Genesys Glyphs and Dice"); - - /** Map of variable names for the different systems. */ - private static final Map ROLL_VARIABLE_MAP = - Map.of("swgenesys", "#lastSWResult", "genesys", "#lastGSResult"); - - /** Constructor. */ - public GeneSysDice() { - super(1, 2, false, "swgenesys", "genesys", "swgenesyslast", "genesyslast"); - } - - @Override - public Object childEvaluate( - Parser parser, VariableResolver resolver, String functionName, List parameters) - throws ParserException { - - var options = new HashSet(); - if (parameters.size() > 1) { - var optionsString = parameters.get(1).toString().toLowerCase(); - for (var option : Options.values()) { - if (optionsString.contains(option.getOptionString())) { - options.add(option); - } - } - } - - return switch (functionName.toLowerCase()) { - case "swgenesys", "genesys" -> performRoll(functionName, resolver, parameters, options); - case "swgenesyslast", "genesyslast" -> render( - functionName.toLowerCase().replace("last", ""), resolver, options); - default -> // Should never happen - throw new ParserException("Invalid function name: " + functionName); - }; - } - - /** - * Render the results of the roll string saved in the variables. - * - * @param functionName the name of the function. - * @param resolver the variable resolver. - * @param options the options for the roll. - * @return the rendered results. - * @throws ParserException if an error occurs. - */ - private Object render(String functionName, VariableResolver resolver, Set options) - throws ParserException { - if (options.contains(Options.FORMAT_JSON)) { - return returnDetails(functionName, resolver); - } else if (options.contains(Options.FORMAT_GROUP_DICE)) { - return renderGrouped(functionName, resolver, options); - } else { - return renderRolls(functionName, resolver, options); - } - } - - /** - * Render the results of a roll. - * - * @param functionName the name of the function. - * @param resolver the variable resolver. - * @param options the options for the roll. - * @return the rendered results. - * @throws ParserException if an error occurs. - */ - private String renderGrouped(String functionName, VariableResolver resolver, Set options) - throws ParserException { - var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); - var rollsMap = new HashMap>(); - for (var entry : rollResults.get("rolls").getAsJsonObject().entrySet()) { - var dieName = entry.getKey(); - var rolls = - StreamSupport.stream(entry.getValue().getAsJsonArray().spliterator(), false) - .map(r -> r.getAsString().toLowerCase()) - .toList(); - rollsMap.put(dieName, rolls); - } - return renderGrouped( - functionName, rollResults.get("diceString").getAsString(), rollsMap, options); - } - - /** - * Render the results of a roll to a string. - * - * @param functionName the name of the function. - * @param diceString the string representing the dice rolled. - * @param rolls the rolls to render. - * @param options the options for the roll. - * @return the rendered results. - */ - private String renderGrouped( - String functionName, - String diceString, - Map> rolls, - Set options) { - var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); - var sb = new StringBuilder(); - - // We don't want to pass the expanded format option to the renderRolls method as we deal with - // it here. - var passedOptions = new HashSet<>(options); - passedOptions.remove(Options.EXPANDED_FORMAT); - - sb.append(""); - if (options.contains(Options.EXPANDED_FORMAT)) { - sb.append(diceString).append(" = "); - } - var dieTypes = - Arrays.stream(DiceType.values()) - .sorted(Comparator.comparingInt(DiceType::getGroupSort)) - .toList(); - boolean first = true; - for (var dt : dieTypes) { - var dieName = dt.name().toLowerCase(); - if (rolls.containsKey(dieName)) { - if (!first) { - sb.append(", "); - } - first = false; - sb.append(dieName).append(": "); - var dieRolls = - rolls.get(dieName).stream().map(r -> ResultType.valueOf(r.toUpperCase())).toList(); - sb.append(renderRolls(functionName, diceString, dieRolls, passedOptions)); - } - } - sb.append(""); - return sb.toString(); - } - - /** - * Render the results of a roll to a string. - * - * @param functionName the name of the function. - * @param resolver the variable resolver. - * @param options the options for the roll. - * @return the rendered results. - * @throws ParserException if an error occurs. - */ - private String renderRolls(String functionName, VariableResolver resolver, Set options) - throws ParserException { - var rollResults = (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); - var rolls = - StreamSupport.stream( - rollResults - .get("rolls") - .getAsJsonObject() - .get("all") - .getAsJsonArray() - .spliterator(), - false) - .map(r -> ResultType.valueOf(r.getAsString().toUpperCase())) - .toList(); - return renderRolls(functionName, rollResults.get("diceString").getAsString(), rolls, options); - } - - /** - * Return the details of the last roll. - * - * @param functionName the name of the function. - * @param resolver the variable resolver. - * @return the details of the last roll. - * @throws ParserException if an error occurs. - */ - private JsonObject returnDetails(String functionName, VariableResolver resolver) - throws ParserException { - return (JsonObject) resolver.getVariable(ROLL_VARIABLE_MAP.get(functionName)); - } - - /** - * Perform a roll. - * - * @param functionName the name of the function. - * @param resolver the variable resolver. - * @param parameters the parameters to the function. - * @param options the options for the roll. - * @return the rendered results. - * @throws ParserException if an error occurs. - */ - private Object performRoll( - String functionName, VariableResolver resolver, List parameters, Set options) - throws ParserException { - - var diceString = parameters.get(0).toString().toLowerCase(); - - var pattern = Pattern.compile("(\\S)(\\d+)"); - var matcher = pattern.matcher(diceString); - var results = new ArrayList(); - var individualResults = new HashMap>(); - - while (matcher.find()) { - var dieType = matcher.group(1); - var dieCount = Integer.parseInt(matcher.group(2)); - var die = - Arrays.stream(DiceType.values()) - .filter(dt -> dt.diePattern.equalsIgnoreCase(dieType)) - .findFirst() - .get(); - for (int i = 0; i < dieCount; i++) { - var roll = die.roll(); - results.add(roll); - var dieName = die.name().toLowerCase(); - individualResults.putIfAbsent(dieName, new ArrayList<>()); - individualResults.get(dieName).add(roll.name().toLowerCase()); - } - } - - individualResults.put("all", results.stream().map(r -> r.name().toLowerCase()).toList()); - - // Set variable with result - var resultMap = new HashMap(); - for (var result : results) { - var resultString = result.toString().toLowerCase(); - resultMap.put(resultString, resultMap.getOrDefault(resultString, 0) + 1); - } - - int successCount = 0; - int failureCount = 0; - int advantageCount = 0; - int threatCount = 0; - int triumphCount = 0; - int despairCount = 0; - int lightCount = 0; - int darkCount = 0; - - for (var result : results) { - successCount += result.getSuccess(); - failureCount += result.getFailure(); - advantageCount += result.getAdvantage(); - threatCount += result.getThreat(); - triumphCount += result.getTriumph(); - despairCount += result.getDespair(); - lightCount += result.getLight(); - darkCount += result.getDark(); - } - - var counters = new HashMap(); - counters.put("success", successCount); - counters.put("failure", failureCount); - counters.put("advantage", advantageCount); - counters.put("threat", threatCount); - counters.put("triumph", triumphCount); - counters.put("despair", despairCount); - if (lightCount > 0) { - counters.put("light", lightCount); - } - if (darkCount > 0) { - counters.put("dark", darkCount); - } - - var result = new HashMap(); - result.put("success", successCount - failureCount); - result.put("advantage", advantageCount - threatCount); - result.put("triumph", triumphCount); - result.put("despair", despairCount); - if (lightCount > 0 || darkCount > 0) { - result.put("force", lightCount - darkCount); - } - - var gson = new Gson().toJsonTree(resultMap).getAsJsonObject(); - gson.add("rolls", new Gson().toJsonTree(individualResults).getAsJsonObject()); - gson.add("counters", new Gson().toJsonTree(counters).getAsJsonObject()); - gson.add("result", new Gson().toJsonTree(result).getAsJsonObject()); - gson.addProperty("diceString", diceString); - - resolver.setVariable(ROLL_VARIABLE_MAP.get(functionName), gson); - - if (options.contains(Options.FORMAT_JSON)) { - return returnDetails(functionName, resolver); - } else if (options.contains(Options.FORMAT_GROUP_DICE)) { - return renderGrouped(functionName, diceString, individualResults, options); - } else { - return renderRolls(functionName, diceString, results, options); - } - } - - /** - * Render the results of a roll to a string. - * - * @param functionName the name of the function. - * @param diceString the string representing the dice rolled. - * @param results the results to render. - * @param options the options for the roll. - * @return the rendered results. - */ - private String renderRolls( - String functionName, String diceString, List results, Set options) { - var gray = ThemeSupport.getThemeColorHexString(ThemeColor.GREY); - - List toDisplay = results; - if (options.contains(Options.FORMAT_GROUP_RESULTS)) { - toDisplay = - results.stream() - .map(this::explode) - .flatMap(List::stream) - .filter(rt -> rt != ResultType.NONE) - .sorted(Comparator.comparingInt(ResultType::getGroupSort)) - .toList(); - } - var sb = new StringBuilder(); - sb.append(""); - if (options.contains(Options.EXPANDED_FORMAT)) { - sb.append(diceString).append(" = "); - } - sb.append(""); - for (var result : toDisplay) { - sb.append(result.fontCharacters); - } - sb.append(""); - sb.append(""); - return sb.toString(); - } - - /** - * Explode the result type by expanding doubles to their component parts.` - * - * @param resultType the result type to simplify. - * @return the exploded result type. - */ - private List explode(ResultType resultType) { - return switch (resultType) { - case SUCCESS_ADVANTAGE -> List.of(ResultType.SUCCESS, ResultType.ADVANTAGE); - case ADVANTAGE_ADVANTAGE -> List.of(ResultType.ADVANTAGE, ResultType.ADVANTAGE); - case SUCCESS_SUCCESS -> List.of(ResultType.SUCCESS, ResultType.SUCCESS); - case FAILURE_THREAT -> List.of(ResultType.FAILURE, ResultType.THREAT); - case FAILURE_FAILURE -> List.of(ResultType.FAILURE, ResultType.FAILURE); - case THREAT_THREAT -> List.of(ResultType.THREAT, ResultType.THREAT); - case LIGHT_LIGHT -> List.of(ResultType.LIGHT, ResultType.LIGHT); - case DARK_DARK -> List.of(ResultType.DARK, ResultType.DARK); - default -> List.of(resultType); - }; - } -}