DragonDice

Check-in [89253a68b2]
Login

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

Overview
Comment:Allow deltas for skill checks and in some other commands
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 89253a68b2a0bce108228be39ad126f8e568011164077bc38000bbe851649695
User & Date: murphy 2020-05-07 20:53:19
Context
2020-05-08
00:37
Added spell slot management check-in: f75d632b0b user: murphy tags: trunk
2020-05-07
20:53
Allow deltas for skill checks and in some other commands check-in: 89253a68b2 user: murphy tags: trunk
2020-05-06
11:24
Replacing language placeholders: Merfolk -> Sylvan (Aquan), Vampire -> Infernal check-in: 706c71665d user: murphy tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to DragonDice.Bot/Session.fs.

54
55
56
57
58
59
60


61










62
63
64
65
66
67
68
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
...
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
...
453
454
455
456
457
458
459
460
461

462
463
464
465
466
467
468
....
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
            0
        Some (attr, delta)
      )
    else
      None
      
  static let (|Skill|_|) s =


    Skill.tryParse s










    
  static let DifficultyPattern = Regex(@"^(?:dc|difficulty)\s+(.+)$", RegexOptions.CultureInvariant ||| RegexOptions.IgnoreCase)
  static let parsePassiveOption _ s =
    let it = DifficultyPattern.Match(s)
    if it.Success then
      Difficulty.ofString it.Groups.[1].Value
    else
................................................................................
/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>.

<b>Skill Checks:</b>
/roll <i>SKILL</i> [, <i>OPTION</i>] — Roll a 20-sided die, add / subtract the skill and ability modifiers, and compare with <i>DC</i>.
/passive <i>SKILL</i> [, dc|difficulty <i>DC</i>] — Determine the passive score using the skill and ability modifiers, and compare with <i>DC</i>.
/estimate <i>SKILL</i> [, <i>OPTION</i>] — Estimate the result of rolling a 20-sided die, adding / subtracting the skill and ability modifiers, and comparing with <i>DC</i>.
/spellsave <i>SKILL</i> — Determine the spell save DC using the skill and ability modifiers of a magic skill.
/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.
................................................................................
            .Append(if result.IsSuccess then "Success" else "Failure")
            .Append("</b>")
          |> ignore
        else
          buffer.Append("<b>").Append(result.Score).Append("</b>") |> ignore
        return Markup (buffer.ToString(), List.empty)
        
      | "/roll" :: "?skill" :: Skill skill :: opts
      | "/roll" :: Skill skill :: opts ->
        let chr = selectedCharacter user
        let check = (chr.GetCheck(skill, 0), opts) ||> List.fold parseCheckOption
        let result = lock random (fun () -> Check.simulate random check)
        let buffer =
          Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
            .Append(Check.toDiceString check)
        if check.Difficulty > 0 then
          buffer.Append(" ≥ ").Append(check.Difficulty) |> ignore
        buffer.Append(" → ") |> ignore
................................................................................
            plotCheckEstimates check,
            sprintf "<i>%s</i> : %s ∈ [%d, %d] → E = <b>%.2f</b>\nP[Critical] = <b>%.1f%%</b>\n" <|
            (HttpUtility.HtmlEncode chr.Name) <|
            (Check.toDiceString check) <| check.Minimum <| check.Maximum <|
            result.Score <| result.IsCritical * 100.0
          )
        
      | "/estimate" :: "?skill" :: Skill skill :: opts
      | "/estimate" :: Skill skill :: opts ->
        let chr = selectedCharacter user
        let check = (chr.GetCheck(skill, 0), opts) ||> List.fold parseCheckOption
        if check.Difficulty > 0 then
          let result = Check.estimate check
          return Markup (
            sprintf "<i>%s</i> : %s ∈ [%d, %d] ≥ %d → E = %.2f\nP[Critical] = <b>%.1f%%</b>\nP[Success] = <b>%.1f%%</b>\n" <|
            (HttpUtility.HtmlEncode chr.Name) <|
            (Check.toDiceString check) <| check.Minimum <| check.Maximum <| check.Difficulty <|
            result.Score <| result.IsCritical * 100.0 <| result.IsSuccess * 100.0,
................................................................................
                None
            )
            >> Seq.toList
          )
          |> Seq.toList
        )
        
      | ["/spellsave"; Skill skill] ->
        let chr = selectedCharacter user

        let score = chr.GetSpellSave(skill)
        return Markup (
          sprintf "<i>%s</i> : DC = <b>%d</b>" (HttpUtility.HtmlEncode chr.Name) score,
          List.empty
        )
        
      | ["/proficiency"] ->
................................................................................
        for KeyValue (sense, v) in chr.Senses do
          if v > 0<ft> then
            if buffer.Length > pos then buffer.Append(", ") |> ignore
            buffer.Append(Sense.toString sense).Append(' ').Append(v).Append(" ft") |> ignore
        if buffer.Length = pos then buffer.Append("None") |> ignore
        return Markup (buffer.ToString(), List.empty)

      | ["/set"; Assignment (Skill skill, op, v)] ->
        let chr = selectedCharacter user
        let v = int v
        chr.Skills <-
          chr.Skills.With(
            match op with
            | '+' -> [skill, chr.Skills.[skill] + v]
            | '-' -> [skill, chr.Skills.[skill] - v]
            | _   -> [skill, v]
          )
        let buffer = Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
        let pos = buffer.Append("Skills = ").Length
        for KeyValue (skill, v) in chr.Skills do
          if v > 0 then
            let v = v + Ability.modifier chr.Abilities.[Skill.ability skill]
            if buffer.Length > pos then buffer.Append(", ") |> ignore
            buffer.Append(Skill.toString skill)
              .AppendFormat(CultureInfo.InvariantCulture, " {0:+0;-#}", v)
            |> ignore
        if buffer.Length = pos then buffer.Append("None") |> ignore
        return Markup (buffer.ToString(), List.empty)

      | ["/set"; Assignment (attr, op, v)] ->
        let chr = selectedCharacter user
        let attr = Ability.ofString attr
        let v = int v
        chr.Abilities <-
          chr.Abilities.With(
            match op with
            | '+' -> [attr, max 0 (chr.Abilities.[attr] + v)]
            | '-' -> [attr, max 0 (chr.Abilities.[attr] - v)]
            | _   -> [attr, max 0 v]
          )
        let buffer = Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
        let pos = buffer.Append("Abilities = ").Length
        for KeyValue (attr, v) in chr.Abilities do
          if buffer.Length > pos then buffer.Append(", ") |> ignore
          buffer.Append(Ability.toString attr)
            .AppendFormat(CultureInfo.InvariantCulture, " {0:D} ({1:+0;-#})", v, Ability.modifier v)







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







 







|
|
|







 







|
|

|







 







|
|

|







 







|

>







 







|







|













|

<






|







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
...
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
...
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
....
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051

1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
            0
        Some (attr, delta)
      )
    else
      None
      
  static let (|Skill|_|) s =
    let it = AbilityPattern.Match(s)
    if it.Success then
      Skill.tryParse it.Groups.[1].Value
      |> Option.bind (fun skill ->
        let delta =
          if it.Groups.[2].Success then
            int (it.Groups.[3].Value + it.Groups.[4].Value)
          else
            0
        Some (skill, delta)
      )
    else
      None
    
  static let DifficultyPattern = Regex(@"^(?:dc|difficulty)\s+(.+)$", RegexOptions.CultureInvariant ||| RegexOptions.IgnoreCase)
  static let parsePassiveOption _ s =
    let it = DifficultyPattern.Match(s)
    if it.Success then
      Difficulty.ofString it.Groups.[1].Value
    else
................................................................................
/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>.

<b>Skill Checks:</b>
/roll <i>SKILL</i> [+- <i>D</i>] [, <i>OPTION</i>] — Roll a 20-sided die, add / subtract the skill and ability modifiers and <i>D</i>, and compare with <i>DC</i>.
/estimate <i>SKILL</i> [+- <i>D</i>] [, <i>OPTION</i>] — Estimate the result of rolling a 20-sided die, adding / subtracting the skill and ability modifiers and <i>D</i>, and comparing with <i>DC</i>.
/passive <i>SKILL</i> [, <i>OPTION</i>] — Determine the passive score using the skill and ability modifiers, and compare with <i>DC</i>.
/spellsave <i>SKILL</i> — Determine the spell save DC using the skill and ability modifiers of a magic skill.
/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.
................................................................................
            .Append(if result.IsSuccess then "Success" else "Failure")
            .Append("</b>")
          |> ignore
        else
          buffer.Append("<b>").Append(result.Score).Append("</b>") |> ignore
        return Markup (buffer.ToString(), List.empty)
        
      | "/roll" :: "?skill" :: Skill (skill, delta) :: opts
      | "/roll" :: Skill (skill, delta) :: opts ->
        let chr = selectedCharacter user
        let check = (chr.GetCheck(skill, 0) + delta, opts) ||> List.fold parseCheckOption
        let result = lock random (fun () -> Check.simulate random check)
        let buffer =
          Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
            .Append(Check.toDiceString check)
        if check.Difficulty > 0 then
          buffer.Append(" ≥ ").Append(check.Difficulty) |> ignore
        buffer.Append(" → ") |> ignore
................................................................................
            plotCheckEstimates check,
            sprintf "<i>%s</i> : %s ∈ [%d, %d] → E = <b>%.2f</b>\nP[Critical] = <b>%.1f%%</b>\n" <|
            (HttpUtility.HtmlEncode chr.Name) <|
            (Check.toDiceString check) <| check.Minimum <| check.Maximum <|
            result.Score <| result.IsCritical * 100.0
          )
        
      | "/estimate" :: "?skill" :: Skill (skill, delta) :: opts
      | "/estimate" :: Skill (skill, delta) :: opts ->
        let chr = selectedCharacter user
        let check = (chr.GetCheck(skill, 0) + delta, opts) ||> List.fold parseCheckOption
        if check.Difficulty > 0 then
          let result = Check.estimate check
          return Markup (
            sprintf "<i>%s</i> : %s ∈ [%d, %d] ≥ %d → E = %.2f\nP[Critical] = <b>%.1f%%</b>\nP[Success] = <b>%.1f%%</b>\n" <|
            (HttpUtility.HtmlEncode chr.Name) <|
            (Check.toDiceString check) <| check.Minimum <| check.Maximum <| check.Difficulty <|
            result.Score <| result.IsCritical * 100.0 <| result.IsSuccess * 100.0,
................................................................................
                None
            )
            >> Seq.toList
          )
          |> Seq.toList
        )
        
      | ["/spellsave"; skill] ->
        let chr = selectedCharacter user
        let skill = Skill.ofString skill
        let score = chr.GetSpellSave(skill)
        return Markup (
          sprintf "<i>%s</i> : DC = <b>%d</b>" (HttpUtility.HtmlEncode chr.Name) score,
          List.empty
        )
        
      | ["/proficiency"] ->
................................................................................
        for KeyValue (sense, v) in chr.Senses do
          if v > 0<ft> then
            if buffer.Length > pos then buffer.Append(", ") |> ignore
            buffer.Append(Sense.toString sense).Append(' ').Append(v).Append(" ft") |> ignore
        if buffer.Length = pos then buffer.Append("None") |> ignore
        return Markup (buffer.ToString(), List.empty)

      | ["/set"; Assignment (Skill (skill, delta), op, v)] ->
        let chr = selectedCharacter user
        let v = int v
        chr.Skills <-
          chr.Skills.With(
            match op with
            | '+' -> [skill, chr.Skills.[skill] + v]
            | '-' -> [skill, chr.Skills.[skill] - v]
            | _   -> [skill, v - delta]
          )
        let buffer = Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
        let pos = buffer.Append("Skills = ").Length
        for KeyValue (skill, v) in chr.Skills do
          if v > 0 then
            let v = v + Ability.modifier chr.Abilities.[Skill.ability skill]
            if buffer.Length > pos then buffer.Append(", ") |> ignore
            buffer.Append(Skill.toString skill)
              .AppendFormat(CultureInfo.InvariantCulture, " {0:+0;-#}", v)
            |> ignore
        if buffer.Length = pos then buffer.Append("None") |> ignore
        return Markup (buffer.ToString(), List.empty)

      | ["/set"; Assignment (Ability (attr, delta), op, v)] ->
        let chr = selectedCharacter user

        let v = int v
        chr.Abilities <-
          chr.Abilities.With(
            match op with
            | '+' -> [attr, max 0 (chr.Abilities.[attr] + v)]
            | '-' -> [attr, max 0 (chr.Abilities.[attr] - v)]
            | _   -> [attr, max 0 (v - delta)]
          )
        let buffer = Text.StringBuilder("<i>").Append(HttpUtility.HtmlEncode chr.Name).Append("</i> : ")
        let pos = buffer.Append("Abilities = ").Length
        for KeyValue (attr, v) in chr.Abilities do
          if buffer.Length > pos then buffer.Append(", ") |> ignore
          buffer.Append(Ability.toString attr)
            .AppendFormat(CultureInfo.InvariantCulture, " {0:D} ({1:+0;-#})", v, Ability.modifier v)