DragonDice

Check-in [76166a5ce7]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Frontend tweaks, split help text into multiple topics rather than bombarding the user with messages
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 76166a5ce733bb042eb1c332fe980566b516b7360a30a07b63307db5a7ac9e79
User & Date: murphy 2020-04-29 09:34:56
Context
2020-04-29
11:51
Corrected command recognition pattern check-in: e9bc359947 user: murphy tags: trunk
09:34
Frontend tweaks, split help text into multiple topics rather than bombarding the user with messages check-in: 76166a5ce7 user: murphy tags: trunk
2020-04-28
23:03
Corrected computation of race without subrace check-in: 58a729d0be user: murphy tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to DragonDice.Bot/Session.fs.

168
169
170
171
172
173
174
175
176








177
178
179
180
181
182
183
184
...
190
191
192
193
194
195
196







197
198
199
200
201
202
203
204
205
206
...
207
208
209
210
211
212
213












214
215
216
217
218
219
220
221
...
231
232
233
234
235
236
237


238
239
240
241
242
243
244
...
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
  
  /// Evaluate a command sent to the bot.
  member __.AsyncEval(user:User, args:List<string>) : Async<CommandResult> =
    async {
      match args with
      | ["/start" | "/help"] ->
        return Markup (
          """I can manage characters, simulate dice rolls and compute statistical estimates for you. You can control me by sending these commands:









<b>Dice Rolls:</b>
/roll <i>N</i>d<i>S</i> [+- <i>D</i>] — Roll <i>N</i> <i>S</i>-sided dice and add / subtract <i>D</i> to / from the total.
/estimate <i>N</i>d<i>S</i> [+- <i>D</i>] — Estimate the result of rolling <i>N</i> <i>S</i>-sided dice and adding / subtracting <i>D</i> to / from the total.

<b>Ability Checks:</b>
/roll <i>ABILITY</i> [+- <i>D</i>] [, <i>OPTION</i>] — Roll a 20-sided die, add / subtract the ability modifier and <i>D</i>, and compare with <i>DC</i>.
/estimate <i>ABILITY</i> [+- <i>D</i>] [, <i>OPTION</i>] — Estimate the result of rolling a 20-sided die, adding / subtracting the ability modifier and <i>D</i>, and comparing with <i>DC</i>.

................................................................................
/proficiency — Determine the proficiency bonus of the active character.

<b>Check Options:</b>
dc|difficulty <i>DC</i> — The <i>DC</i> of an ability or skill check may be specified as a number or it may be one of the adjectives <i>Very Easy</i>, <i>Easy</i>, <i>Medium</i>, <i>Hard</i>, <i>Very Hard</i>, or <i>Impossible</i>.
ins[piration] d<i>S</i> — An <i>S</i>-sided inspiration die may be added to a check. If multiple of these options are given, the highest <i>S</i> is used.
adv[antage]|dis[advantage]|normal — Checks may be performed at advantage, disadvantage or normal conditions. If multiple of these options are specified, the majority of advantage or disadvantage options determines the conditions.








<b>Character State:</b>
/select [<i>NAME</i>] — Select the active character for the user who is asking.
/show|stat — Display a summary of the active character's statistics.
/talk — Display the set of common languages for all the active characters selected in the conversation.
/heal <i>HP</i> — Restore <i>HP</i> hit points up to the maximum of the active character. 
/hurt|damage <i>HP</i> [, <i>DAMAGE</i>] — Deal <i>HP</i> damage to the active character. If a damage type is specified, the character's defenses may apply.
/bolster <i>HP</i> — Add temporary hit points to the active character.
/rest[ore] — Restore hit points to maximum and remove temporary hit points from the active character.
/gain <i>GP</i> — Add an amount of currency to the active character.
/spend <i>GP</i> — Remove an amount of currency to the active character.
................................................................................

<b>Hit Points:</b>
The <i>HP</i> argument to the health management commands may be a dice roll specification or a constant non-negative integer.

<b>Currency:</b>
The <i>GP</i> argument to the currency management commands may be an amount suffixed with <i>cp</i>, <i>sp</i>, <i>ep</i>, <i>gp</i>, or <i>pp</i>.













<b>Character Design:</b>
/create [<i>NAME</i>] — Create a new character and select it for the user who is asking.
/open5e <i>QUERY</i> — Import a new character from Open5e and select it for the user who is asking.
/setname <i>NAME</i> — Change the name of the active character.
/setrace <i>RACE</i> — Change the race of the active character. This operation does not work on imported characters.
/set ac =|+=|-= <i>N</i> — Change the armor class of the active character.
/set hp =|+=|-= <i>N</i> — Change the maximum hit points of the active character.
/set <i>SPEED</i> =|+=|-= <i>N</i> — Change a movement speed of the active character.
................................................................................
/addlanguage <i>NAME</i> — Add a language proficiency to the active character.
/removelanguage <i>NAME</i> — Remove a language proficiency from the active character.

<b>Character Storage:</b>
/save — Save the active character.
/reload <i>NAME</i> — Reload the active character. To confirm, the name must match.
/delete <i>NAME</i> — Delete the active character. To confirm, the name must match.


""",
          List.empty
        )
        
      | ["/roll" | "/estimate"] ->
        let dup x = x, x
        return Markup (
................................................................................
      | ["/proficiency"] ->
        let chr = selectedCharacter user
        return Markup (
          sprintf "<i>%s</i> : Proficiency Bonus = <b>%+d</b>" (HttpUtility.HtmlEncode chr.Name) chr.ProficiencyBonus,
          List.empty
        )
        
      | ["/show" | "/stat"] ->
        let chr = selectedCharacter user
        let buffer = Text.StringBuilder(1024)
        buffer
          .Append("<b>Name:</b> ").Append(HttpUtility.HtmlEncode chr.Name).Append('\n')
          .Append("<i>").Append(HttpUtility.HtmlEncode chr.Race)
        |> ignore
        if not (String.IsNullOrEmpty chr.Profession) then







|
|
>
>
>
>
>
>
>
>
|







 







>
>
>
>
>
>
>
|

|







 







>
>
>
>
>
>
>
>
>
>
>
>
|







 







>
>







 







|







168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
...
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
...
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
  
  /// Evaluate a command sent to the bot.
  member __.AsyncEval(user:User, args:List<string>) : Async<CommandResult> =
    async {
      match args with
      | ["/start" | "/help"] ->
        return Markup (
          "I can manage characters, simulate dice rolls and compute statistical estimates for you. Choose one of the topics below to learn more.",
          [
            ["Dice Rolls", "roll"]
            ["Character State", "state"]
            ["Character Design", "create"]
          ]
        )
        
      | ["/start" | "/help"; "roll"] ->
        return Markup (
          """<b>Dice Rolls:</b>
/roll <i>N</i>d<i>S</i> [+- <i>D</i>] — Roll <i>N</i> <i>S</i>-sided dice and add / subtract <i>D</i> to / from the total.
/estimate <i>N</i>d<i>S</i> [+- <i>D</i>] — Estimate the result of rolling <i>N</i> <i>S</i>-sided dice and adding / subtracting <i>D</i> to / from the total.

<b>Ability Checks:</b>
/roll <i>ABILITY</i> [+- <i>D</i>] [, <i>OPTION</i>] — Roll a 20-sided die, add / subtract the ability modifier and <i>D</i>, and compare with <i>DC</i>.
/estimate <i>ABILITY</i> [+- <i>D</i>] [, <i>OPTION</i>] — Estimate the result of rolling a 20-sided die, adding / subtracting the ability modifier and <i>D</i>, and comparing with <i>DC</i>.

................................................................................
/proficiency — Determine the proficiency bonus of the active character.

<b>Check Options:</b>
dc|difficulty <i>DC</i> — The <i>DC</i> of an ability or skill check may be specified as a number or it may be one of the adjectives <i>Very Easy</i>, <i>Easy</i>, <i>Medium</i>, <i>Hard</i>, <i>Very Hard</i>, or <i>Impossible</i>.
ins[piration] d<i>S</i> — An <i>S</i>-sided inspiration die may be added to a check. If multiple of these options are given, the highest <i>S</i> is used.
adv[antage]|dis[advantage]|normal — Checks may be performed at advantage, disadvantage or normal conditions. If multiple of these options are specified, the majority of advantage or disadvantage options determines the conditions.

/help — Back to Contents
""",
          List.empty
        )

      | ["/start" | "/help"; "state"] ->
        return Markup (
          """<b>Character State:</b>
/select [<i>NAME</i>] — Select the active character for the user who is asking.
/show|stat|state|stats — Display a summary of the active character's statistics.
/talk — Display the set of common languages for all the active characters selected in the conversation.
/heal <i>HP</i> — Restore <i>HP</i> hit points up to the maximum of the active character. 
/hurt|damage <i>HP</i> [, <i>DAMAGE</i>] — Deal <i>HP</i> damage to the active character. If a damage type is specified, the character's defenses may apply.
/bolster <i>HP</i> — Add temporary hit points to the active character.
/rest[ore] — Restore hit points to maximum and remove temporary hit points from the active character.
/gain <i>GP</i> — Add an amount of currency to the active character.
/spend <i>GP</i> — Remove an amount of currency to the active character.
................................................................................

<b>Hit Points:</b>
The <i>HP</i> argument to the health management commands may be a dice roll specification or a constant non-negative integer.

<b>Currency:</b>
The <i>GP</i> argument to the currency management commands may be an amount suffixed with <i>cp</i>, <i>sp</i>, <i>ep</i>, <i>gp</i>, or <i>pp</i>.

<b>Character Storage:</b>
/save — Save the active character.
/reload <i>NAME</i> — Reload the active character. To confirm, the name must match.
/delete <i>NAME</i> — Delete the active character. To confirm, the name must match.

/help — Back to Contents
""",
          List.empty
        )

      | ["/start" | "/help"; "create"] ->
        return Markup (
          """<b>Character Design:</b>
/create [<i>NAME</i>] — Create a new character and select it for the user who is asking.
/open5e <i>QUERY</i> — Import a new character from Open5e and select it for the user who is asking.
/setname <i>NAME</i> — Change the name of the active character.
/setrace <i>RACE</i> — Change the race of the active character. This operation does not work on imported characters.
/set ac =|+=|-= <i>N</i> — Change the armor class of the active character.
/set hp =|+=|-= <i>N</i> — Change the maximum hit points of the active character.
/set <i>SPEED</i> =|+=|-= <i>N</i> — Change a movement speed of the active character.
................................................................................
/addlanguage <i>NAME</i> — Add a language proficiency to the active character.
/removelanguage <i>NAME</i> — Remove a language proficiency from the active character.

<b>Character Storage:</b>
/save — Save the active character.
/reload <i>NAME</i> — Reload the active character. To confirm, the name must match.
/delete <i>NAME</i> — Delete the active character. To confirm, the name must match.

/help — Back to Contents
""",
          List.empty
        )
        
      | ["/roll" | "/estimate"] ->
        let dup x = x, x
        return Markup (
................................................................................
      | ["/proficiency"] ->
        let chr = selectedCharacter user
        return Markup (
          sprintf "<i>%s</i> : Proficiency Bonus = <b>%+d</b>" (HttpUtility.HtmlEncode chr.Name) chr.ProficiencyBonus,
          List.empty
        )
        
      | ["/show" | "/stat" | "/state" | "/stats"] ->
        let chr = selectedCharacter user
        let buffer = Text.StringBuilder(1024)
        buffer
          .Append("<b>Name:</b> ").Append(HttpUtility.HtmlEncode chr.Name).Append('\n')
          .Append("<i>").Append(HttpUtility.HtmlEncode chr.Race)
        |> ignore
        if not (String.IsNullOrEmpty chr.Profession) then

Changes to DragonDice.Bot/Worker.fs.

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
  let sendReply (bot:TelegramBotClient) (ssn:Session) (qid:int) (cmd:List<string>) (reply:Choice<CommandResult, exn>) =
    async {
      let tag = replyTag reply
      let! stop = Async.CancellationToken
      let! reply =
        match reply with
        | Choice1Of2 (Markup (html, buttons)) ->
          if html.Length > 4096 then
            let html = html.Split("\n\n", StringSplitOptions.RemoveEmptyEntries)
            let task =
              bot.SendTextMessageAsync(
                ssn.ChatId, html.[0], ParseMode.Html,
                replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
              )
            for i = 1 to html.Length - 1 do
              bot.SendTextMessageAsync(
                ssn.ChatId, html.[i], ParseMode.Html,
                cancellationToken = stop
              )
              |> ignore
            Async.AwaitTask task
          else
            bot.SendTextMessageAsync(
              ssn.ChatId, html, ParseMode.Html,
              replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
            )
            |> Async.AwaitTask
        | Choice1Of2 (Figure (bitmap, caption)) ->
          let data = new IO.MemoryStream()
          bitmap.Save(data, Imaging.ImageFormat.Png)
          data.Position <- 0L
          bot.SendPhotoAsync(
            ssn.ChatId, InputMedia(data, "figure.png"), caption, ParseMode.Html,
            cancellationToken = stop
................................................................................
    async {
      let tag = replyTag reply
      let! stop = Async.CancellationToken
      if fst info.ReplyId = tag then
        let rid = snd info.ReplyId
        match reply with
        | Choice1Of2 (Markup (html, buttons)) ->
          if html.Length > 4096 then
            let html = html.Split("\n\n", StringSplitOptions.RemoveEmptyEntries)
            bot.EditMessageTextAsync(
              ssn.ChatId, rid, html.[0], ParseMode.Html,
              replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
            )
            |> ignore
            for i = 1 to html.Length - 1 do
              bot.SendTextMessageAsync(
                ssn.ChatId, html.[i], ParseMode.Html,
                cancellationToken = stop
              )
              |> ignore
          else
            bot.EditMessageTextAsync(
              ssn.ChatId, rid, html, ParseMode.Html,
              replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
            )
            |> ignore
        | Choice1Of2 (Figure (bitmap, caption)) ->
          let data = new IO.MemoryStream()
          bitmap.Save(data, Imaging.ImageFormat.Png)
          data.Position <- 0L
          bot.EditMessageMediaAsync(
            ssn.ChatId, rid,
            InputMediaPhoto(InputMedia(data, "figure.png"), Caption = caption, ParseMode = ParseMode.Html),
................................................................................
        ssn.Replies.[info.QueryId] <- info
        ssn.Replies.[rid] <- info
      else
        let del = bot.DeleteMessageAsync(ssn.ChatId, snd info.ReplyId, cancellationToken = stop)
        let! reply =
          match reply with
          | Choice1Of2 (Markup (html, buttons)) ->
            if html.Length > 4096 then
              let html = html.Split("\n\n", StringSplitOptions.RemoveEmptyEntries)
              let task =
                bot.SendTextMessageAsync(
                  ssn.ChatId, html.[0], ParseMode.Html,
                  replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
                )
              for i = 1 to html.Length - 1 do
                bot.SendTextMessageAsync(
                  ssn.ChatId, html.[i], ParseMode.Html,
                  cancellationToken = stop
                )
                |> ignore
              Async.AwaitTask task
            else
              bot.SendTextMessageAsync(
                ssn.ChatId, html, ParseMode.Html,
                replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
              )
              |> Async.AwaitTask
          | Choice1Of2 (Figure (bitmap, caption)) ->
            let data = new IO.MemoryStream()
            bitmap.Save(data, Imaging.ImageFormat.Png)
            data.Position <- 0L
            bot.SendPhotoAsync(
              ssn.ChatId, InputMedia(data, "figure.png"), caption, ParseMode.Html,
              cancellationToken = stop







<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|







 







<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|







 







<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|







83
84
85
86
87
88
89



90












91
92
93
94
95
96
97
98
99
100
101
...
118
119
120
121
122
123
124


125












126
127
128
129
130
131
132
133
134
135
136
...
147
148
149
150
151
152
153



154












155
156
157
158
159
160
161
162
163
164
165
  let sendReply (bot:TelegramBotClient) (ssn:Session) (qid:int) (cmd:List<string>) (reply:Choice<CommandResult, exn>) =
    async {
      let tag = replyTag reply
      let! stop = Async.CancellationToken
      let! reply =
        match reply with
        | Choice1Of2 (Markup (html, buttons)) ->



          bot.SendTextMessageAsync(












            ssn.ChatId, html, ParseMode.Html,
            replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
          )
          |> Async.AwaitTask
        | Choice1Of2 (Figure (bitmap, caption)) ->
          let data = new IO.MemoryStream()
          bitmap.Save(data, Imaging.ImageFormat.Png)
          data.Position <- 0L
          bot.SendPhotoAsync(
            ssn.ChatId, InputMedia(data, "figure.png"), caption, ParseMode.Html,
            cancellationToken = stop
................................................................................
    async {
      let tag = replyTag reply
      let! stop = Async.CancellationToken
      if fst info.ReplyId = tag then
        let rid = snd info.ReplyId
        match reply with
        | Choice1Of2 (Markup (html, buttons)) ->


          bot.EditMessageTextAsync(












            ssn.ChatId, rid, html, ParseMode.Html,
            replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
          )
          |> ignore
        | Choice1Of2 (Figure (bitmap, caption)) ->
          let data = new IO.MemoryStream()
          bitmap.Save(data, Imaging.ImageFormat.Png)
          data.Position <- 0L
          bot.EditMessageMediaAsync(
            ssn.ChatId, rid,
            InputMediaPhoto(InputMedia(data, "figure.png"), Caption = caption, ParseMode = ParseMode.Html),
................................................................................
        ssn.Replies.[info.QueryId] <- info
        ssn.Replies.[rid] <- info
      else
        let del = bot.DeleteMessageAsync(ssn.ChatId, snd info.ReplyId, cancellationToken = stop)
        let! reply =
          match reply with
          | Choice1Of2 (Markup (html, buttons)) ->



            bot.SendTextMessageAsync(












              ssn.ChatId, html, ParseMode.Html,
              replyMarkup = makeInlineKeyboard buttons, cancellationToken = stop
            )
            |> Async.AwaitTask
          | Choice1Of2 (Figure (bitmap, caption)) ->
            let data = new IO.MemoryStream()
            bitmap.Save(data, Imaging.ImageFormat.Png)
            data.Position <- 0L
            bot.SendPhotoAsync(
              ssn.ChatId, InputMedia(data, "figure.png"), caption, ParseMode.Html,
              cancellationToken = stop