-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathtrpg_bot.py
548 lines (480 loc) · 19.3 KB
/
trpg_bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
81
82
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
166
167
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
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
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
import gspread
import discord
import numpy as np
from parse import parse
import json
import unicodedata
import argparse
def get_east_asian_width_count(text):
count = 0
for c in text:
if unicodedata.east_asian_width(c) in 'FWA':
count += 2
else:
count += 1
return count
def load_config(filepath):
with open(filepath) as f:
conf = json.load(f)
return conf
def get_gs():
scopes = ['https://www.googleapis.com/auth/spreadsheets']
json_file = conf['json_file']#OAuth用クライアントIDの作成でダウンロードしたjsonファイル
credentials = ServiceAccountCredentials.from_json_keyfile_name(json_file, scopes=scopes)
http_auth = credentials.authorize(Http())
# スプレッドシート用クライアントの準備
doc_id = conf['doc_id']##これはスプレッドシートのURLのうちhttps://docs.google.com/spreadsheets/d/以下の部分です
gs = gspread.authorize(credentials)
gfile = gs.open_by_key(doc_id)#読み書きするgoogle spreadsheet
return gfile
def get_charactor(name):
gfile = get_gs()
#worksheetの名前はdiscordのユーザー名にしておく
worksheet = gfile.worksheet(name)
charactor = {}
cell_keys = worksheet.col_values(1)
cell_values = worksheet.col_values(2)
cell_dice = worksheet.col_values(7)
for k, v, d in zip(cell_keys, cell_values, cell_dice):
if d == 'dice':
dice_num = 0
dice_size = 0
else:
dice_info = parse('{}d{}', d)
dice_num = dice_info[0]
dice_size = dice_info[1]
charactor[k] = {
'value': v,
'dice_num': dice_num,
'dice_size': dice_size
}
return charactor
def dice(dice_num, dice_size):
return np.random.randint(1, int(dice_size), int(dice_num))
def judge(dice_array, ability):
if int(ability) >= np.sum(dice_array):
return True
else:
return False
def damage(charactor, key):
d = np.array([], dtype=np.int64)
if key == 'こぶし':
d = np.append(d, dice(1, 3))
elif key == '頭突き':
d = np.append(d, dice(1, 4))
elif key == 'キック':
d = np.append(d, dice(1, 6))
else:
return None
if charactor and 'd' in charactor['db']['value']:
result = parse('{}d{}', charactor['db']['value'])
dice_size = int(result[1])
dice_num = int(result[0])
if dice_num < 0:
d = np.hstack([d, -dice(1, dice_size)])
else:
d = np.hstack([d, dice(1, dice_size)])
return d
def temp_madness():
roll = {}
roll[1] = '鸚鵡返し(誰かの動作・発言を真似することしか出来なくなる)'
roll[2] = '健忘症(1d6時間以内のことを忘れる)'
roll[3] = '多弁症(何があってもひたすら喋り続ける)'
roll[4] = '偏食症(奇妙なものを食べたくなる)'
roll[5] = '頭痛・嘔吐などの体調不良(技能値に-5)'
roll[6] = '暴力癖(誰彼構わず暴力を振るう)'
roll[7] = '幻聴或いは一時的難聴(聞き耳半減。この症状の探索者に精神分析や説得などを試みる場合は技能値に-10)'
roll[8] ='逃亡癖(その場から逃げようとする)'
roll[9] = '吃音や失声などの発語障害(交渉技能の技能値が半減する)'
roll[10] = '不信(単独行動をとりたがる。交渉技能不可。)'
roll[11] = '恐怖による行動不能'
roll[12] = '自傷癖(自傷行動を行う。ラウンドごと1d2のダメージ判定を行う)'
roll[13] = '感情の噴出(泣き続ける、笑い続けるなど。自発行動が出来なくなる)'
roll[14] = '気絶(精神分析・またはCON*5のロールに成功で目覚める)'
roll[15] = '幻覚あるいは妄想(目を使う技能は技能値に-30)'
roll[16] = '偏執症(特定のものや行動に強く執着する)'
roll[17] = 'フェティシズム(特定のものに性的魅惑を感じる)'
roll[18] = '退行(乳幼児のような行動をとってしまう)'
roll[19] = '自己愛(自分を守るために何でもしようとする)'
roll[20] = '過信(自分を全能と信じて、どんなことでもしてしまう)'
msg = roll[dice(1, 20)[0]]
msg += '\n一時的狂気(' + str(dice(1, 10)+4) + 'ラウンドまたは' + str(dice(1, 6)*10+30) + '分)'
return msg
def ind_madness():
roll = {}
roll[1] = '失語症(言葉を使う技能が使えなくなる)'
roll[2] = '心因性難聴(聞き耳不可。精神分析を受ける際に技能値に-30)'
roll[3] = '奇妙な性的嗜好(性的倒錯。特定のものに性的興奮を覚える)'
roll[4] = '偏執症(特定のものや行動に異常に執着する)'
roll[5] = '脱力・虚脱(自力での行動が出来なくなる)'
roll[6] = '恐怖症(特定のものに強い恐怖を覚える。そのものが側に存在する場合、技能値に-20)'
roll[7] = '自殺癖(ラウンドごとに1d4+1のダメージ判定を行う)'
roll[8] = '不信(単独行動をとりたがる。交渉技能不可。)'
roll[9] = '幻覚(目を使う技能は技能値に-30)'
roll[10] = '殺人癖(誰彼構わず殺そうとする) '
msg = roll[dice(1, 10)[0]]
msg += '\n不定の狂気(' + str(dice(1, 10)*10) + '時間)'
return msg
def against(input_msg):
active = int(input_msg[0])
passive = int(input_msg[1])
achivement = 50 + ( (active - passive) * 5)
dice_array = dice(1, 100)
result = judge(dice_array, achivement)
if result:
inequality = '>='
result_msg = 'Success'
if np.sum(dice_array) <= 5:
result_msg += '【クリティカル】'
else:
inequality = '<'
result_msg = 'Fail'
if np.sum(dice_array) >= 96:
result_msg += '【ファンブル】'
return '【対抗ロール】{active} VS {passive}'\
' : {achivement} {inequality} {dice_result} --> {result_msg}'.format(
active = active,
passive = passive,
achivement = achivement,
inequality = inequality,
dice_result = np.sum(dice_array),
result_msg = result_msg
)
def charactor_make():
status = {}
status['STR'] = np.sum(dice(3, 6))
status['CON'] = np.sum(dice(3, 6))
status['POW'] = np.sum(dice(3, 6))
status['DEX'] = np.sum(dice(3, 6))
status['APP'] = np.sum(dice(3, 6))
status['SIZ'] = np.sum(dice(2, 6)) + 6
status['INT'] = np.sum(dice(2, 6)) + 6
status['EDU'] = np.sum(dice(3, 6)) + 3
print('---')
for k,v in status.items():
if 8 <= v and v <= 12:
correction = np.random.rand()
if correction > 0.7:
new_v = v + np.sum(dice(3, 4))
print(v, new_v)
status[k] = new_v if new_v <= 21 else 21
elif correction < 0.1:
new_v = v - np.sum(dice(2, 3))
print(v, new_v)
status[k] = new_v if new_v >= 3 else 3
if status['SIZ'] < 8:
status['SIZ'] = 8
if status['INT'] < 8:
status['INT'] = 8
if status['EDU'] < 6:
status['EDU'] = 6
status['HP'] = int((status['CON'] + status['SIZ']) / 2)
status['MP'] = status['POW']
status['SAN'] = status['POW'] * 5
status['アイディア'] = status['INT'] * 5
status['幸運'] = status['POW'] * 5
status['知識'] = status['EDU'] * 5
ATK = (status['STR'] + status['SIZ'])
if 2 <= ATK and ATK <= 12:
status['db'] = '-1d6'
elif 13 <= ATK and ATK <= 16:
status['db'] = '-1d4'
elif 17 <= ATK and ATK <= 24:
status['db'] = '0d0'
elif 25 <= ATK and ATK <= 32:
status['db'] = '1d4'
elif 33 <= ATK and ATK <= 40:
status['db'] = '1d6'
elif 41 <= ATK and ATK <= 56:
status['db'] = '2d6'
elif 57 <= ATK and ATK <= 72:
status['db'] = '3d6'
msg = ''
for k, v in zip(status.keys(), status.values()):
msg += '{k} {v} \n'.format(k=k, v=v)
return msg
def charactor_introduce(message):
charactor = get_charactor(str(message.author))
status = {}
status['キャラクター名'] = charactor['NAME']['value']
status['STR'] = charactor['STR']['value']
status['CON'] = charactor['CON']['value']
status['POW'] = charactor['POW']['value']
status['DEX'] = charactor['DEX']['value']
status['APP'] = charactor['APP']['value']
status['SIZ'] = charactor['SIZ']['value']
status['INT'] = charactor['INT']['value']
status['EDU'] = charactor['EDU']['value']
status['HP'] = charactor['HP']['value']
status['MP'] = charactor['MP']['value']
status['SAN'] = charactor['SAN']['value']
status['アイデア'] = charactor['アイデア']['value']
status['幸運'] = charactor['幸運']['value']
status['知識'] = charactor['知識']['value']
status['db'] = charactor['db']['value']
msg = ''
for k, v in zip(status.keys(), status.values()):
msg += '{k}: {v} \n'.format(k=k, v=v)
return msg
def simple_dice(input_msg):
def single_dice(msg, opt):
dice_info = msg.split('d')
dice_num = int(dice_info[0])
dice_size = int(dice_info[1])
return np.array([int(opt+str(d)) for d in dice(dice_num, dice_size)])
secret = None
top_secret = None
ability = None
msg = input_msg[0]
# ||を除去
msg = msg.replace('||', '')
# 返り値を全て隠す
if 'top_secret' in msg:
msg = parse('{} top_secret', msg)[0]
top_secret = True
# ダイス結果だけ表示する
elif 'secret' in msg:
msg = parse('{} secret', msg)[0]
secret = True
# ()を除去する
if '(' in msg:
tmp = parse('{}({})', msg)
ability = tmp[1]
msg = tmp[0]
dice_size = 100
dice_array = np.array([], dtype=np.int64)
operator = ('+', '-')
opts = list()
if not msg.startswith('-'):
msg = '+' + msg
while any(m in msg for m in operator):
# 文頭から数えて最初に出てくる演算子を探す
opts.append(operator[np.argmin([msg.find(opt) if msg.find(opt)>=0 else 999 for opt in operator])])
# 初回は演算子を取り出すだけ
if len(opts) == 1:
msg_tmp = msg.split(opts[0], 1)
msg = msg_tmp[1]
continue
else:
msg_tmp = msg.split(opts[1], 1)
opt = opts.pop(0)
# diceを振る
if 'd' in msg_tmp[0]:
dice_result = single_dice(msg_tmp[0], opt)
dice_array = np.append(dice_array, dice_result)
# 固定値
else:
dice_array = np.append(dice_array, int(opt+msg_tmp[0]))
msg = msg_tmp[1] # 残りのテキストを取り出す
else: # 最後のテキストに対する処理
opt = opts.pop(0)
if 'd' in msg:
dice_result = single_dice(msg, opt)
dice_array = np.append(dice_array, dice_result)
else:
dice_array = np.append(dice_array, int(opt+msg))
# 技能判定
if ability:
result = judge(dice_array, ability)
result_msg = '--> '
(inequality, _, msg) = result_message(dice_size, dice_array, result)
result_msg += msg
else:
(inequality, _, result_msg) = ('', '', '')
# シークレットメッセージにする
if secret:
secret = '||'
else:
secret = ''
if top_secret:
top_secret = '||'
else:
top_secret = ''
return_msg = '【ダイス】({secret}{ability}{secret}):{inequality} {dice_result}={dice_array} {result_msg}'.format(
secret = secret,
ability = ability,
inequality = inequality,
dice_result = np.sum(dice_array),
dice_array = dice_array,
result_msg = result_msg)
# top_secretを使った時の文字幅調整
dummy_blank = ''
if '【' in result_msg:
count_len = 70
else:
if 'Fail' in return_msg:
count_len = 92
else:
count_len = 84
while get_east_asian_width_count(return_msg+dummy_blank) < count_len:
dummy_blank += ' '
print(return_msg+dummy_blank)
print(get_east_asian_width_count(return_msg+dummy_blank))
return '【ダイス】{top_secret}({secret}{ability}{secret}):{inequality} {dice_result}={dice_array} {result_msg}{dummy_blank}{top_secret}'.format(
top_secret = top_secret,
secret = secret,
ability = ability,
inequality = inequality,
dice_result = np.sum(dice_array),
dice_array = dice_array,
result_msg = result_msg,
dummy_blank = dummy_blank)
def result_message(dice_size, dice_array, result=None, charactor=None, ability_name=None):
if result:
inequality = '>='
result_msg = 'Success'
if dice_size == 100 and np.sum(dice_array) <= 5:
result_msg += '【クリティカル】'
d = damage(charactor=charactor, key=ability_name)
if d is not None:
result_msg += '\nダメージ:{dice_result}={dice_array}'.format(
dice_result = np.sum(d),
dice_array = d)
else:
inequality = '<'
result_msg = 'Fail'
if dice_size == 100 and np.sum(dice_array) >= 96:
result_msg += '【ファンブル】'
d = None
return (inequality, d, result_msg)
def dice_message(input_msg, message):
if '不定の狂気' in input_msg:
return ind_madness()
elif '一時的狂気' in input_msg:
return temp_madness()
elif 'cm' in input_msg:
return charactor_make()
elif 'VS' in message.content:
return against(input_msg)
elif 'dice' in message.content: #技能値判定無しのダイス
return simple_dice(input_msg)
elif 'ci' in input_msg:
return charactor_introduce(message)
elif 'help' in input_msg:
return help()
else:
charactor = get_charactor(str(message.author))
if len(input_msg) == 3:
dice_num = input_msg[0]
dice_size = input_msg[1]
ability_name, ability, ability_detail = calc_ability(input_msg[2], charactor)
elif len(input_msg) == 1:
ability_name, ability, ability_detail = calc_ability(input_msg[0], charactor)
dice_num = charactor[ability_name]['dice_num']
dice_size = charactor[ability_name]['dice_size']
dice_num = int(dice_num)
dice_size = int(dice_size)
dice_array = dice(dice_num, dice_size)
result = judge(dice_array, ability)
(inequality, d, result_msg) = result_message(dice_size, dice_array, result, charactor, ability_name)
return '【{ability_name}】{operator} {correction} : '\
'{base_ability}{operator}{correction} = {ability}'\
'{inequality} {dice_result} = {dice_array} {result_msg}'.format(
ability_name = ability_name,
ability = ability,
base_ability = ability_detail[0],
operator = ability_detail[1],
correction = ability_detail[2],
inequality = inequality,
dice_result = np.sum(dice_array),
dice_array = dice_array,
result_msg = result_msg)
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)
def help():
msg = '【使い方】\n'\
'**ダイスロール**: `/dice [ダイスの数]d[出目の最大値]`\n'\
'**技能判定**: `/[技能名]`\n'\
'**技能判定(ダイスサイズ指定or達成値指定)**: `dice [ダイスの数]d[出目の最大値] [技能名 or 達成値]`\n'\
'**対抗ロール**: `VS [対抗する側]/[対抗される側]`\n'\
'**一時的狂気**: `/一時的狂気`\n'\
'**不定の狂気**: `/不定の狂気`\n'\
'**キャラメイク**: `/cm`\n'\
'**キャラ紹介**: `/ci`\n'
return msg
def bot_startswitch(message):
# 開始ワード
if message.content.startswith('/dice'):
return parse('/dice {}', message.content)
#return parse('/dice {}d{}', message.content)
elif message.content.startswith('dice'):
return parse('dice {}d{} {}', message.content)
elif message.content.startswith('/'):
return parse('/{}', message.content)
elif message.content.startswith('VS'):
return parse('VS {}/{}', message.content)
else:
return None
def playmp3():
global voice
global music_list
filepath = music_list.pop()
if filepath:
voice.play(discord.FFmpegPCMAudio(filepath), after=lambda e: after_play(e))
def after_play(e):
print("error:", e)
playmp3()
parser = argparse.ArgumentParser(description='')
parser.add_argument('-m', '--mode', help='run mode option', default='release', choices=['debug', 'release'])
parser.add_argument('-s', '--sound', help='sound option', default=False, type=bool)
args = parser.parse_args()
filepath = {
'debug': 'config_test.json',
'release': 'config.json'
}
conf = load_config(filepath[args.mode])
client = discord.Client()
client_id = conf['client_id']
voice = None
music_list = []
@client.event
async def on_ready():
print('Logged in')
print('-----')
@client.event
async def on_message(message):
global voice
global music_list
# 送り主がBotじゃないか
if client.user != message.author:
if voice is None and args.sound:
try:
channel = message.author.voice.channel
voice = await channel.connect()
except AttributeError as e:
print(e)
await message.channel.send('[ERROR]ボイスモードがオンになっています。適当なボイスチャンネルにログインしてください')
if message.content == 'bye':
await message.channel.send('[INFO]了解です。ダイスボットはログアウトします')
await client.logout()
# 開始ワード
input_msg = bot_startswitch(message).fixed
if input_msg is not None:
m = 'PL:' + message.author.name + '\n'
m += dice_message(input_msg, message)
# メッセージが送られてきたチャンネルへメッセージを送ります
music_list.append('dice.mp3')
if args.sound:
if not voice.is_playing():
playmp3()
await message.channel.send(m) # discord.py ver1.0
#await client.send_message(message.channel, m) # discord.py ver0.16
client.run(client_id)