diff --git a/README.adoc b/README.adoc index fcb04c0e..19e82a06 100644 --- a/README.adoc +++ b/README.adoc @@ -38,79 +38,139 @@ Some example for commands, many more systems can be mapped, please see in the de [discrete] === DnD 5e -`/custom_dice start buttons:1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20;3d4=@3d4;3d6=@3d6;3d8=@3d8;3d10=@3d10;3d12=@3d12;3d20=@3d20` +`/quickstart system:Dungeon & Dragons 5e` + +or + +`/custom_dice start buttons:1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20` [discrete] === OSR +`/quickstart system:OSR` + +or + `/custom_dice start buttons:1d20@D20;1d6@D6;2d6@2D6;1d4@D4;1d8@D8;6x3d6=@Stats;(3d6=)*10@Gold;1d100@D100;1d10@D10;1d12@D12` [discrete] === Fate +`/quickstart system: Fate` + +or + `/fate start type:with_modifier` [discrete] === World of Darkness [discrete] -==== Chronicles of Darkness / nWoD +==== nWod / Chronicles of Darkness + +`/quickstart system: nWod / Chronicles of Darkness` + +or `/count_successes start dice_sides: 10 target_number: 8 reroll_set: 10` [discrete] -==== Storyteller System / oWoD +==== oWod / Storyteller System + +`/quickstart system: oWod / Storyteller System` + +or `/pool_target start sides:10 max_dice:15 reroll_set:10 botch_set:1 reroll_variant:ask` [discrete] === Shadowrun +`/quickstart system: Shadowrun` + +or + `/count_successes start dice_sides:6 target_number:5 glitch:half_dice_one max_dice:20` [discrete] === Savage Worlds: +`/quickstart system:` + +or + `/custom_parameter start expression: (d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20} + {Type: 0@Regular/1d!!6@Wildcard})k1` [discrete] === Traveller +`/quickstart system: Traveller` + +or + `/sum_custom_set start buttons:+2d6;+(3d6k2)@Boon;+(3d6l2)@Bane;+1d6;+1;+2;+3;+4;-1;-2;-3;-4` [discrete] === Blades in the Dark +`/quickstart system: Blades in the Dark` + +or + `/custom_dice start buttons: ifE(2d[0/0/0/1/1/3]l1,3,'Success',1,'Partial','Failure')@Zero;ifE(1d[0/0/0/1/1/3],3,'Success',1,'Partial','Failure')@1d6;ifG(2d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@2d6;ifG(3d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@3d6;ifG(4d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@4d6;ifG(5d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@5d6;ifG(6d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@6d6;ifG(7d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@7d6` [discrete] -=== Call of Cthulhu (7th Edition) +=== Call of Cthulhu 7th Edition + +`/quickstart system: Call of Cthulhu 7th Edition` + +or `/custom_dice start buttons: 1d100; 2d100L1@1d100 Advantage; 2d100K1@1d100 Penalty; 1d3; 1d4; 1d6; 1d8; 1d10; 1d12; 1d20; 3d6` [discrete] === Universal Dice Calculator === +`/quickstart system: Dice Calculator` + +or + `/sum_custom_set start buttons: 7;8;9;+;-;4;5;6;d;k;1;2;3;0;l always_sum_result: true` [discrete] === Exalted 3rd === +`/quickstart system: Exalted 3ed` + +or + `/custom_parameter start expression: val('$1', cancel(double({number of dice:1<=>24}d10,10),1,[7/8/9/10])), ifE(('$1'>=7)c,0,ifG(('$1'\<=1)c,0,'Botch'))` [discrete] === Vampire 5ed === -`/custom_parameter start expression: val('$r',{regular dice:1<=>16}d10) val('$h',{hunger dice:0<=>5}d10) val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression` +`/quickstart system: Vampire 5ed` + +or + +`/custom_dice expression: val('$r',{regular dice:1<=>16}d10 col 'blue') val('$h',{hunger dice:0<=>5}d10 col 'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression dice_image_style: polyhedral_knots dice_image_color: blue` [discrete] === One-Roll Engine === +`/quickstart system: One-Roll Engine` + +or + `/custom_parameter start expression: groupc({Number of Dice:1<=>10}d10+({Number of Extra Die:0@0/10@1/2r10@2/3r10@3/4r10@4})>={Difficulty:1<=>10})` [discrete] === Rêve de Dragon === +`/quickstart system: Rêve de Dragon` + +or + `/custom_dice start buttons: 1d4@D4;1d6@D6;2d6=@2D6;1d7@D7;1d8@D8;val('roll',1d!8 col 'special') val('diceCount','roll' c) 'roll'-'diceCount'+7=@DDR;2d10=@2D10;1d12@D12;val('$r',1d12 col 'special'),if('$r'=?1,'vaisseau','$r'=?2,'sirène','$r'=?3,'faucon','$r'=?4,'couronne','$r'=?5,'dragon','$r'=?6,'épées','$r'=?7,'lyre','$r'=?8,'serpent','$r'=?9,'poisson acrobate','$r'=?10,'araignée','$r'=?11,'roseaux','$r'=?12,'château dormant')@DAS;1d20@D20;1d100@D100 answer_format: without_expression dice_image_style: polyhedral_RdD` == Commands @@ -120,6 +180,12 @@ If this option is used then the answer of a roll will be posted in the provided The target channel must be a text channel and need the bot need the usual permissions. If a message sends the answer to another channel, it will not be moved to the end of its own channel. +=== Quickstart + +Type `/quickstart system' and the bot will offer a list of ready to play dice sets. +Simple select a system out of the list or keep typing to search and filter in the list. +Please let me know if you're missing an RPG system in the quickstart list. + === Answer Format All commands have an `answer_format` option that determines how the answer of a dice roll is shown. @@ -269,12 +335,12 @@ image:image/polyhedral_RdD_default.png[image,600] .special image:image/polyhedral_RdD_special.png[image,170] -The style has two "colors": `default` and `special`. The `default` "color" contains universal images for d4, d6, d7, d10, d8, d12, d20, d100. +The style has two "colors": `default` and `special`. +The `default` "color" contains universal images for d4, d6, d7, d10, d8, d12, d20, d100. The `special` "color" contains only images for Draconic d8 (image values are 0 to 7 and a dragon for the 8) and Astral d12 with only special symbols. This set was designed and contributed by http://scriptarium.org[scriptarium.org]. - === Custom Dice Buttons image:image/custom_dice.webp[image] diff --git a/bot/src/main/java/de/janno/discord/bot/Bot.java b/bot/src/main/java/de/janno/discord/bot/Bot.java index 75138b90..ce7df60d 100644 --- a/bot/src/main/java/de/janno/discord/bot/Bot.java +++ b/bot/src/main/java/de/janno/discord/bot/Bot.java @@ -3,15 +3,17 @@ import com.google.common.collect.ImmutableList; import de.janno.discord.bot.command.ClearCommand; -import de.janno.discord.bot.command.HelpCommand; -import de.janno.discord.bot.command.WelcomeCommand; +import de.janno.discord.bot.command.channelConfig.ChannelConfigCommand; import de.janno.discord.bot.command.countSuccesses.CountSuccessesCommand; import de.janno.discord.bot.command.customDice.CustomDiceCommand; import de.janno.discord.bot.command.customParameter.CustomParameterCommand; -import de.janno.discord.bot.command.channelConfig.ChannelConfigCommand; import de.janno.discord.bot.command.directRoll.DirectRollCommand; import de.janno.discord.bot.command.directRoll.ValidationCommand; import de.janno.discord.bot.command.fate.FateCommand; +import de.janno.discord.bot.command.help.HelpCommand; +import de.janno.discord.bot.command.help.RpgSystemCommandPreset; +import de.janno.discord.bot.command.help.QuickstartCommand; +import de.janno.discord.bot.command.help.WelcomeCommand; import de.janno.discord.bot.command.holdReroll.HoldRerollCommand; import de.janno.discord.bot.command.poolTarget.PoolTargetCommand; import de.janno.discord.bot.command.sumCustomSet.SumCustomSetCommand; @@ -54,25 +56,36 @@ public static void main(final String[] args) throws Exception { Set allGuildIdsInPersistence = persistenceManager.getAllGuildIds(); CachingDiceEvaluator cachingDiceEvaluator = new CachingDiceEvaluator(new RandomNumberSupplier(), 1000, 10_000); + + CountSuccessesCommand countSuccessesCommand = new CountSuccessesCommand(persistenceManager); + CustomDiceCommand customDiceCommand = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); + FateCommand fateCommand = new FateCommand(persistenceManager); + CustomParameterCommand customParameterCommand = new CustomParameterCommand(persistenceManager, cachingDiceEvaluator); + SumCustomSetCommand sumCustomSetCommand = new SumCustomSetCommand(persistenceManager, cachingDiceEvaluator); + PoolTargetCommand poolTargetCommand = new PoolTargetCommand(persistenceManager); + RpgSystemCommandPreset rpgSystemCommandPreset = new RpgSystemCommandPreset(persistenceManager, customParameterCommand, fateCommand, customDiceCommand, countSuccessesCommand, sumCustomSetCommand, poolTargetCommand); + + WelcomeCommand welcomeCommand = new WelcomeCommand(persistenceManager, rpgSystemCommandPreset); + DiscordConnectorImpl.createAndStart(token, disableCommandUpdate, - ImmutableList.of( - new CountSuccessesCommand(persistenceManager), - new CustomDiceCommand(persistenceManager, cachingDiceEvaluator), - new FateCommand(persistenceManager), + ImmutableList.of(countSuccessesCommand, + customDiceCommand, + fateCommand, new DirectRollCommand(persistenceManager, cachingDiceEvaluator), new ValidationCommand(persistenceManager, cachingDiceEvaluator), new ChannelConfigCommand(persistenceManager), new SumDiceSetCommand(persistenceManager), - new SumCustomSetCommand(persistenceManager, cachingDiceEvaluator), + sumCustomSetCommand, new HoldRerollCommand(persistenceManager), - new PoolTargetCommand(persistenceManager), - new CustomParameterCommand(persistenceManager, cachingDiceEvaluator), - new WelcomeCommand(persistenceManager, cachingDiceEvaluator), + poolTargetCommand, + customParameterCommand, + welcomeCommand, new ClearCommand(persistenceManager), + new QuickstartCommand(rpgSystemCommandPreset), new HelpCommand() ), - new WelcomeCommand(persistenceManager, cachingDiceEvaluator).getWelcomeMessage(), + welcomeCommand.getWelcomeMessage(), allGuildIdsInPersistence); } diff --git a/bot/src/main/java/de/janno/discord/bot/command/BaseCommandOptions.java b/bot/src/main/java/de/janno/discord/bot/command/BaseCommandOptions.java index 46149bfa..fae35384 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/BaseCommandOptions.java +++ b/bot/src/main/java/de/janno/discord/bot/command/BaseCommandOptions.java @@ -15,13 +15,13 @@ import java.util.stream.Collectors; public final class BaseCommandOptions { - private static final String ANSWER_TARGET_CHANNEL_OPTION = "target_channel"; + public static final String ANSWER_TARGET_CHANNEL_OPTION = "target_channel"; public static final CommandDefinitionOption ANSWER_TARGET_CHANNEL_COMMAND_OPTION = CommandDefinitionOption.builder() .name(ANSWER_TARGET_CHANNEL_OPTION) .description("Another channel where the answer will be given") .type(CommandDefinitionOption.Type.CHANNEL) .build(); - private static final String ANSWER_FORMAT_OPTION = "answer_format"; + public static final String ANSWER_FORMAT_OPTION = "answer_format"; public static final CommandDefinitionOption ANSWER_FORMAT_COMMAND_OPTION = CommandDefinitionOption.builder() .name(ANSWER_FORMAT_OPTION) .description("How the answer will be displayed") @@ -33,7 +33,7 @@ public final class BaseCommandOptions { .build()) .collect(Collectors.toList())) .build(); - private static final String DICE_IMAGE_STYLE_OPTION = "dice_image_style"; + public static final String DICE_IMAGE_STYLE_OPTION = "dice_image_style"; public static final CommandDefinitionOption DICE_IMAGE_STYLE_COMMAND_OPTION = CommandDefinitionOption.builder() .name(DICE_IMAGE_STYLE_OPTION) .description("If and in what style the dice throw should be shown as image") @@ -45,7 +45,7 @@ public final class BaseCommandOptions { .build()) .collect(Collectors.toList())) .build(); - private static final String DICE_IMAGE_COLOR_OPTION = "dice_image_color"; + public static final String DICE_IMAGE_COLOR_OPTION = "dice_image_color"; public static final CommandDefinitionOption DICE_IMAGE_COLOR_COMMAND_OPTION = CommandDefinitionOption.builder() .name(DICE_IMAGE_COLOR_OPTION) .description("The default color option. Can be influenced by the `color` method") diff --git a/bot/src/main/java/de/janno/discord/bot/command/Config.java b/bot/src/main/java/de/janno/discord/bot/command/Config.java index 014b754b..fd8920e8 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/Config.java +++ b/bot/src/main/java/de/janno/discord/bot/command/Config.java @@ -12,6 +12,8 @@ import lombok.ToString; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * A configuration for a dice system. It will be created with the slash command and not modified afterwards. @@ -50,6 +52,17 @@ public String toShortString() { return String.format("[%s, %s, %s]", getTargetChannelShortString(), answerFormatType, diceStyleAndColor); } + public String toCommandOptionsString() { + List out = new ArrayList<>(); + out.add(String.format("%s: %s", BaseCommandOptions.ANSWER_FORMAT_OPTION, answerFormatType)); + out.add(String.format("%s: %s", BaseCommandOptions.DICE_IMAGE_STYLE_OPTION, diceStyleAndColor.getDiceImageStyle())); + out.add(String.format("%s: %s", BaseCommandOptions.DICE_IMAGE_COLOR_OPTION, diceStyleAndColor.getConfiguredDefaultColor())); + if (answerTargetChannelId != null) { + out.add(String.format("%s: <#%s>", BaseCommandOptions.ANSWER_TARGET_CHANNEL_OPTION, answerTargetChannelId)); + } + return String.join(" ", out); + } + protected String getTargetChannelShortString() { return answerTargetChannelId == null ? "local" : "target"; } diff --git a/bot/src/main/java/de/janno/discord/bot/command/WelcomeCommand.java b/bot/src/main/java/de/janno/discord/bot/command/WelcomeCommand.java deleted file mode 100644 index ef445bab..00000000 --- a/bot/src/main/java/de/janno/discord/bot/command/WelcomeCommand.java +++ /dev/null @@ -1,305 +0,0 @@ -package de.janno.discord.bot.command; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import de.janno.discord.bot.BotMetrics; -import de.janno.discord.bot.command.countSuccesses.CountSuccessesCommand; -import de.janno.discord.bot.command.countSuccesses.CountSuccessesConfig; -import de.janno.discord.bot.command.customDice.CustomDiceCommand; -import de.janno.discord.bot.command.customDice.CustomDiceConfig; -import de.janno.discord.bot.command.customParameter.CustomParameterCommand; -import de.janno.discord.bot.command.customParameter.CustomParameterConfig; -import de.janno.discord.bot.command.fate.FateCommand; -import de.janno.discord.bot.command.fate.FateConfig; -import de.janno.discord.bot.command.poolTarget.PoolTargetCommand; -import de.janno.discord.bot.command.poolTarget.PoolTargetConfig; -import de.janno.discord.bot.command.sumCustomSet.SumCustomSetCommand; -import de.janno.discord.bot.command.sumCustomSet.SumCustomSetConfig; -import de.janno.discord.bot.dice.CachingDiceEvaluator; -import de.janno.discord.bot.dice.DiceParserSystem; -import de.janno.discord.bot.dice.image.DiceImageStyle; -import de.janno.discord.bot.dice.image.DiceStyleAndColor; -import de.janno.discord.bot.persistance.MessageConfigDTO; -import de.janno.discord.bot.persistance.MessageDataDTO; -import de.janno.discord.bot.persistance.PersistenceManager; -import de.janno.discord.connector.api.BottomCustomIdUtils; -import de.janno.discord.connector.api.ButtonEventAdaptor; -import de.janno.discord.connector.api.message.ButtonDefinition; -import de.janno.discord.connector.api.message.ComponentRowDefinition; -import de.janno.discord.connector.api.message.EmbedOrMessageDefinition; -import de.janno.discord.connector.api.message.MessageDefinition; -import de.janno.discord.connector.api.slash.CommandInteractionOption; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.function.Supplier; - -@Slf4j -public class WelcomeCommand extends AbstractCommand { - - public static final String COMMAND_NAME = "welcome"; - private final static FateConfig FATE_CONFIG = new FateConfig(null, "with_modifier", AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final static CountSuccessesConfig NWOD_CONFIG = new CountSuccessesConfig(null, 10, 8, "no_glitch", 15, 1, Set.of(10), Set.of(), AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final static CountSuccessesConfig SHADOWRUN_CONFIG = new CountSuccessesConfig(null, 6, 5, "half_dice_one", 20, 1, Set.of(), Set.of(), AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final static PoolTargetConfig OWOD_CONFIG = new PoolTargetConfig(null, 10, 15, ImmutableSet.of(10), ImmutableSet.of(1), "ask", AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final static CustomDiceConfig COIN_CONFIG = new CustomDiceConfig(null, ImmutableList.of(new ButtonIdLabelAndDiceExpression("1_button", "Coin Toss \uD83E\uDE99", "1d[Head \uD83D\uDE00/Tail \uD83E\uDD85]")), DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final static CustomDiceConfig DND5_CONFIG_WITH_IMAGE = new CustomDiceConfig(null, ImmutableList.of( - new ButtonIdLabelAndDiceExpression("1_button", "D4", "1d4"), - new ButtonIdLabelAndDiceExpression("2_button", "D6", "1d6"), - new ButtonIdLabelAndDiceExpression("3_button", "D8", "1d8"), - new ButtonIdLabelAndDiceExpression("4_button", "D10", "1d10"), - new ButtonIdLabelAndDiceExpression("5_button", "D12", "1d12"), - new ButtonIdLabelAndDiceExpression("6_button", "D20", "1d20"), - new ButtonIdLabelAndDiceExpression("7_button", "D100", "1d100"), - new ButtonIdLabelAndDiceExpression("8_button", "D20 Advantage", "2d20k1="), - new ButtonIdLabelAndDiceExpression("9_button", "D20 Disadvantage", "2d20L1="), - new ButtonIdLabelAndDiceExpression("10_button", "2D4", "2d4="), - new ButtonIdLabelAndDiceExpression("11_button", "2D6", "2d6="), - new ButtonIdLabelAndDiceExpression("12_button", "2D8", "2d8="), - new ButtonIdLabelAndDiceExpression("13_button", "2D10", "2d10="), - new ButtonIdLabelAndDiceExpression("14_button", "2D12", "2d12="), - new ButtonIdLabelAndDiceExpression("15_button", "2D20", "2d20=") - ), DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); - private final static CustomDiceConfig DND5_CONFIG = new CustomDiceConfig(null, ImmutableList.of( - new ButtonIdLabelAndDiceExpression("1_button", "D4", "1d4"), - new ButtonIdLabelAndDiceExpression("2_button", "D6", "1d6"), - new ButtonIdLabelAndDiceExpression("3_button", "D8", "1d8"), - new ButtonIdLabelAndDiceExpression("4_button", "D10", "1d10"), - new ButtonIdLabelAndDiceExpression("5_button", "D12", "1d12"), - new ButtonIdLabelAndDiceExpression("6_button", "D20", "1d20"), - new ButtonIdLabelAndDiceExpression("7_button", "D100", "1d100"), - new ButtonIdLabelAndDiceExpression("8_button", "D20 Advantage", "2d20k1="), - new ButtonIdLabelAndDiceExpression("9_button", "D20 Disadvantage", "2d20L1="), - new ButtonIdLabelAndDiceExpression("10_button", "2D4", "2d4="), - new ButtonIdLabelAndDiceExpression("11_button", "2D6", "2d6="), - new ButtonIdLabelAndDiceExpression("12_button", "2D8", "2d8="), - new ButtonIdLabelAndDiceExpression("13_button", "2D10", "2d10="), - new ButtonIdLabelAndDiceExpression("14_button", "2D12", "2d12="), - new ButtonIdLabelAndDiceExpression("15_button", "2D20", "2d20=") - ), DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - private final SumCustomSetConfig DICE_CALCULATOR_CONFIG = new SumCustomSetConfig(null, List.of( - new ButtonIdLabelAndDiceExpression("1_button", "7", "7"), new ButtonIdLabelAndDiceExpression("2_button", "8", "8"), new ButtonIdLabelAndDiceExpression("3_button", "9", "9"), new ButtonIdLabelAndDiceExpression("5_button", "+", "+"), new ButtonIdLabelAndDiceExpression("6_button", "-", "-"), - new ButtonIdLabelAndDiceExpression("7_button", "4", "4"), new ButtonIdLabelAndDiceExpression("8_button", "5", "5"), new ButtonIdLabelAndDiceExpression("9_button", "6", "6"), new ButtonIdLabelAndDiceExpression("10_button", "d", "d"), new ButtonIdLabelAndDiceExpression("11_button", "k", "k"), - new ButtonIdLabelAndDiceExpression("12_button", "1", "1"), new ButtonIdLabelAndDiceExpression("13_button", "2", "2"), new ButtonIdLabelAndDiceExpression("14_button", "3", "3"), new ButtonIdLabelAndDiceExpression("15_button", "0", "0"), new ButtonIdLabelAndDiceExpression("16_button", "l", "l") - ), DiceParserSystem.DICE_EVALUATOR, true, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); - private final CustomParameterConfig FATE_WITH_IMAGE_CONFIG = new CustomParameterConfig(null, "4d[-1,0,1]+{Modifier:-4<=>10}=", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.fate, DiceImageStyle.fate.getDefaultColor())); - private final CachingDiceEvaluator cachingDiceEvaluator; - private final Supplier uuidSupplier; - - - public WelcomeCommand(PersistenceManager persistenceManager, CachingDiceEvaluator cachingDiceEvaluator) { - this(persistenceManager, cachingDiceEvaluator, UUID::randomUUID); - } - - @VisibleForTesting - public WelcomeCommand(PersistenceManager persistenceManager, CachingDiceEvaluator cachingDiceEvaluator, Supplier uuidSupplier) { - super(persistenceManager); - this.cachingDiceEvaluator = cachingDiceEvaluator; - this.uuidSupplier = uuidSupplier; - } - - @Override - protected ConfigAndState getMessageDataAndUpdateWithButtonValue(@NonNull MessageConfigDTO messageConfigDTO, - @NonNull MessageDataDTO messageDataDTO, - @NonNull String buttonValue, - @NonNull String invokingUserName) { - return new ConfigAndState<>(messageConfigDTO.getConfigUUID(), new Config(null, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())), new State<>(buttonValue, StateData.empty())); - } - - @Override - protected @NonNull Optional getMessageConfigDTO(@Nullable UUID configId, long channelId, long messageId) { - return Optional.of(new MessageConfigDTO(uuidSupplier.get(), null, channelId, getCommandId(), "None", "None")); - } - - @Override - protected boolean supportsResultImages() { - return false; - } - - @Override - protected boolean supportsAnswerFormat() { - return false; - } - - @Override - protected boolean supportsTargetChannel() { - return false; - } - - @Override - public Optional createMessageConfig(@NonNull UUID configUUID, long guildId, long channelId, @NonNull Config config) { - return Optional.empty(); - } - - @Override - public @NonNull String getCommandId() { - return COMMAND_NAME; - } - - @Override - protected @NonNull String getCommandDescription() { - return "Displays the welcome message"; - } - - @Override - protected @NonNull EmbedOrMessageDefinition getHelpMessage() { - return EmbedOrMessageDefinition.builder().descriptionOrContent("Displays the welcome message") - .field(new EmbedOrMessageDefinition.Field("Full documentation", "https://github.com/twonirwana/DiscordDiceBot", false)) - .field(new EmbedOrMessageDefinition.Field("Discord Server for Help and News", "https://discord.gg/e43BsqKpFr", false)) - .build(); - } - - @Override - protected @NonNull Optional createNewButtonMessageWithState(UUID configUUID, Config config, State state, long guildId, long channelId) { - BotMetrics.incrementButtonMetricCounter(COMMAND_NAME, "[" + state.getButtonValue() + "]"); - if (ButtonIds.isInvalid(state.getButtonValue())) { - return Optional.empty(); - } - UUID newConfigUUID = uuidSupplier.get(); - return switch (ButtonIds.valueOf(state.getButtonValue())) { - case fate -> { - FateCommand command = new FateCommand(persistenceManager); - command.createMessageConfig(newConfigUUID, guildId, channelId, FATE_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, FATE_CONFIG)); - } - case fate_image -> { - CustomParameterCommand command = new CustomParameterCommand(persistenceManager, cachingDiceEvaluator); - command.createMessageConfig(newConfigUUID, guildId, channelId, FATE_WITH_IMAGE_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, FATE_WITH_IMAGE_CONFIG)); - } - case dnd5 -> { - CustomDiceCommand command = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); - command.createMessageConfig(newConfigUUID, guildId, channelId, DND5_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, DND5_CONFIG)); - } - case dnd5_image -> { - CustomDiceCommand command = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); - command.createMessageConfig(newConfigUUID, guildId, channelId, DND5_CONFIG_WITH_IMAGE).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, DND5_CONFIG_WITH_IMAGE)); - } - case nWoD -> { - CountSuccessesCommand command = new CountSuccessesCommand(persistenceManager); - command.createMessageConfig(newConfigUUID, guildId, channelId, NWOD_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, NWOD_CONFIG)); - } - case oWoD -> { - PoolTargetCommand command = new PoolTargetCommand(persistenceManager); - command.createMessageConfig(newConfigUUID, guildId, channelId, OWOD_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, OWOD_CONFIG)); - } - case shadowrun -> { - CountSuccessesCommand command = new CountSuccessesCommand(persistenceManager); - command.createMessageConfig(newConfigUUID, guildId, channelId, SHADOWRUN_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, SHADOWRUN_CONFIG)); - } - case coin -> { - CustomDiceCommand command = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); - command.createMessageConfig(newConfigUUID, guildId, channelId, COIN_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, COIN_CONFIG)); - } - case dice_calculator -> { - SumCustomSetCommand command = new SumCustomSetCommand(persistenceManager, cachingDiceEvaluator); - command.createMessageConfig(newConfigUUID, guildId, channelId, DICE_CALCULATOR_CONFIG).ifPresent(persistenceManager::saveMessageConfig); - yield Optional.of(command.createNewButtonMessage(newConfigUUID, DICE_CALCULATOR_CONFIG)); - } - }; - } - - @Override - protected boolean shouldKeepExistingButtonMessage(@NonNull ButtonEventAdaptor event) { - return true; - } - - @Override - protected @NonNull Optional getAnswer(Config config, State state, long channelId, long userId) { - return Optional.empty(); - } - - public MessageDefinition getWelcomeMessage() { - return createNewButtonMessage(uuidSupplier.get(), null); - } - - @Override - public @NonNull MessageDefinition createNewButtonMessage(UUID configUUID, Config config) { - return MessageDefinition.builder() - .content(""" - Welcome to the Button Dice Bot, - use one of the example buttons below to start one of the RPG dice systems or use the slash command to configure your own custom dice system (see https://github.com/twonirwana/DiscordDiceBot for details or the slash command `/help`).\s - You can also use the slash command `/r` to directly roll dice with. - For help or feature request come to the support discord server: https://discord.gg/e43BsqKpFr""") - .componentRowDefinition(ComponentRowDefinition.builder() - .buttonDefinitions( - ImmutableList.of( - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.fate.name(), configUUID)) - .label("Fate") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.fate_image.name(), configUUID)) - .label("Fate with dice images") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dnd5.name(), configUUID)) - .label("D&D5e") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dnd5_image.name(), configUUID)) - .label("D&D5e with dice images") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.nWoD.name(), configUUID)) - .label("nWoD") - .build() - - ) - ) - .build()) - .componentRowDefinition(ComponentRowDefinition.builder() - .buttonDefinitions( - ImmutableList.of( - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.oWoD.name(), configUUID)) - .label("oWoD") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.shadowrun.name(), configUUID)) - .label("Shadowrun") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.coin.name(), configUUID)) - .label("Coin Toss \uD83E\uDE99") - .build(), - ButtonDefinition.builder() - .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dice_calculator.name(), configUUID)) - .label("Dice Calculator") - .build() - ) - ) - .build()) - .build(); - } - - @Override - protected @NonNull Config getConfigFromStartOptions(@NonNull CommandInteractionOption options) { - return new Config(null, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); - } - - private enum ButtonIds { - fate, - fate_image, - dnd5, - dnd5_image, - nWoD, - oWoD, - shadowrun, - coin, - dice_calculator; - - public static boolean isInvalid(String in) { - return Arrays.stream(ButtonIds.values()).noneMatch(s -> s.name().equals(in)); - } - } -} diff --git a/bot/src/main/java/de/janno/discord/bot/command/channelConfig/ChannelConfigCommand.java b/bot/src/main/java/de/janno/discord/bot/command/channelConfig/ChannelConfigCommand.java index 3968c181..f10cea20 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/channelConfig/ChannelConfigCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/channelConfig/ChannelConfigCommand.java @@ -35,10 +35,10 @@ public class ChannelConfigCommand implements SlashCommand { public static final String DIRECT_ROLL_CONFIG_TYPE_ID = "DirectRollConfig"; + static final String ALWAYS_SUM_RESULTS_COMMAND_OPTIONS_ID = "always_sum_result"; private static final String COMMAND_ID = "channel_config"; private static final String SAVE_DIRECT_ROLL_CONFIG_ACTION = "save_direct_roll_config"; private static final String DELETE_DIRECT_ROLL_CONFIG_ACTION = "delete_direct_roll_config"; - private static final String ALWAYS_SUM_RESULTS_COMMAND_OPTIONS_ID = "always_sum_result"; private static final String CHANNEL_ALIAS = "channel_alias"; private static final String USER_CHANNEL_ALIAS = "user_channel_alias"; private static final String SAVE_ALIAS_ACTION = "save"; diff --git a/bot/src/main/java/de/janno/discord/bot/command/channelConfig/DirectRollConfig.java b/bot/src/main/java/de/janno/discord/bot/command/channelConfig/DirectRollConfig.java index 84f1a2fe..4b5b02e1 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/channelConfig/DirectRollConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/channelConfig/DirectRollConfig.java @@ -31,4 +31,9 @@ public DirectRollConfig(@JsonProperty("answerTargetChannelId") Long answerTarget public String toShortString() { return "[%s, %s, %s, %s]".formatted(getTargetChannelShortString(), alwaysSumResult, getAnswerFormatType(), getDiceStyleAndColor()); } + + @Override + public String toCommandOptionsString() { + return "%s: %s %s".formatted(ChannelConfigCommand.ALWAYS_SUM_RESULTS_COMMAND_OPTIONS_ID, alwaysSumResult, super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesCommand.java b/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesCommand.java index 7ece6ded..97876f97 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesCommand.java @@ -36,15 +36,15 @@ public class CountSuccessesCommand extends AbstractCommand { static final String SUBSET_DELIMITER = ";"; + static final String ACTION_SIDE_OPTION = "dice_sides"; + static final String ACTION_TARGET_OPTION = "target_number"; + static final String ACTION_MAX_DICE_OPTION = "max_dice"; + static final String ACTION_MIN_DICE_COUNT_OPTION = "min_dice_count"; + static final String ACTION_REROLL_SET_OPTION = "reroll_set"; + static final String ACTION_BOTCH_SET_OPTION = "botch_set"; + static final String ACTION_GLITCH_OPTION = "glitch"; private static final String COMMAND_NAME = "count_successes"; - private static final String ACTION_SIDE_OPTION = "dice_sides"; - private static final String ACTION_TARGET_OPTION = "target_number"; - private static final String ACTION_MAX_DICE_OPTION = "max_dice"; - private static final String ACTION_MIN_DICE_COUNT_OPTION = "min_dice_count"; - private static final String ACTION_REROLL_SET_OPTION = "reroll_set"; - private static final String ACTION_BOTCH_SET_OPTION = "botch_set"; private static final long MAX_NUMBER_OF_DICE = 25; - private static final String ACTION_GLITCH_OPTION = "glitch"; private static final long MAX_NUMBER_SIDES_OR_TARGET_NUMBER = 1000; private static final String GLITCH_OPTION_HALF_ONES = "half_dice_one"; private static final String GLITCH_NO_OPTION = "no_glitch"; diff --git a/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesConfig.java b/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesConfig.java index af305f99..40e037cc 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/countSuccesses/CountSuccessesConfig.java @@ -12,6 +12,8 @@ import lombok.NonNull; import lombok.ToString; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -68,4 +70,21 @@ public String toShortString() { getDiceStyleAndColor() ).toList().toString(); } + + @Override + public String toCommandOptionsString() { + List out = new ArrayList<>(); + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_SIDE_OPTION, diceSides)); + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_TARGET_OPTION, target)); + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_GLITCH_OPTION, glitchOption)); + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_MAX_DICE_OPTION, maxNumberOfButtons)); + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_MIN_DICE_COUNT_OPTION, minDiceCount)); + if (!rerollSet.isEmpty()) { + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_REROLL_SET_OPTION, rerollSet.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")))); + } + if (!botchSet.isEmpty()) { + out.add(String.format("%s: %s", CountSuccessesCommand.ACTION_BOTCH_SET_OPTION, botchSet.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")))); + } + return "%s %s".formatted(String.join(" ", out), super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java index dd681158..265e1747 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java @@ -32,8 +32,8 @@ @Slf4j public class CustomDiceCommand extends AbstractCommand { + static final String BUTTONS_COMMAND_OPTIONS_ID = "buttons"; private static final String COMMAND_NAME = "custom_dice"; - private static final String BUTTONS_COMMAND_OPTIONS_ID = "buttons"; private static final String LABEL_DELIMITER = "@"; private static final String BUTTON_MESSAGE = "Click on a button to roll the dice"; private static final String CONFIG_TYPE_ID = "CustomDiceConfig"; diff --git a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceConfig.java b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceConfig.java index 580b25b7..8be30841 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceConfig.java @@ -45,4 +45,17 @@ public String toShortString() { return "[%s, %s, %s, %s, %s]".formatted(buttons, getTargetChannelShortString(), diceParserSystem, getAnswerFormatType(), getDiceStyleAndColor()); } + + @Override + public String toCommandOptionsString() { + String buttons = buttonIdLabelAndDiceExpressions.stream() + .map(b -> { + if (b.getDiceExpression().equals(b.getLabel())) { + return b.getDiceExpression(); + } + return "%s@%s".formatted(b.getDiceExpression(), b.getLabel()); + }) + .collect(Collectors.joining(";")); + return "%s: %s %s".formatted(CustomDiceCommand.BUTTONS_COMMAND_OPTIONS_ID, String.join(" ", buttons), super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterCommand.java b/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterCommand.java index 96cebfc0..9402070f 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterCommand.java @@ -39,11 +39,11 @@ public class CustomParameterCommand extends AbstractCommand(-?\\d+)"); diff --git a/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterConfig.java b/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterConfig.java index ed55c604..921c4dca 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/customParameter/CustomParameterConfig.java @@ -44,4 +44,9 @@ public CustomParameterConfig( public String toShortString() { return "[%s, %s, %s, %s, %s]".formatted(baseExpression, getTargetChannelShortString(), diceParserSystem, getAnswerFormatType(), getDiceStyleAndColor()); } + + @Override + public String toCommandOptionsString() { + return "%s: %s %s".formatted(CustomParameterCommand.EXPRESSION_OPTION, baseExpression, super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/fate/FateCommand.java b/bot/src/main/java/de/janno/discord/bot/command/fate/FateCommand.java index 2f79491f..32c62c65 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/fate/FateCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/fate/FateCommand.java @@ -29,8 +29,8 @@ @Slf4j public class FateCommand extends AbstractCommand { + static final String ACTION_MODIFIER_OPTION = "type"; private static final String COMMAND_NAME = "fate"; - private static final String ACTION_MODIFIER_OPTION = "type"; private static final String ACTION_MODIFIER_OPTION_SIMPLE = "simple"; private static final String ACTION_MODIFIER_OPTION_MODIFIER = "with_modifier"; private static final String ROLL_BUTTON_ID = "roll"; diff --git a/bot/src/main/java/de/janno/discord/bot/command/fate/FateConfig.java b/bot/src/main/java/de/janno/discord/bot/command/fate/FateConfig.java index 30b1a632..9965c148 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/fate/FateConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/fate/FateConfig.java @@ -32,4 +32,9 @@ public FateConfig(@JsonProperty("answerTargetChannelId") Long answerTargetChanne public String toShortString() { return String.format("[%s, %s, %s, %s]", type, getTargetChannelShortString(), getAnswerFormatType(), getDiceStyleAndColor()); } + + @Override + public String toCommandOptionsString() { + return "%s: %s %s".formatted(FateCommand.ACTION_MODIFIER_OPTION, type, super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/HelpCommand.java b/bot/src/main/java/de/janno/discord/bot/command/help/HelpCommand.java similarity index 97% rename from bot/src/main/java/de/janno/discord/bot/command/HelpCommand.java rename to bot/src/main/java/de/janno/discord/bot/command/help/HelpCommand.java index b32d8b3e..218031cc 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/HelpCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/help/HelpCommand.java @@ -1,4 +1,4 @@ -package de.janno.discord.bot.command; +package de.janno.discord.bot.command.help; import de.janno.discord.bot.BotMetrics; import de.janno.discord.connector.api.SlashCommand; diff --git a/bot/src/main/java/de/janno/discord/bot/command/help/QuickstartCommand.java b/bot/src/main/java/de/janno/discord/bot/command/help/QuickstartCommand.java new file mode 100644 index 00000000..95d283b3 --- /dev/null +++ b/bot/src/main/java/de/janno/discord/bot/command/help/QuickstartCommand.java @@ -0,0 +1,93 @@ +package de.janno.discord.bot.command.help; + +import com.google.common.base.Stopwatch; +import de.janno.discord.bot.BotMetrics; +import de.janno.discord.connector.api.AutoCompleteAnswer; +import de.janno.discord.connector.api.AutoCompleteRequest; +import de.janno.discord.connector.api.SlashCommand; +import de.janno.discord.connector.api.SlashEventAdaptor; +import de.janno.discord.connector.api.slash.CommandDefinition; +import de.janno.discord.connector.api.slash.CommandDefinitionOption; +import de.janno.discord.connector.api.slash.CommandInteractionOption; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +public class QuickstartCommand implements SlashCommand { + + public static final String ROLL_COMMAND_ID = "quickstart"; + protected static final String ACTION_SYSTEM = "system"; + private final RpgSystemCommandPreset rpgSystemCommandPreset; + + public QuickstartCommand(RpgSystemCommandPreset rpgSystemCommandPreset) { + this.rpgSystemCommandPreset = rpgSystemCommandPreset; + } + + + + @Override + public @NonNull List getAutoCompleteAnswer(AutoCompleteRequest option) { + if (!ACTION_SYSTEM.equals(option.getFocusedOptionName())) { + return List.of(); + } + return Arrays.stream(RpgSystemCommandPreset.PresetId.values()) + .filter(p -> Stream.concat(Stream.of(p.getDisplayName()), p.getSynonymes().stream()) + .anyMatch(n -> n.toLowerCase().contains(option.getFocusedOptionValue().toLowerCase()))) + .sorted(Comparator.comparing(RpgSystemCommandPreset.PresetId::getDisplayName)) + .map(p -> new AutoCompleteAnswer(p.getDisplayName(), p.name())) + .collect(Collectors.toList()); + } + + @Override + public @NonNull String getCommandId() { + return ROLL_COMMAND_ID; + } + + @Override + public @NonNull CommandDefinition getCommandDefinition() { + return CommandDefinition.builder() + .name(getCommandId()) + .description("Select a preconfigured dice set from a given systems") + .option(CommandDefinitionOption.builder() + .name(ACTION_SYSTEM) + .required(true) + .autoComplete(true) + .description("Start typing to filter and see more options") + .type(CommandDefinitionOption.Type.STRING) + .build()) + .build(); + } + + @Override + public @NonNull Mono handleSlashCommandEvent(@NonNull SlashEventAdaptor event, @NonNull Supplier uuidSupplier) { + Stopwatch stopwatch = Stopwatch.createStarted(); + + Optional checkPermissions = event.checkPermissions(); + if (checkPermissions.isPresent()) { + return event.reply(checkPermissions.get(), false); + } + + final UUID newConfigUUID = uuidSupplier.get(); + final long guildId = event.getGuildId(); + final long channelId = event.getChannelId(); + Optional expressionOptional = event.getOption(ACTION_SYSTEM); + if (expressionOptional.isPresent()) { + final String systemId = expressionOptional + .map(CommandInteractionOption::getStringValue) + .orElseThrow(); + final RpgSystemCommandPreset.PresetId presetId = RpgSystemCommandPreset.PresetId.valueOf(systemId); + RpgSystemCommandPreset.CommandAndMessageDefinition commandAndMessageDefinition = rpgSystemCommandPreset.createMessage(presetId, newConfigUUID, guildId, channelId); + return Mono.defer(() -> event.createButtonMessage(commandAndMessageDefinition.getMessageDefinition())) + .doOnSuccess(v -> BotMetrics.timerNewButtonMessageMetricCounter(getCommandId(), stopwatch.elapsed())) + .then(event.reply("`%s`".formatted(commandAndMessageDefinition.getCommand()), false)); + + } + return Mono.empty(); + } +} \ No newline at end of file diff --git a/bot/src/main/java/de/janno/discord/bot/command/help/RpgSystemCommandPreset.java b/bot/src/main/java/de/janno/discord/bot/command/help/RpgSystemCommandPreset.java new file mode 100644 index 00000000..944199fc --- /dev/null +++ b/bot/src/main/java/de/janno/discord/bot/command/help/RpgSystemCommandPreset.java @@ -0,0 +1,271 @@ +package de.janno.discord.bot.command.help; + +import com.google.common.collect.ImmutableSet; +import de.janno.discord.bot.command.AbstractCommand; +import de.janno.discord.bot.command.AnswerFormatType; +import de.janno.discord.bot.command.ButtonIdLabelAndDiceExpression; +import de.janno.discord.bot.command.Config; +import de.janno.discord.bot.command.countSuccesses.CountSuccessesCommand; +import de.janno.discord.bot.command.countSuccesses.CountSuccessesConfig; +import de.janno.discord.bot.command.customDice.CustomDiceCommand; +import de.janno.discord.bot.command.customDice.CustomDiceConfig; +import de.janno.discord.bot.command.customParameter.CustomParameterCommand; +import de.janno.discord.bot.command.customParameter.CustomParameterConfig; +import de.janno.discord.bot.command.fate.FateCommand; +import de.janno.discord.bot.command.fate.FateConfig; +import de.janno.discord.bot.command.poolTarget.PoolTargetCommand; +import de.janno.discord.bot.command.poolTarget.PoolTargetConfig; +import de.janno.discord.bot.command.sumCustomSet.SumCustomSetCommand; +import de.janno.discord.bot.command.sumCustomSet.SumCustomSetConfig; +import de.janno.discord.bot.dice.DiceParserSystem; +import de.janno.discord.bot.dice.image.DiceImageStyle; +import de.janno.discord.bot.dice.image.DiceStyleAndColor; +import de.janno.discord.bot.persistance.PersistenceManager; +import de.janno.discord.connector.api.message.MessageDefinition; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Value; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + + +public class RpgSystemCommandPreset { + + private final PersistenceManager persistenceManager; + private final CustomParameterCommand customParameterCommand; + private final FateCommand fateCommand; + private final CustomDiceCommand customDiceCommand; + private final CountSuccessesCommand countSuccessesCommand; + private final SumCustomSetCommand sumCustomSetCommand; + private final PoolTargetCommand poolTargetCommand; + + public RpgSystemCommandPreset(PersistenceManager persistenceManager, + CustomParameterCommand customParameterCommand, + FateCommand fateCommand, + CustomDiceCommand customDiceCommand, + CountSuccessesCommand countSuccessesCommand, + SumCustomSetCommand sumCustomSetCommand, + PoolTargetCommand poolTargetCommand) { + this.persistenceManager = persistenceManager; + this.customParameterCommand = customParameterCommand; + this.fateCommand = fateCommand; + this.customDiceCommand = customDiceCommand; + this.countSuccessesCommand = countSuccessesCommand; + this.sumCustomSetCommand = sumCustomSetCommand; + this.poolTargetCommand = poolTargetCommand; + } + + public CommandAndMessageDefinition createMessage(PresetId presetId, UUID newConfigUUID, long guildId, long channelId) { + return switch (presetId) { + case FATE -> { + FateConfig config = new FateConfig(null, "with_modifier", AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, fateCommand, newConfigUUID, guildId, channelId); + } + case FATE_IMAGE -> { + CustomParameterConfig config = new CustomParameterConfig(null, "4d[-1,0,1]+{Modifier:-4<=>10}=", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.fate, DiceImageStyle.fate.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case DND5 -> { + CustomDiceConfig config = new CustomDiceConfig(null, string2ButtonIdLabelAndDiceExpression("1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20"), DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case DND5_IMAGE -> { + CustomDiceConfig config = new CustomDiceConfig(null, string2ButtonIdLabelAndDiceExpression("1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20"), + DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case NWOD -> { + CountSuccessesConfig config = new CountSuccessesConfig(null, 10, 8, "no_glitch", 15, 1, Set.of(10), Set.of(), AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, countSuccessesCommand, newConfigUUID, guildId, channelId); + + } + case OWOD -> { + PoolTargetConfig config = new PoolTargetConfig(null, 10, 15, ImmutableSet.of(10), ImmutableSet.of(1), "ask", AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, poolTargetCommand, newConfigUUID, guildId, channelId); + } + case SHADOWRUN -> { + CountSuccessesConfig config = new CountSuccessesConfig(null, 6, 5, "half_dice_one", 20, 1, Set.of(), Set.of(), AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, countSuccessesCommand, newConfigUUID, guildId, channelId); + } + case COIN -> { + CustomDiceConfig config = new CustomDiceConfig(null, List.of(new ButtonIdLabelAndDiceExpression("1_button", "Coin Toss \uD83E\uDE99", "1d[Head \uD83D\uDE00/Tail \uD83E\uDD85]")), DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case DICE_CALCULATOR -> { + ///sum_custom_set start buttons: 7;8;9;+;-;4;5;6;d;k;1;2;3;0;l always_sum_result: true + SumCustomSetConfig config = new SumCustomSetConfig(null, string2ButtonIdLabelAndDiceExpression("7;8;9;+;-;4;5;6;d;k;1;2;3;0;l"), + DiceParserSystem.DICE_EVALUATOR, true, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, sumCustomSetCommand, newConfigUUID, guildId, channelId); + } + case OSR -> { + //1d20@D20;1d6@D6;2d6@2D6;1d4@D4;1d8@D8;3d6=,3d6=,3d6=,3d6=,3d6=,3d6=@Stats;(3d6=)*10@Gold;1d100@D100;1d10@D10;1d12@D12 + CustomDiceConfig config = new CustomDiceConfig(null, + string2ButtonIdLabelAndDiceExpression("1d20@D20;1d6@D6;2d6@2D6;1d4@D4;1d8@D8;3d6=,3d6=,3d6=,3d6=,3d6=,3d6=@Stats;(3d6=)*10@Gold;1d100@D100;1d10@D10;1d12@D12"), + DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case TRAVELLER -> { + //sum_custom_set start buttons:+2d6;+(3d6k2)@Boon;+(3d6l2)@Bane;+1d6;+1;+2;+3;+4;-1;-2;-3;-4 + SumCustomSetConfig config = new SumCustomSetConfig(null, + string2ButtonIdLabelAndDiceExpression("+2d6;+(3d6k2)@Boon;+(3d6l2)@Bane;+1d6;+1;+2;+3;+4;-1;-2;-3;-4"), + DiceParserSystem.DICE_EVALUATOR, true, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, sumCustomSetCommand, newConfigUUID, guildId, channelId); + } + case SAVAGE_WORLDS -> { + ///custom_parameter start expression: (d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20} + {Type: 0@Regular/1d!!6@Wildcard})k1 + CustomParameterConfig config = new CustomParameterConfig(null, "(d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20} + {Type: 0@Regular/1d!!6@Wildcard})k1", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case BLADES_IN_THE_DARK -> { + // /custom_dice start buttons: ifE(2d[0/0/0/1/1/3]l1,3,'Success',1,'Partial','Failure')@Zero;ifE(1d[0/0/0/1/1/3],3,'Success',1,'Partial','Failure')@1d6;ifG(2d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@2d6;ifG(3d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@3d6;ifG(4d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@4d6;ifG(5d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@5d6;ifG(6d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@6d6;ifG(7d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@7d6 + CustomDiceConfig config = new CustomDiceConfig(null, + string2ButtonIdLabelAndDiceExpression("ifE(2d[0/0/0/1/1/3]l1,3,'Success',1,'Partial','Failure')@Zero;ifE(1d[0/0/0/1/1/3],3,'Success',1,'Partial','Failure')@1d6;ifG(2d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@2d6;ifG(3d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@3d6;ifG(4d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@4d6;ifG(5d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@5d6;ifG(6d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@6d6;ifG(7d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@7d6") + , DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case CALL_OF_CTHULHU_7ED -> { + //7th Edition Call of Cthulhu: custom_dice start buttons: 1d100; 2d100L1@1d100 Advantage; 2d100K1@1d100 Penalty; 1d3; 1d4; 1d6; 1d8; 1d10; 1d12; 1d20; 3d6 + CustomDiceConfig config = new CustomDiceConfig(null, string2ButtonIdLabelAndDiceExpression("1d100;2d100L1@1d100 Advantage; 2d100K1@1d100 Penalty; 1d3; 1d4; 1d6; 1d8; 1d10; 1d12; 1d20; 3d6"), + DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case EXALTED_3ED -> { + //Exalted 3rd /custom_parameter start expression: val('$1', cancel(double({number of dice}d10,10),1,[7/8/9/10])), ifE(('$1'>=7)c,0,ifG(('$1'<=1)c,0,'Botch')) + CustomParameterConfig config = new CustomParameterConfig(null, "val('$1', cancel(double({number of dice}d10,10),1,[7/8/9/10])), ifE(('$1'>=7)c,0,ifG(('$1'<=1)c,0,'Botch'))", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case VAMPIRE_5ED -> { + //Vampire 5ed /custom_parameter start expression: val('$r',{regular dice:1<=>16}d10 col 'blue') val('$h',{hunger dice:0<=>5}d10 col 'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression + CustomParameterConfig config = new CustomParameterConfig(null, "val('$r',{regular dice:1<=>16}d10 col 'blue') val('$h',{hunger dice:0<=>5}d10 col 'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), ''))", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_knots, DiceImageStyle.polyhedral_knots.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case HUNTER_5ED -> { + //Hunter 5ed /custom_parameter start expression: val('$r',{Regular D10 Dice:1<=>16}d10 col 'blue') val('$h', {Desperation Dice:0<=>5}d10 col'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression dice_image_style: polyhedral_knots + CustomParameterConfig config = new CustomParameterConfig(null, "val('$r',{Regular D10 Dice:1<=>16}d10 col 'blue') val('$h', {Desperation Dice:0<=>5}d10 col'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), ''))", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_knots, DiceImageStyle.polyhedral_knots.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case ONE_ROLL_ENGINE -> { + //One-Roll Engine /custom_parameter start expression: groupc({Number of Dice:1<=>10}d10+({Number of Extra Die:0@0/10@1/2r10@2/3r10@3/4r10@4})>={Difficulty:1<=>10}) + CustomParameterConfig config = new CustomParameterConfig(null, "groupc({Number of Dice:1<=>10}d10+({Number of Extra Die:0@0/10@1/2r10@2/3r10@3/4r10@4})>={Difficulty:1<=>10})", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case DUNGEON_CRAWL_CLASSICS -> { + //Dungeon Crawl Classics /sum_custom_set start buttons: 1d4;1d6;1d7;1d8;1d10;1d12;1d14;1d16;1d20;1d24;1d16;1d30;1d100;+1;+2;+3;+4;+5;-1;-2;-3;-4;-5 + SumCustomSetConfig config = new SumCustomSetConfig(null, string2ButtonIdLabelAndDiceExpression("+1d4;+1d6;+1d7;+1d8;+1d10;+1d12;+1d14;+1d16;+1d20;+1d24;+1d16;+1d30;+1d100;+1;+2;+3;+4;-1;-2;-3;-4") + , DiceParserSystem.DICE_EVALUATOR, true, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + yield startPreset(config, sumCustomSetCommand, newConfigUUID, guildId, channelId); + } + case TINY_D6 -> { + // Tiny D6 /custom_dice start buttons: ifG(1d6>=5c,0,'Success','Failure')@Disadvantage; ifG(2d6>=5c,0,'Success','Failure')@Test;ifG(3d6>=5c,0,'Success','Failure')@Advantage answer_format: without_expression + CustomDiceConfig config = new CustomDiceConfig(null, string2ButtonIdLabelAndDiceExpression("ifG(1d6>=5c,0,'Success','Failure')@Disadvantage; ifG(2d6>=5c,0,'Success','Failure')@Test;ifG(3d6>=5c,0,'Success','Failure')@Advantage"), + DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + case CYBERPUNK_RED -> { + //Cyberpunk Red /custom_parameter start expression: 9}+{skill:0<=>9}=> + CustomParameterConfig config = new CustomParameterConfig(null, "val('$roll', 1d10) ifE('$roll', 1, '$roll'-1d10, 10, '$roll'+1d10, '$roll')+{ability:0<=>9}+{skill:0<=>9}=", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case ASOIAF -> { + //A Song of Ice and Fire /custom_parameter start expression: {numberOfDice:1<=>15}d6k{keep:1<=>10} + CustomParameterConfig config = new CustomParameterConfig(null, "{numberOfDice:1<=>15}d6k{keep:1<=>10}", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case CITY_OF_MIST -> { + //City of mist /custom_parameter start expression: 2d6+{modifier:-6<=>6} + CustomParameterConfig config = new CustomParameterConfig(null, "2d6+{modifier:-6<=>6}", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case RISUS -> { + //Risus The Anything RPG "Evens Up" optional rule /custom_parameter start expression: val('$r',{numberOfDice:1<=>10}d!6) ('$r'==2c) + ('$r'==4c) + ('$r'==6c)= + CustomParameterConfig config = new CustomParameterConfig(null, "val('$r',{numberOfDice:1<=>10}d!6) ('$r'==2c) + ('$r'==4c) + ('$r'==6c)=", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case KIDS_ON_BROOMS -> { + //Kids on Brooms: /custom_parameter start expression: (d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20/4+d!!4@D4Spell/6+d!!4@D6Spell/8+d!!4@D8Spell/12+d!!4@D12Spell/20+d!!4@D20Spell} {plus or minus:+/-}{Modifier:0<=>15}=)-{Difficulty:1<=>30}= + CustomParameterConfig config = new CustomParameterConfig(null, "(d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20/4+d!!4@D4Spell/6+d!!4@D6Spell/8+d!!4@D8Spell/12+d!!4@D12Spell/20+d!!4@D20Spell} {plus or minus:+/-}{Modifier:0<=>15}=)-{Difficulty:1<=>30}=", DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, customParameterCommand, newConfigUUID, guildId, channelId); + } + case DND5_CALC -> { + //Alternate 5e Calculating setup: /sum_custom_set start buttons: d4;d6;d8;d10;d12;d20;d100;1;2;3;4;5;k@Keep Highest;L@Keep Lowest;(2d20k1)@D20 Advantage;(2d20L1)@D20 Disadvantage;-@Minus;+@Plus;(4r(d6))k3=@Stats;,@Split;[Muliple dice can be rolled using Number then die type. Plus Minus can be used to add modifiers If adding a modifier to an Advantage or disadvantage roll those buttons must be used. Keep Highest Lowest only work correctly with multiple dice of the same type. If you want to roll dice added together use Plus between each die type. This is a work around to give some guidance. Bot is not supposed to work this way.]@Help always_sum_result: true answer_format: full dice_image_style: polyhedral_alies_v2 dice_image_color: orange_and_silver + SumCustomSetConfig config = new SumCustomSetConfig(null, string2ButtonIdLabelAndDiceExpression("d4;d6;d8;d10;d12;d20;d100;1;2;3;4;5;k@Keep Highest;L@Keep Lowest;(2d20k1)@D20 Advantage;(2d20L1)@D20 Disadvantage;-@Minus;+@Plus;(4r(d6))k3=@Stats;,@Split") + , DiceParserSystem.DICE_EVALUATOR, true, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, DiceImageStyle.polyhedral_3d.getDefaultColor())); + yield startPreset(config, sumCustomSetCommand, newConfigUUID, guildId, channelId); + } + case REVE_DE_DRAGON -> { + // Rêve de Dragon /custom_dice start buttons: 1d4@D4;1d6@D6;2d6=@2D6;1d7@D7;1d8@D8;val('roll',1d!8 col 'special') val('diceCount','roll' c) 'roll'-'diceCount'+7=@DDR;2d10=@2D10;1d12@D12;val('$r',1d12 col 'special'),if('$r'=?1,'vaisseau','$r'=?2,'sirène','$r'=?3,'faucon','$r'=?4,'couronne','$r'=?5,'dragon','$r'=?6,'épées','$r'=?7,'lyre','$r'=?8,'serpent','$r'=?9,'poisson acrobate','$r'=?10,'araignée','$r'=?11,'roseaux','$r'=?12,'château dormant')@DAS;1d20@D20;1d100@D100 answer_format: without_expression dice_image_style: polyhedral_RdD + CustomDiceConfig config = new CustomDiceConfig(null, string2ButtonIdLabelAndDiceExpression("1d4@D4;1d6@D6;2d6=@2D6;1d7@D7;1d8@D8;val('roll',1d!8 col 'special') val('diceCount','roll' c) 'roll'-'diceCount'+7=@DDR;2d10=@2D10;1d12@D12;val('$r',1d12 col 'special'),if('$r'=?1,'vaisseau','$r'=?2,'sirène','$r'=?3,'faucon','$r'=?4,'couronne','$r'=?5,'dragon','$r'=?6,'épées','$r'=?7,'lyre','$r'=?8,'serpent','$r'=?9,'poisson acrobate','$r'=?10,'araignée','$r'=?11,'roseaux','$r'=?12,'château dormant')@DAS;1d20@D20;1d100@D100"), + DiceParserSystem.DICE_EVALUATOR, AnswerFormatType.without_expression, null, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, DiceImageStyle.polyhedral_RdD.getDefaultColor())); + yield startPreset(config, customDiceCommand, newConfigUUID, guildId, channelId); + } + }; + } + + private CommandAndMessageDefinition startPreset(C config, AbstractCommand command, UUID newConfigUUID, long guildId, long channelId) { + String commandString = "/%s %s".formatted(customDiceCommand.getCommandId(), config.toCommandOptionsString()); + command.createMessageConfig(newConfigUUID, guildId, channelId, config).ifPresent(persistenceManager::saveMessageConfig); + return new CommandAndMessageDefinition(commandString, command.createNewButtonMessage(newConfigUUID, config)); + } + + private List string2ButtonIdLabelAndDiceExpression(String buttons) { + AtomicLong buttonIdCounter = new AtomicLong(1); + return Arrays.stream(buttons.split(";")) + .map(s -> { + if (s.contains("@")) { + String[] expressionLabel = s.split("@"); + return new ButtonIdLabelAndDiceExpression(buttonIdCounter.getAndIncrement() + "_button", expressionLabel[1].trim(), expressionLabel[0].trim()); + } + return new ButtonIdLabelAndDiceExpression(buttonIdCounter.getAndIncrement() + "_button", s.trim(), s.trim()); + + }) + .collect(Collectors.toList()); + } + + @Getter + @AllArgsConstructor + public enum PresetId { + DND5_IMAGE("Dungeon & Dragons 5e with Dice Images", List.of("DnD", "D&D")), + DND5("Dungeon & Dragons 5e", List.of("DnD", "D&D")), + DND5_CALC("Dungeon & Dragons 5e Calculator", List.of("DnD", "D&D")), + NWOD("nWod / Chronicles of Darkness", List.of("World of Darkness")), + OWOD("oWod / Storyteller System", List.of("World of Darkness")), + SHADOWRUN("Shadowrun", List.of()), + SAVAGE_WORLDS("Savage Worlds", List.of()), + FATE_IMAGE("Fate with Dice Images", List.of("Fudge")), + FATE("Fate", List.of("Fudge")), + COIN("Coin Toss", List.of()), + DICE_CALCULATOR("Dice Calculator", List.of()), + OSR("OSR", List.of("Old School Renaissance")), + TRAVELLER("Traveller", List.of()), + BLADES_IN_THE_DARK("Blades in the Dark", List.of()), + CALL_OF_CTHULHU_7ED("Call of Cthulhu 7th Edition", List.of()), + EXALTED_3ED("Exalted 3ed", List.of()), + VAMPIRE_5ED("Vampire 5ed", List.of()), + HUNTER_5ED("Hunter 5ed", List.of()), + ONE_ROLL_ENGINE("One-Roll Engine", List.of()), + DUNGEON_CRAWL_CLASSICS("Dungeon Crawl Classics", List.of()), + TINY_D6("Tiny D6", List.of()), + CYBERPUNK_RED("Cyberpunk Red", List.of()), + ASOIAF("A Song of Ice and Fire", List.of("ASOIAF")), + CITY_OF_MIST("City of Mist", List.of()), + RISUS("Risus The Anything RPG \"Evens Up\"", List.of()), + KIDS_ON_BROOMS("Kids on Brooms", List.of()), + REVE_DE_DRAGON("Rêve de Dragon", List.of("Reve de Dragon")); + private final String displayName; + private final List synonymes; + } + + @Value + public static class CommandAndMessageDefinition { + @NonNull + String command; + @NonNull + MessageDefinition messageDefinition; + } +} diff --git a/bot/src/main/java/de/janno/discord/bot/command/help/WelcomeCommand.java b/bot/src/main/java/de/janno/discord/bot/command/help/WelcomeCommand.java new file mode 100644 index 00000000..0b897e04 --- /dev/null +++ b/bot/src/main/java/de/janno/discord/bot/command/help/WelcomeCommand.java @@ -0,0 +1,221 @@ +package de.janno.discord.bot.command.help; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import de.janno.discord.bot.BotMetrics; +import de.janno.discord.bot.command.*; +import de.janno.discord.bot.dice.image.DiceImageStyle; +import de.janno.discord.bot.dice.image.DiceStyleAndColor; +import de.janno.discord.bot.persistance.MessageConfigDTO; +import de.janno.discord.bot.persistance.MessageDataDTO; +import de.janno.discord.bot.persistance.PersistenceManager; +import de.janno.discord.connector.api.BottomCustomIdUtils; +import de.janno.discord.connector.api.ButtonEventAdaptor; +import de.janno.discord.connector.api.message.ButtonDefinition; +import de.janno.discord.connector.api.message.ComponentRowDefinition; +import de.janno.discord.connector.api.message.EmbedOrMessageDefinition; +import de.janno.discord.connector.api.message.MessageDefinition; +import de.janno.discord.connector.api.slash.CommandInteractionOption; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Supplier; + +@Slf4j +public class WelcomeCommand extends AbstractCommand { + + public static final String COMMAND_NAME = "welcome"; + private final Supplier uuidSupplier; + private final RpgSystemCommandPreset rpgSystemCommandPreset; + + public WelcomeCommand(PersistenceManager persistenceManager, RpgSystemCommandPreset rpgSystemCommandPreset) { + this(persistenceManager, rpgSystemCommandPreset, UUID::randomUUID); + } + + @VisibleForTesting + public WelcomeCommand(PersistenceManager persistenceManager, RpgSystemCommandPreset rpgSystemCommandPreset, Supplier uuidSupplier) { + super(persistenceManager); + this.uuidSupplier = uuidSupplier; + this.rpgSystemCommandPreset = rpgSystemCommandPreset; + } + + @Override + protected ConfigAndState getMessageDataAndUpdateWithButtonValue(@NonNull MessageConfigDTO messageConfigDTO, + @NonNull MessageDataDTO messageDataDTO, + @NonNull String buttonValue, + @NonNull String invokingUserName) { + return new ConfigAndState<>(messageConfigDTO.getConfigUUID(), new Config(null, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())), new State<>(buttonValue, StateData.empty())); + } + + @Override + protected @NonNull Optional getMessageConfigDTO(@Nullable UUID configId, long channelId, long messageId) { + return Optional.of(new MessageConfigDTO(uuidSupplier.get(), null, channelId, getCommandId(), "None", "None")); + } + + @Override + protected boolean supportsResultImages() { + return false; + } + + @Override + protected boolean supportsAnswerFormat() { + return false; + } + + @Override + protected boolean supportsTargetChannel() { + return false; + } + + @Override + public Optional createMessageConfig(@NonNull UUID configUUID, long guildId, long channelId, @NonNull Config config) { + return Optional.empty(); + } + + @Override + public @NonNull String getCommandId() { + return COMMAND_NAME; + } + + @Override + protected @NonNull String getCommandDescription() { + return "Displays the welcome message"; + } + + @Override + protected @NonNull EmbedOrMessageDefinition getHelpMessage() { + return EmbedOrMessageDefinition.builder().descriptionOrContent("Displays the welcome message") + .field(new EmbedOrMessageDefinition.Field("Full documentation", "https://github.com/twonirwana/DiscordDiceBot", false)) + .field(new EmbedOrMessageDefinition.Field("Discord Server for Help and News", "https://discord.gg/e43BsqKpFr", false)) + .build(); + } + + @Override + protected @NonNull Optional createNewButtonMessageWithState(UUID configUUID, Config ignore, State state, long guildId, long channelId) { + BotMetrics.incrementButtonMetricCounter(COMMAND_NAME, "[" + state.getButtonValue() + "]"); + if (ButtonIds.isInvalid(state.getButtonValue())) { + return Optional.empty(); + } + UUID newConfigUUID = uuidSupplier.get(); + log.info("Click on welcome command creation: " + state.getButtonValue()); + return switch (ButtonIds.valueOf(state.getButtonValue())) { + case fate -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.FATE, newConfigUUID, guildId, channelId).getMessageDefinition()); + case fate_image -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.FATE_IMAGE, newConfigUUID, guildId, channelId).getMessageDefinition()); + case dnd5 -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.DND5, newConfigUUID, guildId, channelId).getMessageDefinition()); + case dnd5_image -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.DND5_IMAGE, newConfigUUID, guildId, channelId).getMessageDefinition()); + case nWoD -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.NWOD, newConfigUUID, guildId, channelId).getMessageDefinition()); + case oWoD -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.OWOD, newConfigUUID, guildId, channelId).getMessageDefinition()); + case shadowrun -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.SHADOWRUN, newConfigUUID, guildId, channelId).getMessageDefinition()); + case coin -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.COIN, newConfigUUID, guildId, channelId).getMessageDefinition()); + case dice_calculator -> + Optional.of(rpgSystemCommandPreset.createMessage(RpgSystemCommandPreset.PresetId.DICE_CALCULATOR, newConfigUUID, guildId, channelId).getMessageDefinition()); + }; + } + + @Override + protected boolean shouldKeepExistingButtonMessage(@NonNull ButtonEventAdaptor event) { + return true; + } + + @Override + protected @NonNull Optional getAnswer(Config config, State state, long channelId, long userId) { + return Optional.empty(); + } + + public MessageDefinition getWelcomeMessage() { + return createNewButtonMessage(uuidSupplier.get(), null); + } + + @Override + public @NonNull MessageDefinition createNewButtonMessage(UUID configUUID, Config config) { + return MessageDefinition.builder() + .content(""" + Welcome to the Button Dice Bot, + use one of the example buttons below to start one of the RPG dice systems or use the slash command to configure your own custom dice system (see https://github.com/twonirwana/DiscordDiceBot for details or the slash command `/help`).\s + You can also use the slash command `/r` to directly roll dice with. + For help or feature request come to the support discord server: https://discord.gg/e43BsqKpFr""") + .componentRowDefinition(ComponentRowDefinition.builder() + .buttonDefinitions( + ImmutableList.of( + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.fate.name(), configUUID)) + .label("Fate") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.fate_image.name(), configUUID)) + .label("Fate with dice images") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dnd5.name(), configUUID)) + .label("D&D5e") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dnd5_image.name(), configUUID)) + .label("D&D5e with dice images") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.nWoD.name(), configUUID)) + .label("nWoD") + .build() + + ) + ) + .build()) + .componentRowDefinition(ComponentRowDefinition.builder() + .buttonDefinitions( + ImmutableList.of( + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.oWoD.name(), configUUID)) + .label("oWoD") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.shadowrun.name(), configUUID)) + .label("Shadowrun") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.coin.name(), configUUID)) + .label("Coin Toss \uD83E\uDE99") + .build(), + ButtonDefinition.builder() + .id(BottomCustomIdUtils.createButtonCustomId(getCommandId(), ButtonIds.dice_calculator.name(), configUUID)) + .label("Dice Calculator") + .build() + ) + ) + .build()) + .build(); + } + + @Override + protected @NonNull Config getConfigFromStartOptions(@NonNull CommandInteractionOption options) { + return new Config(null, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, DiceImageStyle.none.getDefaultColor())); + } + + private enum ButtonIds { + fate, + fate_image, + dnd5, + dnd5_image, + nWoD, + oWoD, + shadowrun, + coin, + dice_calculator; + + public static boolean isInvalid(String in) { + return Arrays.stream(ButtonIds.values()).noneMatch(s -> s.name().equals(in)); + } + } +} diff --git a/bot/src/main/java/de/janno/discord/bot/command/holdReroll/HoldRerollConfig.java b/bot/src/main/java/de/janno/discord/bot/command/holdReroll/HoldRerollConfig.java index 66fc9691..be372192 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/holdReroll/HoldRerollConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/holdReroll/HoldRerollConfig.java @@ -11,6 +11,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.ToString; +import org.apache.commons.lang3.NotImplementedException; import java.util.Set; import java.util.stream.Collectors; @@ -58,4 +59,9 @@ public String toShortString() { getDiceStyleAndColor() ).toString(); } + + @Override + public String toCommandOptionsString() { + throw new NotImplementedException(); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetCommand.java b/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetCommand.java index d9ae6a66..b1649c4c 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetCommand.java @@ -33,13 +33,13 @@ public class PoolTargetCommand extends AbstractCommand { static final String SUBSET_DELIMITER = ";"; + static final String SIDES_OF_DIE_OPTION = "sides"; + static final String MAX_DICE_OPTION = "max_dice"; + static final String REROLL_SET_OPTION = "reroll_set"; + static final String BOTCH_SET_OPTION = "botch_set"; + static final String REROLL_VARIANT_OPTION = "reroll_variant"; private static final String COMMAND_NAME = "pool_target"; - private static final String SIDES_OF_DIE_OPTION = "sides"; - private static final String MAX_DICE_OPTION = "max_dice"; - private static final String REROLL_SET_OPTION = "reroll_set"; - private static final String BOTCH_SET_OPTION = "botch_set"; - private static final String REROLL_VARIANT_OPTION = "reroll_variant"; - private static final String ALWAYS_REROLL = "always"; + static final String ALWAYS_REROLL = "always"; private static final String ASK_FOR_REROLL = "ask"; private static final String CLEAR_BUTTON_ID = "clear"; private static final String DO_REROLL_ID = "do_reroll"; diff --git a/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetConfig.java b/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetConfig.java index 0ea847a7..09e27193 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetConfig.java +++ b/bot/src/main/java/de/janno/discord/bot/command/poolTarget/PoolTargetConfig.java @@ -12,6 +12,9 @@ import lombok.NonNull; import lombok.ToString; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -25,6 +28,7 @@ public class PoolTargetConfig extends Config { private final Set rerollSet; @NonNull private final Set botchSet; + @NonNull private final String rerollVariant; @JsonCreator @@ -42,7 +46,7 @@ public PoolTargetConfig(@JsonProperty("answerTargetChannelId") Long answerTarget this.maxNumberOfButtons = maxNumberOfButtons; this.rerollSet = rerollSet; this.botchSet = botchSet; - this.rerollVariant = rerollVariant; + this.rerollVariant = Optional.ofNullable(rerollVariant).orElse(PoolTargetCommand.ALWAYS_REROLL); } @Override @@ -58,4 +62,19 @@ public String toShortString() { getDiceStyleAndColor() ).toString(); } + + @Override + public String toCommandOptionsString() { + List out = new ArrayList<>(); + out.add(String.format("%s: %s", PoolTargetCommand.SIDES_OF_DIE_OPTION, diceSides)); + out.add(String.format("%s: %s", PoolTargetCommand.MAX_DICE_OPTION, maxNumberOfButtons)); + out.add(String.format("%s: %s", PoolTargetCommand.REROLL_VARIANT_OPTION, rerollVariant)); + if (!rerollSet.isEmpty()) { + out.add(String.format("%s: %s", PoolTargetCommand.REROLL_SET_OPTION, rerollSet.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")))); + } + if (!botchSet.isEmpty()) { + out.add(String.format("%s: %s", PoolTargetCommand.BOTCH_SET_OPTION, botchSet.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")))); + } + return "%s %s".formatted(String.join(" ", out), super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/command/sumCustomSet/SumCustomSetCommand.java b/bot/src/main/java/de/janno/discord/bot/command/sumCustomSet/SumCustomSetCommand.java index fa4194d0..bc6f7b5d 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/sumCustomSet/SumCustomSetCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/sumCustomSet/SumCustomSetCommand.java @@ -38,8 +38,8 @@ public class SumCustomSetCommand extends AbstractCommand { + if (b.getDiceExpression().equals(b.getLabel())) { + return b.getDiceExpression(); + } + return "%s@%s".formatted(b.getDiceExpression(), b.getLabel()); + }) + .collect(Collectors.joining(";")); + return "%s: %s %s: %s %s".formatted(SumCustomSetCommand.BUTTONS_COMMAND_OPTIONS_ID, String.join(" ", buttons), + SumCustomSetCommand.ALWAYS_SUM_RESULTS_COMMAND_OPTIONS_ID, alwaysSumResult, + super.toCommandOptionsString()); + } } diff --git a/bot/src/main/java/de/janno/discord/bot/dice/DiceParserSystem.java b/bot/src/main/java/de/janno/discord/bot/dice/DiceParserSystem.java index 8056bfc2..cb502687 100644 --- a/bot/src/main/java/de/janno/discord/bot/dice/DiceParserSystem.java +++ b/bot/src/main/java/de/janno/discord/bot/dice/DiceParserSystem.java @@ -1,6 +1,7 @@ package de.janno.discord.bot.dice; public enum DiceParserSystem { + @Deprecated DICEROLL_PARSER, DICE_EVALUATOR } diff --git a/bot/src/main/java/de/janno/discord/bot/dice/DiceSystemAdapter.java b/bot/src/main/java/de/janno/discord/bot/dice/DiceSystemAdapter.java index 903489b7..b091d1ed 100644 --- a/bot/src/main/java/de/janno/discord/bot/dice/DiceSystemAdapter.java +++ b/bot/src/main/java/de/janno/discord/bot/dice/DiceSystemAdapter.java @@ -1,7 +1,6 @@ package de.janno.discord.bot.dice; import de.janno.discord.bot.BotMetrics; -import de.janno.discord.bot.ResultImage; import de.janno.discord.bot.command.AnswerFormatType; import de.janno.discord.bot.command.RollAnswer; import de.janno.discord.bot.dice.image.DiceStyleAndColor; @@ -31,6 +30,31 @@ public DiceSystemAdapter(CachingDiceEvaluator cachingDiceEvaluator, Dice dice) { return expressionWithOptionalLabel; } + public static Optional validateLabel(@NonNull String expressionWithOptionalLabel) { + //todo remove duplicate + String label; + String diceExpression; + + if (expressionWithOptionalLabel.contains(LABEL_DELIMITER)) { + String[] split = expressionWithOptionalLabel.split(LABEL_DELIMITER); + if (split.length != 2) { + return Optional.of(String.format("The expression '%s' should have the diceExpression@Label", expressionWithOptionalLabel)); + } + label = split[1].trim(); + diceExpression = split[0].trim(); + } else { + label = expressionWithOptionalLabel; + diceExpression = expressionWithOptionalLabel; + } + if (label.isBlank()) { + return Optional.of(String.format("Label for '%s' requires a visible name", expressionWithOptionalLabel)); + } + if (diceExpression.isBlank()) { + return Optional.of(String.format("Dice expression for '%s' is empty", expressionWithOptionalLabel)); + } + return Optional.empty(); + } + public RollAnswer answerRollWithGivenLabel(String expression, @Nullable String label, boolean sumUp, DiceParserSystem system, AnswerFormatType answerFormatType, DiceStyleAndColor diceStyleAndColor) { BotMetrics.incrementDiceParserSystemCounter(system); return switch (system) { @@ -57,31 +81,6 @@ public Optional validateListOfExpressions(List expressions, Stri return Optional.empty(); } - public static Optional validateLabel(@NonNull String expressionWithOptionalLabel) { - //todo remove duplicate - String label; - String diceExpression; - - if (expressionWithOptionalLabel.contains(LABEL_DELIMITER)) { - String[] split = expressionWithOptionalLabel.split(LABEL_DELIMITER); - if (split.length != 2) { - return Optional.of(String.format("The expression '%s' should have the diceExpression@Label", expressionWithOptionalLabel)); - } - label = split[1].trim(); - diceExpression = split[0].trim(); - } else { - label = expressionWithOptionalLabel; - diceExpression = expressionWithOptionalLabel; - } - if (label.isBlank()) { - return Optional.of(String.format("Label for '%s' requires a visible name", expressionWithOptionalLabel)); - } - if (diceExpression.isBlank()) { - return Optional.of(String.format("Dice expression for '%s' is empty", expressionWithOptionalLabel)); - } - return Optional.empty(); - } - public Optional validateDiceExpressionWitOptionalLabel(@NonNull String expressionWithOptionalLabel, String helpCommand, DiceParserSystem system) { Optional validateLabel = validateLabel(expressionWithOptionalLabel); if (validateLabel.isPresent()) { diff --git a/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java b/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java index 709e2879..0338b1ab 100644 --- a/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java +++ b/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java @@ -169,11 +169,12 @@ private Supplier createNewFileForRoll(Roll roll, File fil .map(r -> { if (r.getMaxInc() != null && r.getMinInc() != null) { return (r.getMaxInc() + 1) - r.getMinInc(); + } else if (r.getRandomSelectedFrom() != null) { + return r.getRandomSelectedFrom().size(); } else { throw new IllegalStateException("The roll %s should not used to crate images".formatted(roll)); } } - ) .map(BigInteger::valueOf) .reduce(BigInteger.ONE, BigInteger::multiply); diff --git a/bot/src/test/java/de/janno/discord/bot/command/channelConfig/ChannelConfigMockTest.java b/bot/src/test/java/de/janno/discord/bot/command/channelConfig/ChannelConfigMockTest.java index 16cacaa5..b3e8c76b 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/channelConfig/ChannelConfigMockTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/channelConfig/ChannelConfigMockTest.java @@ -23,11 +23,10 @@ public class ChannelConfigMockTest { PersistenceManager persistenceManager; @BeforeEach - void setup() { - try { - FileUtils.cleanDirectory(new File("imageCache/")); - } catch (IOException e) { - throw new RuntimeException(e); + void setup() throws IOException { + File cacheDirectory = new File("imageCache/"); + if (cacheDirectory.exists()) { + FileUtils.cleanDirectory(cacheDirectory); } persistenceManager = new PersistenceManagerImpl("jdbc:h2:mem:" + UUID.randomUUID(), null, null); } @@ -174,7 +173,7 @@ void userChannelAlias_2UserCreateAlias() { .option(CommandInteractionOption.builder().name("name").stringValue("att").build()) .option(CommandInteractionOption.builder().name("value").stringValue("2d20+10").build()) .build()) - .build()),1L); + .build()), 1L); channelConfig.handleSlashCommandEvent(slashEvent1, () -> UUID.fromString("00000000-0000-0000-0000-000000000000")).block(); SlashEventAdaptorMock slashEvent2 = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() @@ -184,7 +183,7 @@ void userChannelAlias_2UserCreateAlias() { .option(CommandInteractionOption.builder().name("name").stringValue("att").build()) .option(CommandInteractionOption.builder().name("value").stringValue("2d20+1").build()) .build()) - .build()),2L); + .build()), 2L); channelConfig.handleSlashCommandEvent(slashEvent2, () -> UUID.fromString("00000000-0000-0000-0000-000000000001")).block(); SlashEventAdaptorMock slashEvent3 = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() @@ -192,7 +191,7 @@ void userChannelAlias_2UserCreateAlias() { .option(CommandInteractionOption.builder() .name("list") .build()) - .build()),1L); + .build()), 1L); channelConfig.handleSlashCommandEvent(slashEvent3, () -> UUID.fromString("00000000-0000-0000-0000-000000000002")).block(); SlashEventAdaptorMock slashEvent4 = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() @@ -200,7 +199,7 @@ void userChannelAlias_2UserCreateAlias() { .option(CommandInteractionOption.builder() .name("list") .build()) - .build()),2L); + .build()), 2L); channelConfig.handleSlashCommandEvent(slashEvent4, () -> UUID.fromString("00000000-0000-0000-0000-000000000003")).block(); assertThat(slashEvent1.getActions()).containsExactlyInAnyOrder("reply: `commandString`\nSaved new alias"); diff --git a/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java b/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java index 6832d6da..44d5bf0f 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java @@ -45,14 +45,20 @@ public class CustomDiceCommandMockTest { @BeforeEach void setup() throws IOException { - FileUtils.cleanDirectory(new File("imageCache/")); + File cacheDirectory = new File("imageCache/"); + if(cacheDirectory.exists()){ + FileUtils.cleanDirectory(cacheDirectory); + } messageIdCounter = new AtomicLong(0); persistenceManager = new PersistenceManagerImpl("jdbc:h2:mem:" + UUID.randomUUID(), null, null); } @AfterEach void cleanUp() throws IOException { - FileUtils.cleanDirectory(new File("imageCache/")); + File cacheDirectory = new File("imageCache/"); + if(cacheDirectory.exists()){ + FileUtils.cleanDirectory(cacheDirectory); + } } @Test diff --git a/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java b/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java index 0e755aee..5a5d0d47 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java @@ -23,11 +23,10 @@ public class DirectRollCommandMockTest { PersistenceManager persistenceManager; @BeforeEach - void setup() { - try { - FileUtils.cleanDirectory(new File("imageCache/")); - } catch (IOException e) { - throw new RuntimeException(e); + void setup() throws IOException { + File cacheDirectory = new File("imageCache/"); + if(cacheDirectory.exists()){ + FileUtils.cleanDirectory(cacheDirectory); } persistenceManager = new PersistenceManagerImpl("jdbc:h2:mem:" + UUID.randomUUID(), null, null); } diff --git a/bot/src/test/java/de/janno/discord/bot/command/HelpCommandTest.java b/bot/src/test/java/de/janno/discord/bot/command/help/HelpCommandTest.java similarity index 95% rename from bot/src/test/java/de/janno/discord/bot/command/HelpCommandTest.java rename to bot/src/test/java/de/janno/discord/bot/command/help/HelpCommandTest.java index c3d1b8c3..549117c8 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/HelpCommandTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/help/HelpCommandTest.java @@ -1,4 +1,4 @@ -package de.janno.discord.bot.command; +package de.janno.discord.bot.command.help; import de.janno.discord.connector.api.SlashEventAdaptor; import org.junit.jupiter.api.Test; diff --git a/bot/src/test/java/de/janno/discord/bot/command/help/QuickstartCommandTest.java b/bot/src/test/java/de/janno/discord/bot/command/help/QuickstartCommandTest.java new file mode 100644 index 00000000..53caddd2 --- /dev/null +++ b/bot/src/test/java/de/janno/discord/bot/command/help/QuickstartCommandTest.java @@ -0,0 +1,144 @@ +package de.janno.discord.bot.command.help; + +import de.janno.discord.bot.SlashEventAdaptorMock; +import de.janno.discord.bot.command.countSuccesses.CountSuccessesCommand; +import de.janno.discord.bot.command.customDice.CustomDiceCommand; +import de.janno.discord.bot.command.customParameter.CustomParameterCommand; +import de.janno.discord.bot.command.fate.FateCommand; +import de.janno.discord.bot.command.poolTarget.PoolTargetCommand; +import de.janno.discord.bot.command.sumCustomSet.SumCustomSetCommand; +import de.janno.discord.bot.dice.CachingDiceEvaluator; +import de.janno.discord.bot.persistance.PersistenceManager; +import de.janno.discord.connector.api.slash.CommandInteractionOption; +import de.janno.evaluator.dice.random.RandomNumberSupplier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class QuickstartCommandTest { + + QuickstartCommand underTest; + + private static Stream generateData() { + return Stream.of( + Arguments.of(RpgSystemCommandPreset.PresetId.DND5_IMAGE, List.of("reply: `/custom_dice buttons: 1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20 answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d4, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d6, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d8, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d10, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d12, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d20, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d100, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20 Advantage, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20 Disadvantage, id=custom_dice9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d4, id=custom_dice10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=2d6, id=custom_dice11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d8, id=custom_dice12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d10, id=custom_dice13_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d12, id=custom_dice14_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d20, id=custom_dice15_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.DND5, List.of("reply: `/custom_dice buttons: 1d4;1d6;1d8;1d10;1d12;1d20;1d100;2d20k1@D20 Advantage;2d20L1@D20 Disadvantage;2d4=@2d4;2d6=@2d6;2d8=@2d8;2d10=@2d10;2d12=@2d12;2d20=@2d20 answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d4, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d6, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d8, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d10, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d12, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d20, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d100, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20 Advantage, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20 Disadvantage, id=custom_dice9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d4, id=custom_dice10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=2d6, id=custom_dice11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d8, id=custom_dice12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d10, id=custom_dice13_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d12, id=custom_dice14_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d20, id=custom_dice15_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.DND5_CALC, List.of("reply: `/custom_dice buttons: d4;d6;d8;d10;d12;d20;d100;1;2;3;4;5;k@Keep Highest;L@Keep Lowest;(2d20k1)@D20 Advantage;(2d20L1)@D20 Disadvantage;-@Minus;+@Plus;(4r(d6))k3=@Stats;,@Split always_sum_result: true answer_format: full dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click the buttons to add dice to the set and then on Roll, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=d4, id=sum_custom_set1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d6, id=sum_custom_set2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d8, id=sum_custom_set3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d10, id=sum_custom_set4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d12, id=sum_custom_set5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=d20, id=sum_custom_set6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d100, id=sum_custom_set7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1, id=sum_custom_set8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=sum_custom_set9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=sum_custom_set10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=4, id=sum_custom_set11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=sum_custom_set12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Keep Highest, id=sum_custom_set13_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Keep Lowest, id=sum_custom_set14_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20 Advantage, id=sum_custom_set15_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D20 Disadvantage, id=sum_custom_set16_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Minus, id=sum_custom_set17_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Plus, id=sum_custom_set18_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Stats, id=sum_custom_set19_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Split, id=sum_custom_set20_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Roll, id=sum_custom_setroll00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=true), ButtonDefinition(label=Clear, id=sum_custom_setclear00000000-0000-0000-0000-000000000000, style=DANGER, disabled=false), ButtonDefinition(label=Back, id=sum_custom_setback00000000-0000-0000-0000-000000000000, style=SECONDARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.NWOD, List.of("reply: `/custom_dice dice_sides: 10 target_number: 8 glitch: no_glitch max_dice: 15 min_dice_count: 1 reroll_set: 10 answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click to roll the dice against 8, reroll for: [10], componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d10, id=count_successes100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d10, id=count_successes200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d10, id=count_successes300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4d10, id=count_successes400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5d10, id=count_successes500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6d10, id=count_successes600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7d10, id=count_successes700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8d10, id=count_successes800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9d10, id=count_successes900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10d10, id=count_successes1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11d10, id=count_successes1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12d10, id=count_successes1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13d10, id=count_successes1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14d10, id=count_successes1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15d10, id=count_successes1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.OWOD, List.of("reply: `/custom_dice sides: 10 max_dice: 15 reroll_variant: ask reroll_set: 10 botch_set: 1 answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click on the buttons to roll dice, with ask reroll:10 and botch:1, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d10, id=pool_target100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d10, id=pool_target200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d10, id=pool_target300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4d10, id=pool_target400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5d10, id=pool_target500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6d10, id=pool_target600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7d10, id=pool_target700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8d10, id=pool_target800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9d10, id=pool_target900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10d10, id=pool_target1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11d10, id=pool_target1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12d10, id=pool_target1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13d10, id=pool_target1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14d10, id=pool_target1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15d10, id=pool_target1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.SHADOWRUN, List.of("reply: `/custom_dice dice_sides: 6 target_number: 5 glitch: half_dice_one max_dice: 20 min_dice_count: 1 answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click to roll the dice against 5 and check for more then half of dice 1s, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d6, id=count_successes100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d6, id=count_successes200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d6, id=count_successes300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4d6, id=count_successes400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5d6, id=count_successes500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6d6, id=count_successes600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7d6, id=count_successes700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8d6, id=count_successes800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9d6, id=count_successes900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10d6, id=count_successes1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11d6, id=count_successes1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12d6, id=count_successes1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13d6, id=count_successes1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14d6, id=count_successes1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15d6, id=count_successes1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=16d6, id=count_successes1600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=17d6, id=count_successes1700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=18d6, id=count_successes1800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=19d6, id=count_successes1900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=20d6, id=count_successes2000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])")), + Arguments.of(RpgSystemCommandPreset.PresetId.SAVAGE_WORLDS, List.of("reply: `/custom_dice expression: (d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20} + {Type: 0@Regular/1d!!6@Wildcard})k1 answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **Dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D4, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D6, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D8, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D12, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.FATE_IMAGE, List.of("reply: `/custom_dice expression: 4d[-1,0,1]+{Modifier:-4<=>10}= answer_format: without_expression dice_image_style: fate dice_image_color: black`", + "createButtonMessage: MessageDefinition(content=Please select value for **Modifier**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-4, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-3, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-2, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-1, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=0, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.FATE, List.of("reply: `/custom_dice type: with_modifier answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click a button to roll four fate dice and add the value of the button, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-4, id=fate-400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-3, id=fate-300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-2, id=fate-200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-1, id=fate-100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=0, id=fate000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+1, id=fate100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+2, id=fate200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+3, id=fate300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+4, id=fate400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+5, id=fate500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+6, id=fate600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+7, id=fate700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+8, id=fate800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+9, id=fate900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+10, id=fate1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.COIN, List.of("reply: `/custom_dice buttons: 1d[Head 😀/Tail 🦅]@Coin Toss 🪙 answer_format: without_expression dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Coin Toss 🪙, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.DICE_CALCULATOR, List.of("reply: `/custom_dice buttons: 7;8;9;+;-;4;5;6;d;k;1;2;3;0;l always_sum_result: true answer_format: full dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click the buttons to add dice to the set and then on Roll, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=7, id=sum_custom_set1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=sum_custom_set2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=sum_custom_set3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+, id=sum_custom_set4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-, id=sum_custom_set5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=4, id=sum_custom_set6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=sum_custom_set7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=6, id=sum_custom_set8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=d, id=sum_custom_set9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=k, id=sum_custom_set10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=sum_custom_set11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=sum_custom_set12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=sum_custom_set13_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=0, id=sum_custom_set14_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=l, id=sum_custom_set15_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Roll, id=sum_custom_setroll00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=true), ButtonDefinition(label=Clear, id=sum_custom_setclear00000000-0000-0000-0000-000000000000, style=DANGER, disabled=false), ButtonDefinition(label=Back, id=sum_custom_setback00000000-0000-0000-0000-000000000000, style=SECONDARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.OSR, List.of("reply: `/custom_dice buttons: 1d20@D20;1d6@D6;2d6@2D6;1d4@D4;1d8@D8;3d6=,3d6=,3d6=,3d6=,3d6=,3d6=@Stats;(3d6=)*10@Gold;1d100@D100;1d10@D10;1d12@D12 answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D20, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D6, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2D6, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D4, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D8, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Stats, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Gold, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D100, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D10, id=custom_dice9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D12, id=custom_dice10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.TRAVELLER, List.of("reply: `/custom_dice buttons: +2d6;+(3d6k2)@Boon;+(3d6l2)@Bane;+1d6;+1;+2;+3;+4;-1;-2;-3;-4 always_sum_result: true answer_format: full dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click the buttons to add dice to the set and then on Roll, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+2d6, id=sum_custom_set1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Boon, id=sum_custom_set2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Bane, id=sum_custom_set3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d6, id=sum_custom_set4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1, id=sum_custom_set5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+2, id=sum_custom_set6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+3, id=sum_custom_set7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+4, id=sum_custom_set8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-1, id=sum_custom_set9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-2, id=sum_custom_set10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-3, id=sum_custom_set11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-4, id=sum_custom_set12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Roll, id=sum_custom_setroll00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=true), ButtonDefinition(label=Clear, id=sum_custom_setclear00000000-0000-0000-0000-000000000000, style=DANGER, disabled=false), ButtonDefinition(label=Back, id=sum_custom_setback00000000-0000-0000-0000-000000000000, style=SECONDARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.BLADES_IN_THE_DARK, List.of("reply: `/custom_dice buttons: ifE(2d[0/0/0/1/1/3]l1,3,'Success',1,'Partial','Failure')@Zero;ifE(1d[0/0/0/1/1/3],3,'Success',1,'Partial','Failure')@1d6;ifG(2d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@2d6;ifG(3d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@3d6;ifG(4d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@4d6;ifG(5d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@5d6;ifG(6d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@6d6;ifG(7d[0/0/0/1/1/3]k2=,5,'Critical',2,'Success',0,'Partial','Failure')@7d6 answer_format: without_expression dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Zero, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d6, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2d6, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d6, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4d6, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=5d6, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=6d6, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7d6, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.CALL_OF_CTHULHU_7ED, List.of("reply: `/custom_dice buttons: 1d100;2d100L1@1d100 Advantage;2d100K1@1d100 Penalty;1d3;1d4;1d6;1d8;1d10;1d12;1d20;3d6 answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d100, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d100 Advantage, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d100 Penalty, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d3, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d4, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d6, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d8, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d10, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d12, id=custom_dice9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1d20, id=custom_dice10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=3d6, id=custom_dice11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.EXALTED_3ED, List.of("reply: `/custom_dice expression: val('$1', cancel(double({number of dice}d10,10),1,[7/8/9/10])), ifE(('$1'>=7)c,0,ifG(('$1'<=1)c,0,'Botch')) answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **number of dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14, id=custom_parameterid1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15, id=custom_parameterid1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.VAMPIRE_5ED, List.of("reply: `/custom_dice expression: val('$r',{regular dice:1<=>16}d10 col 'blue') val('$h',{hunger dice:0<=>5}d10 col 'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression dice_image_style: polyhedral_knots dice_image_color: blue`", + "createButtonMessage: MessageDefinition(content=Please select value for **regular dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14, id=custom_parameterid1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15, id=custom_parameterid1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=16, id=custom_parameterid1600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.HUNTER_5ED, List.of("reply: `/custom_dice expression: val('$r',{Regular D10 Dice:1<=>16}d10 col 'blue') val('$h', {Desperation Dice:0<=>5}d10 col'purple_dark') val('$s',('$r'+'$h')>=6c) val('$rt','$r'==10c) val('$ht','$h'==10c) val('$ho','$h'==1c) val('$2s',((('$rt'+'$ht'=))/2)*2) val('$ts',('$s'+'$2s'=)) concat('successes: ', '$ts', ifE('$ts',0,ifG('$ho',1,' bestial failure' , ''),''), ifE('$rt' mod 2, 1, ifE('$ht' mod 2, 1, ' messy critical', ''), '')) answer_format: without_expression dice_image_style: polyhedral_knots dice_image_color: blue`", + "createButtonMessage: MessageDefinition(content=Please select value for **Regular D10 Dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14, id=custom_parameterid1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15, id=custom_parameterid1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=16, id=custom_parameterid1600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.ONE_ROLL_ENGINE, List.of("reply: `/custom_dice expression: groupc({Number of Dice:1<=>10}d10+({Number of Extra Die:0@0/10@1/2r10@2/3r10@3/4r10@4})>={Difficulty:1<=>10}) answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **Number of Dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.DUNGEON_CRAWL_CLASSICS, List.of("reply: `/custom_dice buttons: +1d4;+1d6;+1d7;+1d8;+1d10;+1d12;+1d14;+1d16;+1d20;+1d24;+1d16;+1d30;+1d100;+1;+2;+3;+4;-1;-2;-3;-4 always_sum_result: true answer_format: full dice_image_style: none dice_image_color: none`", + "createButtonMessage: MessageDefinition(content=Click the buttons to add dice to the set and then on Roll, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+1d4, id=sum_custom_set1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d6, id=sum_custom_set2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d7, id=sum_custom_set3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d8, id=sum_custom_set4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d10, id=sum_custom_set5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+1d12, id=sum_custom_set6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d14, id=sum_custom_set7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d16, id=sum_custom_set8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d20, id=sum_custom_set9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d24, id=sum_custom_set10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+1d16, id=sum_custom_set11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d30, id=sum_custom_set12_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1d100, id=sum_custom_set13_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+1, id=sum_custom_set14_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+2, id=sum_custom_set15_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=+3, id=sum_custom_set16_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=+4, id=sum_custom_set17_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-1, id=sum_custom_set18_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-2, id=sum_custom_set19_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-3, id=sum_custom_set20_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-4, id=sum_custom_set21_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Roll, id=sum_custom_setroll00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=true), ButtonDefinition(label=Clear, id=sum_custom_setclear00000000-0000-0000-0000-000000000000, style=DANGER, disabled=false), ButtonDefinition(label=Back, id=sum_custom_setback00000000-0000-0000-0000-000000000000, style=SECONDARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.TINY_D6, List.of("reply: `/custom_dice buttons: ifG(1d6>=5c,0,'Success','Failure')@Disadvantage;ifG(2d6>=5c,0,'Success','Failure')@Test;ifG(3d6>=5c,0,'Success','Failure')@Advantage answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=Disadvantage, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Test, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Advantage, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.CYBERPUNK_RED, List.of("reply: `/custom_dice expression: val('$roll', 1d10) ifE('$roll', 1, '$roll'-1d10, 10, '$roll'+1d10, '$roll')+{ability:0<=>9}+{skill:0<=>9}= answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **ability**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=0, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=5, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=6, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.ASOIAF, List.of("reply: `/custom_dice expression: {numberOfDice:1<=>15}d6k{keep:1<=>10} answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **numberOfDice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=11, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=12, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=13, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=14, id=custom_parameterid1400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=15, id=custom_parameterid1500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.CITY_OF_MIST, List.of("reply: `/custom_dice expression: 2d6+{modifier:-6<=>6} answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **modifier**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-6, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-5, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-4, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-3, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=-2, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=-1, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=0, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=1, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=4, id=custom_parameterid1100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid1200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=6, id=custom_parameterid1300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.RISUS, List.of("reply: `/custom_dice expression: val('$r',{numberOfDice:1<=>10}d!6) ('$r'==2c) + ('$r'==4c) + ('$r'==6c)= answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **numberOfDice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=4, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=5, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=6, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=7, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=8, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=9, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=10, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.KIDS_ON_BROOMS, List.of("reply: `/custom_dice expression: (d!!{Dice:4@D4/6@D6/8@D8/12@D12/20@D20/4+d!!4@D4Spell/6+d!!4@D6Spell/8+d!!4@D8Spell/12+d!!4@D12Spell/20+d!!4@D20Spell} {plus or minus:+/-}{Modifier:0<=>15}=)-{Difficulty:1<=>30}= answer_format: without_expression dice_image_style: polyhedral_3d dice_image_color: red_and_white`", + "createButtonMessage: MessageDefinition(content=Please select value for **Dice**, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D4, id=custom_parameterid100000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D6, id=custom_parameterid200000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D8, id=custom_parameterid300000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D12, id=custom_parameterid400000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20, id=custom_parameterid500000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D4Spell, id=custom_parameterid600000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D6Spell, id=custom_parameterid700000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D8Spell, id=custom_parameterid800000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D12Spell, id=custom_parameterid900000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20Spell, id=custom_parameterid1000000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )), + Arguments.of(RpgSystemCommandPreset.PresetId.REVE_DE_DRAGON, List.of("reply: `/custom_dice buttons: 1d4@D4;1d6@D6;2d6=@2D6;1d7@D7;1d8@D8;val('roll',1d!8 col 'special') val('diceCount','roll' c) 'roll'-'diceCount'+7=@DDR;2d10=@2D10;1d12@D12;val('$r',1d12 col 'special'),if('$r'=?1,'vaisseau','$r'=?2,'sirène','$r'=?3,'faucon','$r'=?4,'couronne','$r'=?5,'dragon','$r'=?6,'épées','$r'=?7,'lyre','$r'=?8,'serpent','$r'=?9,'poisson acrobate','$r'=?10,'araignée','$r'=?11,'roseaux','$r'=?12,'château dormant')@DAS;1d20@D20;1d100@D100 answer_format: without_expression dice_image_style: polyhedral_RdD dice_image_color: default`", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D4, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D6, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2D6, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D7, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D8, id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=DDR, id=custom_dice6_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=2D10, id=custom_dice7_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D12, id=custom_dice8_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=DAS, id=custom_dice9_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=D20, id=custom_dice10_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)]), ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=D100, id=custom_dice11_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + )) + ); + } + + @BeforeEach + void setup() { + PersistenceManager persistenceManager = Mockito.mock(PersistenceManager.class); + CachingDiceEvaluator cachingDiceEvaluator = new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0); + CountSuccessesCommand countSuccessesCommand = new CountSuccessesCommand(persistenceManager); + CustomDiceCommand customDiceCommand = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); + FateCommand fateCommand = new FateCommand(persistenceManager); + CustomParameterCommand customParameterCommand = new CustomParameterCommand(persistenceManager, cachingDiceEvaluator); + SumCustomSetCommand sumCustomSetCommand = new SumCustomSetCommand(persistenceManager, cachingDiceEvaluator); + PoolTargetCommand poolTargetCommand = new PoolTargetCommand(persistenceManager); + RpgSystemCommandPreset rpgSystemCommandPreset = new RpgSystemCommandPreset(persistenceManager, customParameterCommand, fateCommand, customDiceCommand, countSuccessesCommand, sumCustomSetCommand, poolTargetCommand); + underTest = new QuickstartCommand(rpgSystemCommandPreset); + } + + @Test + void getCommandId() { + assertThat(underTest.getCommandId()).isEqualTo("quickstart"); + } + + @ParameterizedTest(name = "{index} config={0} -> {1}") + @MethodSource("generateData") + void handleSlashCommandEvent(RpgSystemCommandPreset.PresetId presetId, List actions) { + SlashEventAdaptorMock slashEventAdaptor = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() + .name("system") + .stringValue(presetId.name()) + .build())); + + Mono res = underTest.handleSlashCommandEvent(slashEventAdaptor, () -> UUID.fromString("00000000-0000-0000-0000-000000000000")); + StepVerifier.create(res).verifyComplete(); + assertThat(slashEventAdaptor.getActions()).containsExactlyInAnyOrderElementsOf(actions); + } +} \ No newline at end of file diff --git a/bot/src/test/java/de/janno/discord/bot/command/WelcomeCommandTest.java b/bot/src/test/java/de/janno/discord/bot/command/help/WelcomeCommandTest.java similarity index 66% rename from bot/src/test/java/de/janno/discord/bot/command/WelcomeCommandTest.java rename to bot/src/test/java/de/janno/discord/bot/command/help/WelcomeCommandTest.java index ff8bf8d4..58fc82c1 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/WelcomeCommandTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/help/WelcomeCommandTest.java @@ -1,5 +1,15 @@ -package de.janno.discord.bot.command; - +package de.janno.discord.bot.command.help; + +import de.janno.discord.bot.command.AnswerFormatType; +import de.janno.discord.bot.command.Config; +import de.janno.discord.bot.command.State; +import de.janno.discord.bot.command.StateData; +import de.janno.discord.bot.command.countSuccesses.CountSuccessesCommand; +import de.janno.discord.bot.command.customDice.CustomDiceCommand; +import de.janno.discord.bot.command.customParameter.CustomParameterCommand; +import de.janno.discord.bot.command.fate.FateCommand; +import de.janno.discord.bot.command.poolTarget.PoolTargetCommand; +import de.janno.discord.bot.command.sumCustomSet.SumCustomSetCommand; import de.janno.discord.bot.dice.CachingDiceEvaluator; import de.janno.discord.bot.dice.image.DiceImageStyle; import de.janno.discord.bot.dice.image.DiceStyleAndColor; @@ -10,31 +20,46 @@ import de.janno.discord.connector.api.slash.CommandDefinition; import de.janno.discord.connector.api.slash.CommandDefinitionOption; import de.janno.evaluator.dice.random.RandomNumberSupplier; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; import java.util.Collection; import java.util.Optional; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WelcomeCommandTest { - final WelcomeCommand underTest = new WelcomeCommand(mock(PersistenceManager.class), new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0), () -> UUID.fromString("00000000-0000-0000-0000-000000000000")); + WelcomeCommand underTest; + + @BeforeEach + void setup() { + PersistenceManager persistenceManager = Mockito.mock(PersistenceManager.class); + CachingDiceEvaluator cachingDiceEvaluator = new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0); + CountSuccessesCommand countSuccessesCommand = new CountSuccessesCommand(persistenceManager); + CustomDiceCommand customDiceCommand = new CustomDiceCommand(persistenceManager, cachingDiceEvaluator); + FateCommand fateCommand = new FateCommand(persistenceManager); + CustomParameterCommand customParameterCommand = new CustomParameterCommand(persistenceManager, cachingDiceEvaluator); + SumCustomSetCommand sumCustomSetCommand = new SumCustomSetCommand(persistenceManager, cachingDiceEvaluator); + PoolTargetCommand poolTargetCommand = new PoolTargetCommand(persistenceManager); + RpgSystemCommandPreset rpgSystemCommandPreset = new RpgSystemCommandPreset(persistenceManager, customParameterCommand, fateCommand, customDiceCommand, countSuccessesCommand, sumCustomSetCommand, poolTargetCommand); + underTest = new WelcomeCommand(persistenceManager, rpgSystemCommandPreset, () -> UUID.fromString("00000000-0000-0000-0000-000000000000")); + } + @Test public void getButtonMessageWithState_fate() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("fate", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click a button to roll four fate dice and add the value of the button"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("fate-400000000-0000-0000-0000-000000000000", "fate-300000000-0000-0000-0000-000000000000", "fate-200000000-0000-0000-0000-000000000000", @@ -50,11 +75,11 @@ public void getButtonMessageWithState_fate() { "fate800000000-0000-0000-0000-000000000000", "fate900000000-0000-0000-0000-000000000000", "fate1000000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("-4", "-3", "-2", "-1", "0", "+1", "+2", "+3", "+4", "+5", "+6", "+7", "+8", "+9", "+10"); } @@ -62,13 +87,13 @@ public void getButtonMessageWithState_fate() { @Test public void getButtonMessageWithState_dnd5() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("dnd5", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click on a button to roll the dice"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("custom_dice1_button00000000-0000-0000-0000-000000000000", "custom_dice2_button00000000-0000-0000-0000-000000000000", "custom_dice3_button00000000-0000-0000-0000-000000000000", @@ -84,45 +109,45 @@ public void getButtonMessageWithState_dnd5() { "custom_dice13_button00000000-0000-0000-0000-000000000000", "custom_dice14_button00000000-0000-0000-0000-000000000000", "custom_dice15_button00000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) - .containsExactly("D4", - "D6", - "D8", - "D10", - "D12", - "D20", - "D100", + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) + .containsExactly("1d4", + "1d6", + "1d8", + "1d10", + "1d12", + "1d20", + "1d100", "D20 Advantage", "D20 Disadvantage", - "2D4", - "2D6", - "2D8", - "2D10", - "2D12", - "2D20"); + "2d4", + "2d6", + "2d8", + "2d10", + "2d12", + "2d20"); } @Test public void getButtonMessageWithState_coin() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("coin", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click on a button to roll the dice"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("custom_dice1_button00000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("Coin Toss \uD83E\uDE99"); } @@ -130,13 +155,13 @@ public void getButtonMessageWithState_coin() { public void getButtonMessageWithState_nWoD() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("nWoD", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click to roll the dice against 8, reroll for: [10]"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("count_successes100000000-0000-0000-0000-000000000000", "count_successes200000000-0000-0000-0000-000000000000", "count_successes300000000-0000-0000-0000-000000000000", @@ -152,24 +177,24 @@ public void getButtonMessageWithState_nWoD() { "count_successes1300000000-0000-0000-0000-000000000000", "count_successes1400000000-0000-0000-0000-000000000000", "count_successes1500000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("1d10", "2d10", "3d10", "4d10", "5d10", "6d10", "7d10", "8d10", "9d10", "10d10", "11d10", "12d10", "13d10", "14d10", "15d10"); } @Test public void getButtonMessageWithState_oWoD() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("oWoD", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click on the buttons to roll dice, with ask reroll:10 and botch:1"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("pool_target100000000-0000-0000-0000-000000000000", "pool_target200000000-0000-0000-0000-000000000000", "pool_target300000000-0000-0000-0000-000000000000", @@ -185,11 +210,11 @@ public void getButtonMessageWithState_oWoD() { "pool_target1300000000-0000-0000-0000-000000000000", "pool_target1400000000-0000-0000-0000-000000000000", "pool_target1500000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("1d10", "2d10", "3d10", "4d10", "5d10", "6d10", "7d10", "8d10", "9d10", "10d10", "11d10", "12d10", "13d10", "14d10", "15d10"); @@ -198,13 +223,13 @@ public void getButtonMessageWithState_oWoD() { @Test public void getButtonMessageWithState_Shadowrun() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("shadowrun", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click to roll the dice against 5 and check for more then half of dice 1s"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("count_successes100000000-0000-0000-0000-000000000000", "count_successes200000000-0000-0000-0000-000000000000", "count_successes300000000-0000-0000-0000-000000000000", @@ -225,11 +250,11 @@ public void getButtonMessageWithState_Shadowrun() { "count_successes1800000000-0000-0000-0000-000000000000", "count_successes1900000000-0000-0000-0000-000000000000", "count_successes2000000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("1d6", "2d6", "3d6", "4d6", "5d6", "6d6", "7d6", "8d6", "9d6", "10d6", "11d6", "12d6", "13d6", "14d6", "15d6", "16d6", "17d6", "18d6", "19d6", "20d6"); } @@ -237,16 +262,17 @@ public void getButtonMessageWithState_Shadowrun() { @Test public void getButtonMessageWithState_diceCalculator() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("dice_calculator", StateData.empty()), 1L, 2L); - assertThat(res.map(MessageDefinition::getContent)) + Assertions.assertThat(res.map(MessageDefinition::getContent)) .contains("Click the buttons to add dice to the set and then on Roll"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly("sum_custom_set1_button00000000-0000-0000-0000-000000000000", "sum_custom_set2_button00000000-0000-0000-0000-000000000000", "sum_custom_set3_button00000000-0000-0000-0000-000000000000", + "sum_custom_set4_button00000000-0000-0000-0000-000000000000", "sum_custom_set5_button00000000-0000-0000-0000-000000000000", "sum_custom_set6_button00000000-0000-0000-0000-000000000000", "sum_custom_set7_button00000000-0000-0000-0000-000000000000", @@ -258,15 +284,14 @@ public void getButtonMessageWithState_diceCalculator() { "sum_custom_set13_button00000000-0000-0000-0000-000000000000", "sum_custom_set14_button00000000-0000-0000-0000-000000000000", "sum_custom_set15_button00000000-0000-0000-0000-000000000000", - "sum_custom_set16_button00000000-0000-0000-0000-000000000000", "sum_custom_setroll00000000-0000-0000-0000-000000000000", "sum_custom_setclear00000000-0000-0000-0000-000000000000", "sum_custom_setback00000000-0000-0000-0000-000000000000"); - assertThat(res.map(MessageDefinition::getComponentRowDefinitions) - .stream() - .flatMap(Collection::stream) - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.map(MessageDefinition::getComponentRowDefinitions) + .stream() + .flatMap(Collection::stream) + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly("7", "8", "9", @@ -291,7 +316,7 @@ public void getButtonMessageWithState_diceCalculator() { @Test public void getButtonMessageWithState_other() { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), null, new State<>("-", StateData.empty()), 1L, 2L); - assertThat(res) + Assertions.assertThat(res) .isEmpty(); } @@ -299,16 +324,16 @@ public void getButtonMessageWithState_other() { @Test public void getWelcomeMessage() { MessageDefinition res = underTest.getWelcomeMessage(); - assertThat(res.getContent()) + Assertions.assertThat(res.getContent()) .isEqualTo(""" Welcome to the Button Dice Bot, use one of the example buttons below to start one of the RPG dice systems or use the slash command to configure your own custom dice system (see https://github.com/twonirwana/DiscordDiceBot for details or the slash command `/help`).\s You can also use the slash command `/r` to directly roll dice with. For help or feature request come to the support discord server: https://discord.gg/e43BsqKpFr"""); - assertThat(res.getComponentRowDefinitions() - .stream() - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getId)) + Assertions.assertThat(res.getComponentRowDefinitions() + .stream() + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getId)) .containsExactly( "welcomefate00000000-0000-0000-0000-000000000000", "welcomefate_image00000000-0000-0000-0000-000000000000", @@ -319,10 +344,10 @@ use one of the example buttons below to start one of the RPG dice systems or use "welcomeshadowrun00000000-0000-0000-0000-000000000000", "welcomecoin00000000-0000-0000-0000-000000000000", "welcomedice_calculator00000000-0000-0000-0000-000000000000"); - assertThat(res.getComponentRowDefinitions() - .stream() - .flatMap(s -> s.getButtonDefinitions().stream()) - .map(ButtonDefinition::getLabel)) + Assertions.assertThat(res.getComponentRowDefinitions() + .stream() + .flatMap(s -> s.getButtonDefinitions().stream()) + .map(ButtonDefinition::getLabel)) .containsExactly( "Fate", "Fate with dice images", @@ -348,17 +373,17 @@ use one of the example buttons below to start one of the RPG dice systems or use }) void createMessageDataForNewMessage(String buttonValue) { Optional res = underTest.createNewButtonMessageWithState(UUID.fromString("00000000-0000-0000-0000-000000000000"), new Config(null, AnswerFormatType.full, null, new DiceStyleAndColor(DiceImageStyle.none, "none")), new State<>(buttonValue, StateData.empty()), 1L, 2L); - assertThat(res).isPresent(); + Assertions.assertThat(res).isPresent(); } @Test public void shouldKeepExistingButtonMessage() { - assertThat(underTest.shouldKeepExistingButtonMessage(mock(ButtonEventAdaptor.class))).isTrue(); + Assertions.assertThat(underTest.shouldKeepExistingButtonMessage(Mockito.mock(ButtonEventAdaptor.class))).isTrue(); } @Test public void getAnswer() { - assertThat(underTest.getAnswer(null, null, 0L, 0L)).isEmpty(); + Assertions.assertThat(underTest.getAnswer(null, null, 0L, 0L)).isEmpty(); } @@ -366,29 +391,29 @@ public void getAnswer() { public void matchingComponentCustomId() { boolean res = underTest.matchingComponentCustomId("welcome,fate"); - assertThat(res).isTrue(); + Assertions.assertThat(res).isTrue(); } @Test public void matchingComponentCustomId_notMatch() { boolean res = underTest.matchingComponentCustomId("welcome2,fate"); - assertThat(res).isFalse(); + Assertions.assertThat(res).isFalse(); } @Test void matchingComponentCustomId_match() { - assertThat(underTest.matchingComponentCustomId("welcomefate")).isTrue(); + Assertions.assertThat(underTest.matchingComponentCustomId("welcomefate")).isTrue(); } @Test void matchingComponentCustomId_noMatch() { - assertThat(underTest.matchingComponentCustomId("welcome2fate")).isFalse(); + Assertions.assertThat(underTest.matchingComponentCustomId("welcome2fate")).isFalse(); } @Test void getName() { - assertThat(underTest.getCommandId()).isEqualTo("welcome"); + Assertions.assertThat(underTest.getCommandId()).isEqualTo("welcome"); } @@ -396,7 +421,7 @@ void getName() { void getCommandDefinition() { CommandDefinition res = underTest.getCommandDefinition(); - assertThat(res).isEqualTo(CommandDefinition.builder() + Assertions.assertThat(res).isEqualTo(CommandDefinition.builder() .name("welcome") .description("Displays the welcome message") .option(CommandDefinitionOption.builder() diff --git a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java index c0933c1b..90c3bf2d 100644 --- a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java +++ b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java @@ -74,12 +74,18 @@ static Stream generateDiceStyleDataPerColor() { @BeforeEach void setup() throws IOException { - FileUtils.cleanDirectory(new File("imageCache/")); + File cacheDirectory = new File("imageCache/"); + if(cacheDirectory.exists()){ + FileUtils.cleanDirectory(cacheDirectory); + } } @AfterEach void cleanUp() throws IOException { - FileUtils.cleanDirectory(new File("imageCache/")); + File cacheDirectory = new File("imageCache/"); + if(cacheDirectory.exists()){ + FileUtils.cleanDirectory(cacheDirectory); + } } @Test @@ -275,6 +281,58 @@ void getImageForRoll_noCacheForLargeDiceSets() throws ExpressionException, IOExc assertThat(getDataHash(res2)).isEqualTo("5672d47a89d51f0098bc154b22c72f1ea2059ba8d77a589adca8bd720c7ace2b"); } + @Test + void getImageForCustomRoll_cache() throws ExpressionException, IOException, InterruptedException { + List rolls1 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("1d[-1,0,1]"); + Supplier res1 = underTest.getImageForRoll(rolls1, new DiceStyleAndColor(DiceImageStyle.fate, "black")); + assertThat(res1).isNotNull(); + + File cacheFile = new File("imageCache/fate_black/9f295c8cb283c4dbc3c58ba29e5b93884bb3f0cb369ca66f3f932220db2fa6bf.png"); + assertThat(cacheFile).exists(); + long res1LastModified = cacheFile.lastModified(); + assertThat(getDataHash(res1)).isEqualTo("cb853512212a4e57391479cdae47cee5e014be1eed9511b1298908b12623c0bb"); + + Thread.sleep(100); + + List rolls2 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("1d[-1,0,1]"); + Supplier cachedRes1 = underTest.getImageForRoll(rolls2, new DiceStyleAndColor(DiceImageStyle.fate, "black")); + assertThat(cachedRes1).isNotNull(); + + File cacheFile2 = new File("imageCache/fate_black/9f295c8cb283c4dbc3c58ba29e5b93884bb3f0cb369ca66f3f932220db2fa6bf.png"); + assertThat(cacheFile2).exists(); + assertThat(cacheFile2.lastModified()).isEqualTo(res1LastModified); + assertThat(getDataHash(cachedRes1)).isEqualTo("cb853512212a4e57391479cdae47cee5e014be1eed9511b1298908b12623c0bb"); + + Thread.sleep(100); + + List rolls3 = new DiceEvaluator(new GivenNumberSupplier(2), 1000).evaluate("1d[-1,0,1]"); + Supplier res2 = underTest.getImageForRoll(rolls3, new DiceStyleAndColor(DiceImageStyle.fate, "black")); + assertThat(res2).isNotNull(); + + File cacheFile3 = new File("imageCache/fate_black/3b1ffd07870d80beda782bd15bd670798617a179419084d5a5206f91c7ce8048.png"); + assertThat(cacheFile3).exists(); + assertThat(cacheFile3.lastModified()).isNotEqualTo(res1LastModified); + + assertThat(getDataHash(res2)).isEqualTo("6aee5beb6715fbbfd1b808a4215c3d066488aeadd7f57d30b476b0286df3ef58"); + } + + @Test + void getImageForCustomRoll_noCacheForLargeDiceSets() throws ExpressionException, IOException { + List rolls1 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("7d[-1,0,1]"); + Supplier res1 = underTest.getImageForRoll(rolls1, new DiceStyleAndColor(DiceImageStyle.fate, "black")); + assertThat(res1).isNotNull(); + File cacheFolder = new File("imageCache/"); + assertThat(cacheFolder).isEmptyDirectory(); + assertThat(getDataHash(res1)).isEqualTo("b3929fb41bad88f279554cb27e729c05ca783f03134ffe531f6bd3c3cb315d5a"); + + List rolls2 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("7d[-1,0,1]"); + Supplier res2 = underTest.getImageForRoll(rolls2, new DiceStyleAndColor(DiceImageStyle.fate, "black")); + + assertThat(cacheFolder).isEmptyDirectory(); + assertThat(res2).isNotNull(); + assertThat(getDataHash(res2)).isEqualTo("b3929fb41bad88f279554cb27e729c05ca783f03134ffe531f6bd3c3cb315d5a"); + } + @Test void createRollCacheNameTest_3dRedWhite() throws ExpressionException { List rolls = new DiceEvaluator(new GivenNumberSupplier(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6), 1000).evaluate("6d6+1d6+3d6"); diff --git a/discord-connector/jda/src/main/java/de/janno/discord/connector/jda/JdaClient.java b/discord-connector/jda/src/main/java/de/janno/discord/connector/jda/JdaClient.java index 132c2beb..1328c9b4 100644 --- a/discord-connector/jda/src/main/java/de/janno/discord/connector/jda/JdaClient.java +++ b/discord-connector/jda/src/main/java/de/janno/discord/connector/jda/JdaClient.java @@ -177,7 +177,7 @@ public void onButtonInteraction(@NonNull ButtonInteractionEvent event) { } } ) - .setActivity(Activity.customStatus("Type /help or /welcome start")); + .setActivity(Activity.customStatus("Type /quickstart or /help")); ShardManager shardManager = shardManagerBuilder.build(); JdaMetrics.startGuildCountGauge(botInGuildIdSet);