258 lines
10 KiB
Python
258 lines
10 KiB
Python
import re
|
|
import secrets
|
|
|
|
import numpy as np
|
|
|
|
from config import Config
|
|
from core.utils.text import remove_prefix
|
|
|
|
MAX_DICE_COUNT = Config('dice_limit', 100) # 一次摇动最多的骰子数量
|
|
MAX_ROLL_TIMES = Config('dice_roll_limit', 10) # 一次命令最多的摇动次数
|
|
MAX_MOD_NUMBER = Config('dice_mod_max', 10000) # 骰子最大加权值
|
|
MIN_MOD_NUMBER = Config('dice_mod_min', -10000) # 骰子最小加权值
|
|
MAX_OUTPUT_CNT = Config('dice_output_count', 50) # 输出的最多数据量
|
|
MAX_OUTPUT_LEN = Config('dice_output_len', 200) # 输出的最大长度
|
|
MAX_DETAIL_CNT = Config('dice_detail_count', 5) # n次投掷的骰子的总量超过该值时将不再显示详细信息
|
|
MAX_ITEM_COUNT = Config('dice_count_limit', 10) # 骰子多项式最多的项数
|
|
|
|
|
|
class DiceSyntaxError(Exception):
|
|
"""骰子语法错误"""
|
|
|
|
def __init__(self, session, message):
|
|
self.message = session.locale.t("dice.message.error.syntax") + message
|
|
|
|
|
|
class DiceValueError(Exception):
|
|
"""骰子参数值错误"""
|
|
|
|
def __init__(self, session, message, value=None):
|
|
if value:
|
|
self.message = session.locale.t("dice.message.error.value.invalid", value=value) + message
|
|
else:
|
|
self.message = session.locale.t("dice.message.error.value") + message
|
|
|
|
|
|
class DiceItemBase(object):
|
|
"""骰子项的基类"""
|
|
|
|
def __init__(self, dice_code: str, positive: bool):
|
|
self.positive = positive
|
|
self.code = dice_code
|
|
self.result = None
|
|
self.detail = ''
|
|
|
|
def GetResult(self, abs=True):
|
|
if abs:
|
|
return self.result
|
|
else:
|
|
return self.result if self.positive else -self.result
|
|
|
|
def GetDetail(self):
|
|
return self.detail
|
|
|
|
def Roll(self, session):
|
|
pass
|
|
|
|
|
|
class DiceMod(DiceItemBase):
|
|
"""调节值项"""
|
|
|
|
def __init__(self, session, dice_code: str, positive: bool):
|
|
super().__init__(dice_code, positive)
|
|
if not dice_code.isdigit():
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.M.invalid"),
|
|
'+' if self.positive else '-' + dice_code)
|
|
else:
|
|
self.result = int(dice_code)
|
|
if self.result > MAX_MOD_NUMBER or self.result < MIN_MOD_NUMBER:
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.M.out_of_range", min=MIN_MOD_NUMBER,
|
|
max=MAX_MOD_NUMBER),
|
|
self.result)
|
|
|
|
def GetDetail(self):
|
|
return self.result
|
|
|
|
|
|
class Dice(DiceItemBase):
|
|
"""骰子项"""
|
|
|
|
def __init__(self, session, dice_code: str, positive: bool):
|
|
dice_code = dice_code.replace(' ', '')
|
|
super().__init__(dice_code, positive)
|
|
args = self.GetArgs(session)
|
|
self.count = args[0]
|
|
self.type = args[1]
|
|
self.adv = args[2]
|
|
if self.count <= 0 or self.count > MAX_DICE_COUNT:
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.n.out_of_range", max=MAX_DICE_COUNT),
|
|
self.count)
|
|
if self.type <= 0:
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.n.less2"),
|
|
self.count)
|
|
if self.type == 1:
|
|
raise DiceValueError(session, session.locale.t("dice.message.error.value.n.d1"))
|
|
if abs(self.adv) > self.count:
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.k.out_of_range"),
|
|
self.adv)
|
|
|
|
def GetArgs(self, session):
|
|
dice_code = self.code.upper() # 便于识别
|
|
dice_count = '1' # 骰子数量
|
|
advantage = '0' # 保留的骰子量
|
|
if re.search(r'[^0-9DKL]', dice_code):
|
|
raise DiceSyntaxError(session, session.locale.t("dice.message.error.syntax.invalid"))
|
|
if 'D' not in dice_code:
|
|
raise DiceSyntaxError(session, session.locale.t("dice.message.error.syntax.missing_d"))
|
|
temp = dice_code.split('D')
|
|
if len(temp[0]):
|
|
dice_count = temp[0]
|
|
else:
|
|
dice_count = '1'
|
|
midstrs = temp[1].partition('K')
|
|
dice_type = midstrs[0]
|
|
if 'K' in midstrs[1]:
|
|
advantage = midstrs[2].replace('L', '-')
|
|
if not len(advantage.removeprefix('-')):
|
|
advantage += '1' # K/KL后没有值默认为1
|
|
# 语法合法检定
|
|
if not dice_count.isdigit():
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.m.invalid"),
|
|
dice_count)
|
|
if not dice_type.isdigit():
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.n.invalid"),
|
|
dice_type)
|
|
if not (advantage.isdigit() or (advantage[0] == '-' and advantage[1:].isdigit())):
|
|
raise DiceValueError(session,
|
|
session.locale.t("dice.message.error.value.k.invalid"),
|
|
advantage)
|
|
return (int(dice_count), int(dice_type), int(advantage))
|
|
|
|
def Roll(self, session):
|
|
output = ''
|
|
result = 0
|
|
dice_results = []
|
|
adv = self.adv
|
|
output += self.code + ' = '
|
|
# 生成随机序列
|
|
for i in range(self.count):
|
|
dice_results.append(secrets.randbelow(int(self.type)) + 1)
|
|
if adv != 0:
|
|
new_results = []
|
|
indexes = np.array(dice_results).argsort()
|
|
indexes = indexes[-adv:] if adv > 0 else indexes[:-adv]
|
|
output += '( '
|
|
output_buffer = ''
|
|
for i in range(self.count):
|
|
output_buffer += str(dice_results[i])
|
|
if i in indexes:
|
|
new_results.append(dice_results[i])
|
|
output_buffer += '*'
|
|
if i < self.count - 1:
|
|
output_buffer += ','
|
|
if self.count >= MAX_OUTPUT_CNT:
|
|
output_buffer = session.locale.t("dice.message.output.too_long", length=self.count)
|
|
output += output_buffer + ' ) = '
|
|
dice_results = new_results
|
|
# 公用加法
|
|
length = len(dice_results)
|
|
if (length > 1):
|
|
output += '[ '
|
|
if length > MAX_OUTPUT_CNT: # 显示数据含100
|
|
output += session.locale.t("dice.message.output.too_long", length=length)
|
|
for i in range(length):
|
|
result += dice_results[i]
|
|
if length <= MAX_OUTPUT_CNT: # 显示数据含100
|
|
output += str(dice_results[i])
|
|
if i < length - 1:
|
|
output += '+'
|
|
output += ' ] = '
|
|
else:
|
|
result = dice_results[0]
|
|
if len(output) > MAX_OUTPUT_LEN:
|
|
output = session.locale.t("dice.message.too_long")
|
|
self.detail = output + f"{result} "
|
|
self.result = result
|
|
|
|
|
|
async def GenerateMessage(msg, dices: str, times: int, dc: int):
|
|
if not all([MAX_DICE_COUNT > 0, MAX_ROLL_TIMES > 0, MAX_MOD_NUMBER >= MIN_MOD_NUMBER, MAX_OUTPUT_CNT > 0,
|
|
MAX_OUTPUT_LEN > 0, MAX_DETAIL_CNT > 0, MAX_ITEM_COUNT > 0]):
|
|
raise OverflowError(msg.locale.t("error.config.invalid"))
|
|
if re.search(r'[^0-9+\-DKL]', dices.upper()):
|
|
return DiceSyntaxError(msg, msg.locale.t('dice.message.error.syntax.invalid')).message
|
|
if times > MAX_ROLL_TIMES or times < 1:
|
|
return DiceValueError(msg, msg.locale.t('dice.message.error.value.N.out_of_range', max=MAX_ROLL_TIMES),
|
|
times).message
|
|
dice_code_cist = re.compile(r'[+-]?[^+-]+').findall(dices)
|
|
dice_list = []
|
|
have_err = False
|
|
output = ""
|
|
dice_count = 0
|
|
i = 0
|
|
if len(dice_code_cist) > MAX_ITEM_COUNT:
|
|
return DiceValueError(msg, msg.locale.t('dice.message.error.value.too_long'), len(dice_code_cist)).message
|
|
# 初始化骰子序列
|
|
for item in dice_code_cist:
|
|
i += 1
|
|
is_add = True
|
|
if item[0] == '-':
|
|
is_add = False
|
|
item = item[1:]
|
|
if item[0] == '+':
|
|
item = item[1:]
|
|
try:
|
|
if 'D' in item or 'd' in item:
|
|
d = Dice(msg, item, is_add)
|
|
dice_list.append(d)
|
|
dice_count += d.count
|
|
elif item.isdigit():
|
|
dice_list.append(DiceMod(msg, item, is_add))
|
|
except (DiceSyntaxError, DiceValueError) as ex:
|
|
output += '\n' + msg.locale.t('dice.message.error.prompt', i=i) + ex.message
|
|
have_err = True
|
|
if have_err:
|
|
return msg.locale.t('dice.message.error') + output
|
|
success_num = 0
|
|
fail_num = 0
|
|
output = msg.locale.t('dice.message.output')
|
|
# 开始投掷并输出
|
|
for i in range(times):
|
|
output_line = ''
|
|
result = 0
|
|
for dice in dice_list:
|
|
dice.Roll(msg)
|
|
output_line += '+' if dice.positive else '-'
|
|
if isinstance(dice, Dice) and times * dice_count < MAX_DETAIL_CNT:
|
|
output_line += f'( {dice.GetDetail()})'
|
|
else:
|
|
output_line += str(dice.GetResult())
|
|
result += dice.GetResult(False)
|
|
output_line = remove_prefix(output_line, '+') # 移除多项式首个+
|
|
output_line += ' = ' + str(result)
|
|
if dc != 0:
|
|
if msg.data.options.get('dice_dc_reversed'):
|
|
if result <= dc:
|
|
output_line += msg.locale.t('dice.message.dc.success')
|
|
success_num += 1
|
|
else:
|
|
output_line += msg.locale.t('dice.message.dc.failed')
|
|
fail_num += 1
|
|
else:
|
|
if result >= dc:
|
|
output_line += msg.locale.t('dice.message.dc.success')
|
|
success_num += 1
|
|
else:
|
|
output_line += msg.locale.t('dice.message.dc.failed')
|
|
fail_num += 1
|
|
output += f'\n{dices} = {output_line}'
|
|
if dc != 0 and times > 1:
|
|
output += '\n' + msg.locale.t('dice.message.dc.check', success=str(success_num), failed=str(fail_num))
|
|
return output
|