[拡張版]PythonでDiscordのTRPG用のダイスボットを自作してみた

前回、[PythonでDiscordのTRPG用のダイスボットを自作してみた](https://qiita.com/Sashimimochi/items/3cbea852f133fed5d44b)というものを作ってみました。
完成したウキウキ気分で早速、実際にオフセで使ってみたら使いにくいと結構不評でした。このままでは悔しいので、出てきた改善要望から特に要望の強かった機能を追加実装してみました。追加実装したのは次の4つです。

1. ショットカットコマンド
1. 一時的技能値補正
2. 複数ダイス
1. 対抗ロール
1. シークレットダイス

需要があるのか不明ですが、アップデート版のコードは[GitHub](https://github.com/Sashimimochi/discord_dice_bot)に上げておきます。

## ショートカットコマンド
一番不評だったのがダイスを振るコマンドが長いということでした。「いちいち`dice 1d100 SAN`などと打つのは面倒」ということでしたので、単に技能名だけで振れるようにショートカットコマンドを実装します。
Discordにはスラッシュコマンドが存在するみたいなので、それに倣って

```Console
/(技能名)
```
でダイスを振れるようにしました。技能名は前回と同様にスプレットシートの技能名欄の文字です。ただ、当然既存のスラッシュコマンドとバッティングする技能名だと誤作動すると思います。

このままだとダイスを指定しないことになるのですが、ダイスの情報はスプレッドシートから取ってくることにします。ショートカットコマンドが欲しいという要望から推測できるように、基本的には技能に対して振るダイスはほぼ固定なのでデフォルトダイスを指定することでショートカットコマンドで技能を振れるようにしました。

結果、スプレットシートはこんな感じにしています。

| NAME | キャラ名 | minus | init | 職業P | 興味P | dice |
|:--|--:|--:|--:|--:|--:|:--|
| STR | 15 | 0 | 15 | 0 | 0 | 1d100 |
| CON | 14 | 0 | 14 | 0 | 0 | 1d100 |
| POW | 15 | 0 | 15 | 0 | 0 | 1d100 |
| アイデア | 45 | 0 | 45 | 0 | 0 | 1d100 |
| 幸運 | 75 | 0 | 75 | 0 | 0 | 1d100 |
| 知識 | 75 | 0 | 75 | 0 | 0 | 1d100 |

## 一時的技能値補正
現状でもスプレッドシートの補正欄(minus)に補正値を入力すれば補正がかかるようにスプレットシートの方で計算式を組んではいます。ですが、実際にセッションを回してみると、PLの皆さんはセッション中にスプレットシートをいじるのは面倒なようで、補正がかかっているときは任意ダイスを振って成否は頭の中で判定していました。
そこで、スプレットシートを編集しなくてもそのダイスロール限定でダイス結果を補正できるようにしました。計算対象は単純な四則演算としています。

```Console
/(技能名)+20
/(技能名)-20
/(技能名)*5
/(技能名)/2
```

内部では技能名と演算子と補正値に分離して判定をしています。この時に便利なのが`eval`という関数です。`eval`を使うと文字列に対して計算してくれます。例えば、

```py
print(eval('1+2'))
# 3
```
と言った感じです。これを使って補正付きの技能値を算出します。

```python:dice_bot.py
def calc_ability(input_msg, charactor):
    operators = ['+', '-', '*', '/']
    for opt in operators:
        if  opt in input_msg:
            parsed_msg = parse('{}'+opt+'{}', input_msg)
            ability_name = parsed_msg[0]
            correction = parsed_msg[1]
            operator = opt
            ability = eval(
                charactor[ability_name]['value']+operator+correction
            )
            break
    else:
        ability_name = input_msg
        correction = ''
        operator = ''
        ability = charactor[ability_name]['value']

    return ability_name, int(ability), (charactor[ability_name]['value'], operator, correction)
```
あとは、補正された技能値に対して成否判定を行えば無事完成です。

## 複数ダイス
これまでは、`1d4+1d6`などのようにサイズの異なる複数のダイスを振りたいときは、`1d4`と`1d6`のダイスを別々に振らないといけなかったのですが、上記のようなロールをしたいタイミングがしばしばあったので実装してみました。

```Console
/dice 1d4+1d6
```
ちなみに

```Console
/dice 1d4+1d6+2-1
```
みたいなこともできます。

## 対抗ロール
これは完全にKPの準備不足だったのですが、対抗ロールの成功値を調べるのに手こずったので、対抗ロールもbotで判定できるようにしてやろうというものです。

やってることは単純に以下の式で算出される達成値を使って他の技能値判定と同じように成否の判定をしているだけです。
$$達成値 = \frac{対抗する側-対抗される側}{2}$$
[【クトゥルフ神話TRPG】対抗･抵抗ロールの求め方について](https://imademogamesuki.com/pl/teikou.html)

対抗ロールの実行コマンドは以下のように定義しました。

```Console
VS (対抗する側の数値)/(対抗される側の数値)
```

## シークレットダイス
Discordには一部のMarkdown記法が使えるようです。その中にネタバレ防止用の**スポイラータグ**というものがあります。
[【Discord】テキストを装飾する方法 | Markdown記法 チートシート](https://qiita.com/xero/items/6026ed007d5d34623a50)

メッセージにマスクがかかっており、クリックをすることで表示状態にできるようです。これを使えば、シークレットダイスが実現できそうです(PLは勝手に表示しないという信頼の下であれば)。

```Console
# シークレットダイスを振る時
/dice 1d100 secret
# 判定つきでシークレットダイスを振る時
/dice 1d100(||10||) secret
```
Discord上ではこんな感じに見えます。
![qiita.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199063/1d7ad190-af37-eec9-2cc3-64f2de9a319b.png)


これでbotだけでできることが増えたので、セッションで使ってもらえるようになったのではないでしょうか。

それでは、皆さんもよきTRPGライフを。