DragonDice

Check-in [89253a68b2]
Login

Check-in [89253a68b2]

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
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 89253a68b2a0bce108228be39ad126f8e568011164077bc38000bbe851649695
User & Date: murphy 2020-05-07 20:53:19.000
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
Unified Diff Ignore Whitespace Patch
Changes to DragonDice.Bot/Session.fs.
54
55
56
57
58
59
60


61










62
63
64
65
66
67
68
            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







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







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
            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
187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
202
203
/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.







|
>
|
<







199
200
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
/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.
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
            .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







|
|

|







336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
            .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
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
            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,







|
|

|







418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
            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,
453
454
455
456
457
458
459
460
461

462
463
464
465
466
467
468
                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"] ->







|

>







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
                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"] ->
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
        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)







|







|













|

<






|







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
        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)