PythonでDiscordのTRPG用のダイスボットを自作してみた
始まり
始めに断っておきますが、私はTRPGもDiscordもにわか勢です。
最近、身内でTRPG(主にCoC)をぼちぼちやるのですが、毎回ダイス振って判定するのってメンドくさいよねって話になりました。 セッション環境はdiscordを使い始めたので、discord上で動くbotにしようということになりました。正直なところ、既に先人のお方が作られた素晴らしいものがあるので「discord-bcdicebot使えばよいのでは?」というのが賢い選択だと思います。 ですが、ポンコツの私には使い方がわからなかったので、勉強も兼ねて友人に手伝ってもらいながら作ってみました。
Python + Googleスプレッドシート (+ Heroku)で実現しています。 ソースコード全体はGitHubに晒しておきます。
PythonからGoogleスプレッドシートにアクセスする
ダイスを振るだけなら不要なのですが、技能の成否判定も自動でしたいのでキャラクターシートをGoogleスプレッドシートに作って判定することにしました。 Googleスプレッドシートの操作方法はこちらを参考にしました。
こちらを参考にOAuth用のクライアントIDを作成してjsonファイルをダウンロードするところまで進めます。 セキュリティを度外視するならGoogle Apps ScriptでHTTPリクエストを受け付けるという方法もあるかとは思います。
スプレッドシートの仕様は適当でこんな感じです。
技能 | 合計値 | 純減 | 初期値 | 職業P | 興味P |
---|---|---|---|---|---|
HP | 9 | 0 | 9 | 0 | 0 |
MP | 7 | 0 | 7 | 0 | 0 |
SAN | 60 | 0 | 60 | 0 | 0 |
db | 1d4 | 0 | 1d4 | 0 | 0 |
こぶし | 50 | 0 | 50 | 0 | 0 |
図書館 | 50 | 0 | 25 | 25 | 0 |
初期値の決め方は下記を参照しました。
この中で使うのは技能と合計値のカラムです。これを以下のような感じでpythonから取得します。
def get_gs(): scopes = ['https://www.googleapis.com/auth/spreadsheets'] json_file = './hoge.json'#OAuth用クライアントIDの作成でダウンロードしたjsonファイル credentials = ServiceAccountCredentials.from_json_keyfile_name(json_file, scopes=scopes) http_auth = credentials.authorize(Http()) # スプレッドシート用クライアントの準備 doc_id = 'doc_id'#これはスプレッドシートのURLのうちhttps://docs.google.com/spreadsheets/d/以下の部分です gs = gspread.authorize(credentials) gfile = gs.open_by_key(doc_id) #読み書きするgoogle spreadsheet return gfile
ユーザーごとにでシートを切り替えられるようにします。シート名はなんでも良いのですが、私はdiscordから自動でシートを切り替えられるようにdiscordのIDをシート名にしています。
def get_charactor(sheet_name): gfile = get_gs() worksheet = gfile.worksheet(sheet_name) charactor = {} #技能名カラム cell_keys = worksheet.col_values(1) #合計値カラム cell_values = worksheet.col_values(2) for k,v in zip(cell_keys, cell_values): charactor[k] = v return charactor
ここまででスプレッドシートの設定は終了です。
Discord側のBOTを作る
次にDiscordの設定をしていきます。
Bot作成
DiscordのBOTの作り方はこちらを参考にさせていただきました
- DiscordでBOT作成メモ w/ discord.py
- PythonでDiscord botを作る 【discord.py解説】
- DiscordのBotをPythonで作ってみた
- Pythonで実用Discord bot(discord.py解説)
メッセージを取る
discordのメッセージ欄に特定の入力があったらダイスを振るようにします。入力形式は以下の仕様にします。ダイスを振るトリガーはdice
にします。
dice 1d100 技能名
これで受け取ってメッセージを返す待機botの処理を実装します。
client = discord.Client() client_id = conf['client_id'] @client.event async def on_ready(): print('Logged in') print('-----') @client.event async def on_message(message): # 開始ワード if message.content.startswith('dice'): # 送り主がBotではないか if client.user != message.author: info = parse('dice {}d{} {}', message.content) if info: if info[1].isdecimal() and info[0].isdecimal(): dice_num = int(info[0]) dice_size = int(info[1]) key = info[2] # メッセージを書きます m = message.author.name + ' ' if key == '一時的狂気': m = temp_madness() elif key == '不定の狂気': m = ind_madness() elif key == 'dice': m = simple_dice(dice_size, dice_num) else: chara = get_charactor(str(message.author)) msg, result = judge(chara, key, dice_size, dice_num) m += msg if result: d = damage(chara, key) else: d = None if d is not None: m += '\nダメージ: ' + str(np.sum(d)) + ' = ' + str(d) # メッセージが送られてきたチャンネルへメッセージを送ります await client.send_message(message.channel, m) client.run(client_id)
ダイス
ダイス振る部分を実装します。単純に入力したダイスを振るものと成否判定をするものの2種類用意します。
まず、1〜dice_size
までの一様整数乱数を1つ生成します。
def dice(dice_size): num = np.random.randint(1, int(dice_size)) return num
単純にダイスを振る場合は次のようにしています。上記のダイスをdice_num
回分振ります。2d6
なら1d6
のダイスを2回振っています。あとでメッセージ表示の際に個別のダイス結果も見たいのでダイス結果はnumpy.array
にしています。合計値はnp.sum
で計算します。msg
はdiscordに返すメッセージです。
def simple_dice(dice_size, dice_num): dice_val = np.array([], dtype=np.int64) for i in range(dice_num): dice_val = np.append(dice_val, dice(dice_size)) msg = 'dice: ' + str(np.sum(dice_val)) + ' = ' + str(dice_val) return msg
discord上ではこんな感じになります。
成否判定をするときもsimple_dice
をベースに実装します。
成否判定
スプレッドシートから引っ張ってきた情報を参照して成否判定をさせます。 取得したスプレッドシートの情報は辞書型にして持たせておきます。
charactor = { 'HP': 9, 'MP': 7, 'SAN': 60, 'こぶし': 50, '図書館': 50 }
と言っても、インスタンス生成とかしているわけではないので、判定のたびにスプレッドシートからデータを参照しているので、たぶん非効率的です。
処理の流れは
- 入力メッセージからダイスのサイズと数を取得する
- ダイスを振ってダイス値を取得する
- 技能値をダイス値を比較する
といった感じです。ちなみに卓ルールで5以下でクリティカル、96以上でファンブルにしています。return
のbool値はダメージ判定時に使用します。
def judge(charactor, key, dice_size, dice_num): dice_val = np.array([], dtype=np.int64) for i in range(dice_num): dice_val = np.append(dice_val, dice(dice_size)) if int(charactor[key]) >= np.sum(dice_val): msg = key + ' ' + str(charactor[key]) + ' >= ' + str(np.sum(dice_val)) + ' = ' + str(dice_val) if np.sum(dice_val) <= 5: msg += ' 【クリティカル】' msg += ' Success' return msg, True else: msg = key + ' ' + str(charactor[key]) + ' < ' + str(np.sum(dice_val)) + ' = ' + str(dice_val) if np.sum(dice_val) >= 96: msg += ' 【ファンブル】' msg += ' Fail' return msg, False
discord上ではこのように見えます。
ダメージ判定
特定の技能名
で技能判定が成功した際に自動でダメージロールを振るようにしました。トリガーとなる技能名
はあらかじめダメージがわかっているこぶし,頭突き,キックだけに絞って実装します。マーシャルアーツは考慮していません。
ダメージボーナスがあればマイナスも含めて追加しています。
def damage(charactor, key): d = np.array([], dtype=np.int64) if key == 'こぶし': d = np.append(d, dice(3)) elif key == '頭突き': d = np.append(d, dice(4)) elif key == 'キック': d = np.append(d, dice(6)) else: return None if 'd' in charactor['db']: result = parse('{}d{}', charactor['db']) dice_size = int(result[1]) dice_num = int(result[0]) for i in range(np.abs(dice_num)): if dice_num < 0: d = np.append(d, -dice(dice_size)) else: d = np.append(d, dice(dice_size)) return d
- 成功
- 失敗
- ダメージボーナスあり
- ダメージボーナスあり(マイナス)
狂気表
discord上で次のように入力した場合に狂気票を振るようにします。
dice 1d10 狂気の種類
狂気の種類
は一時的発狂
と不定の狂気
の2種類が振れます。1d10
はフォーマットの統一の為につけていますが、実質使っていません。
一瞬、狂気表もスプレッドシートに書こうかと思いましたが、処理速度をあげる為にハードコーディングしてます(正直、面倒臭かったので)。
def temp_madness(): roll = {} roll[1] = '鸚鵡返し(誰かの動作・発言を真似することしか出来なくなる)' #(中略) roll[20] = '過信(自分を全能と信じて、どんなことでもしてしまう)' msg = roll[dice(20)] msg += '\n一時的狂気(' + str(dice(10)+4) + 'ラウンドまたは' + str(dice(6)*10+30) + '分)' return msg def ind_madness(): roll = {} roll[1] = '失語症(言葉を使う技能が使えなくなる)' #(中略) roll[10] = '殺人癖(誰彼構わず殺そうとする) ' msg = roll[dice(10)] msg += '\n不定の狂気(' + str(dice(10)*10) + '時間)' return msg
- 一時的狂気
- 不定の狂気
Herokuにデプロイする
そのままではローカルでbotを起動している時しか使えないので不便です。そこでサーバーを立てて常時使えるようにします。今回はHerokuを使います。
Procfile
まずは、Heroku上で動かす実行スクリプトを作成します。今回はtrpg_bot.py
を実行するだけなので
woker: python trpg_bot.py
とします。
requirement.txt
requirement.txt
にpythonファイル内で使っているライブラリを記入します。今回は以下のライブラリを使用しています。
oauth2client httplib2 gspread discord parse numpy
デプロイ
基本的にはHerokuのアカウントを作ってCreate New AppしてHeroku Gitにしたがって進めれば良いです。
# Herokuにログインする $ heroku login # リポジトリをクローンする $ heroku git:clone -a trpg_dice_bot $ cd trpg_dice_bot
cloneしたリポジトリに作成したアプリケーションなどを格納します。
- trpg_bot.py #メインアプリケーション
- config.json #設定ファイル
- requirement.txt # さっき作ったやつ
- Procfile #さっき作ったやつ
- oauth.json #Googleスプレッドシートを作った時にダウンロードしたoauthのjsonファイルです
$ git add . $ git commit -am "make it better" $ git push heroku master
無事デプロイに成功すると
remote: Verifying deploy... done.
のようなメッセージが表示されます。 デプロイした時点ではまだdiscord上ではbotはオフラインのはずです。
有効にするには、Resourcesからアプリを起動すればdiscord上でbotがオンラインになるはずです。 ちなみにHerokuの無料枠は550時間です。
無事、オンラインになりました。
このあたりを参考にさせていただいています。
これで快適なTRPGライフが送れるはず。 少しでも誰かの参考になれば幸いです。