1
Fork 0
arcaea-server/core/character.py
Lost-MSth 488b8625da [Enhance][Bug fix] Fatalis values & Salt skill
Merged from commit a23e5372fb8d8dcff193a72a6d8fc778c28ef177
- Revised Salt's skill implemtation (used Lost's implementation)
- Add support for dynamic values of "Hikari (Fatalis)", which is depended by world mode total steps.
- Fix a bug that the character "Hikari (Fatalis)" cannot be used in world mode.(due to 3f5281582cc2e9141e748a99fadb385db522e664)
- Another attempt at fixing Nell's world map traversal
2025-02-07 20:03:54 +07:00

501 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from .config_manager import Config
from .constant import Constant
from .error import ArcError, InputError, ItemNotEnough, NoData
from .item import CollectionItemMixin, ItemCore
from .sql import UserKVTable
class Level:
mid_level = 20
min_level = 1
def __init__(self) -> None:
self.max_level: int = None
self.level: int = None
self.exp: float = None
@property
def level_exp(self):
return Constant.LEVEL_STEPS[self.level]
def add_exp(self, exp_addition: float):
# 添加经验计算
exp = self.exp + exp_addition
if exp >= Constant.LEVEL_STEPS[self.max_level]:
self.exp = Constant.LEVEL_STEPS[self.max_level]
self.level = self.max_level
return None
a = []
b = []
for k, v in Constant.LEVEL_STEPS.items():
a.append(k)
b.append(v)
if exp < b[0]: # 向下溢出是异常状态不该被try捕获不然数据库无法回滚
raise ValueError('EXP value error.')
i = len(a) - 1
while exp < b[i]:
i -= 1
self.exp = exp
self.level = a[i]
class Skill:
def __init__(self) -> None:
self.skill_id: str = None
self.skill_id_uncap: str = None
self.skill_unlock_level: int = None
self.skill_requires_uncap: bool = None
class CharacterValue:
def __init__(self, start: float = 0, mid: float = 0, end: float = 0) -> None:
self.set_parameter(start, mid, end)
self.addition: float = 0
@staticmethod
def _calc_char_value_20_math(level: int, value_1: float, value_20: float) -> float:
# by Lost-MSth
# 4/6859 = 0.00058317539
if level <= 10:
return 0.00058317539 * (level - 1) ** 3 * (value_20 - value_1) + value_1
return - 0.00058317539 * (20 - level) ** 3 * (value_20 - value_1) + value_20
# @staticmethod
# def _calc_char_value_20(level: int, stata, statb, lva=1, lvb=20) -> float:
# # 计算1~20级搭档数值的核心函数返回浮点数来自https://redive.estertion.win/arcaea/calc/
# n = [0, 0, 0.0005831753900000081, 0.004665403120000065, 0.015745735529959858, 0.03732322495992008, 0.07289692374980007, 0.12596588423968, 0.2000291587694801, 0.29858579967923987, 0.42513485930893946,
# 0.5748651406910605, 0.7014142003207574, 0.7999708412305152, 0.8740341157603029, 0.9271030762501818, 0.962676775040091, 0.9842542644700301, 0.9953345968799998, 0.9994168246100001, 1]
# e = n[lva] - n[lvb]
# a = stata - statb
# r = a / e
# d = stata - n[lva] * r
# return d + r * n[level]
@staticmethod
def _calc_char_value_30(level, stata, statb, lva=20, lvb=30):
# 计算21~30级搭档数值返回浮点数
return (level - lva) * (statb - stata) / (lvb - lva) + stata
def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0):
self.start: float = start
self.mid: float = mid
self.end: float = end
def get_value(self, level: Level):
if level.min_level <= level.level <= level.mid_level:
return self._calc_char_value_20_math(level.level, self.start, self.mid) + self.addition
if level.mid_level < level.level <= level.max_level:
return self._calc_char_value_30(level.level, self.mid, self.end) + self.addition
return 0
class Character(CollectionItemMixin):
database_table_name = None
collection_item_const = {
'name': 'character',
'table_name': 'char_item',
'table_primary_key': 'character_id',
'id_name': 'character_id',
'items_name': 'uncap_cores'
}
def __init__(self, c=None) -> None:
self.c = c
self.character_id: int = None
self.name: str = None
self.char_type: int = None
self.is_uncapped: bool = None
self.is_uncapped_override: bool = None
self.skill = Skill()
self.level = Level()
self.frag = CharacterValue()
self.prog = CharacterValue()
self.overdrive = CharacterValue()
self.uncap_cores: list = []
self.voice: list = None
@property
def frag_value(self) -> float:
return self.frag.get_value(self.level)
@property
def prog_value(self) -> float:
return self.prog.get_value(self.level)
@property
def overdrive_value(self) -> float:
return self.overdrive.get_value(self.level)
@property
def skill_id_displayed(self) -> str:
return None
def uncap_cores_to_dict(self):
return [x.to_dict(character_format=True) for x in self.uncap_cores]
@property
def is_uncapped_displayed(self) -> bool:
'''对外显示的uncap状态'''
return False if self.is_uncapped_override else self.is_uncapped
@property
def is_base_character(self) -> bool:
# 应该是只有对立这样
return self.character_id == 1
def to_dict(self, has_cores=False) -> dict:
# 用于API里游戏内的数据不太一样故不能super
r = {
'character_id': self.character_id,
'name': self.name,
'char_type': self.char_type,
'is_uncapped': self.is_uncapped,
'max_level': self.level.max_level,
'skill_id': self.skill.skill_id,
'skill_unlock_level': self.skill.skill_unlock_level,
'skill_requires_uncap': self.skill.skill_requires_uncap,
'skill_id_uncap': self.skill.skill_id_uncap,
'frag1': self.frag.start,
'frag20': self.frag.mid,
'frag30': self.frag.end,
'prog1': self.prog.start,
'prog20': self.prog.mid,
'prog30': self.prog.end,
'overdrive1': self.overdrive.start,
'overdrive20': self.overdrive.mid,
'overdrive30': self.overdrive.end,
}
if has_cores:
r['uncap_cores'] = self.uncap_cores_to_dict()
return r
def from_list(self, l: tuple) -> 'Character':
self.character_id = l[0]
self.name = l[1]
self.level.max_level = l[2]
self.frag.set_parameter(l[3], l[6], l[9])
self.prog.set_parameter(l[4], l[7], l[10])
self.overdrive.set_parameter(l[5], l[8], l[11])
self.skill.skill_id = l[12]
self.skill.skill_unlock_level = l[13]
self.skill.skill_requires_uncap = l[14] == 1
self.skill.skill_id_uncap = l[15]
self.char_type = l[16]
self.is_uncapped = l[17] == 1
return self
def select(self, character_id: int = None) -> 'Character':
if character_id is not None:
self.character_id = character_id
self.c.execute(
'select * from character where character_id = ?', (self.character_id,))
x = self.c.fetchone()
if not x:
raise NoData(
f'No such character: {self.character_id}', api_error_code=-130)
return self.from_list(x)
def update(self) -> None:
self.c.execute('''update character set name = ?, max_level = ?, frag1 = ?, frag20 = ?, frag30 = ?, prog1 = ?, prog20 = ?, prog30 = ?, overdrive1 = ?, overdrive20 = ?, overdrive30 = ?, skill_id = ?, skill_unlock_level = ?, skill_requires_uncap = ?, skill_id_uncap = ?, char_type = ?, is_uncapped = ? where character_id = ?''', (
self.name, self.level.max_level, self.frag.start, self.frag.mid, self.frag.end, self.prog.start, self.prog.mid, self.prog.end, self.overdrive.start, self.overdrive.mid, self.overdrive.end, self.skill.skill_id, self.skill.skill_unlock_level, self.skill.skill_requires_uncap, self.skill.skill_id_uncap, self.char_type, self.is_uncapped, self.character_id))
def select_character_core(self):
# 获取此角色所需核心
self.c.execute(
'''select item_id, amount from char_item where character_id = ? and type="core"''', (self.character_id,))
x = self.c.fetchall()
if x:
self.uncap_cores = []
for i in x:
self.uncap_cores.append(ItemCore(self.c, i[0], i[1]))
class UserCharacter(Character):
'''
用户角色类
property: `user` - `User`类或子类的实例
'''
database_table_name = 'user_char_full' if Config.CHARACTER_FULL_UNLOCK else 'user_char'
def __init__(self, c, character_id=None, user=None) -> None:
super().__init__()
self.c = c
self.character_id = character_id
self.user = user
self.skill_flag: bool = None
self.fatalis_is_limited: bool = False
@property
def skill_id_displayed(self) -> str:
'''对外显示的技能id'''
if self.is_uncapped_displayed and self.skill.skill_id_uncap:
return self.skill.skill_id_uncap
if self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level:
return self.skill.skill_id
return None
@property
def skill_state(self) -> str:
if self.skill_id_displayed == 'skill_maya':
return 'add_random' if self.skill_flag else 'remove_random'
return None
def select_character_uncap_condition(self, user=None):
# parameter: user - User类或子类的实例
# 获取此角色的觉醒信息
if user:
self.user = user
self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''',
{'a': self.user.user_id, 'b': self.character_id})
x = self.c.fetchone()
if not x:
self.is_uncapped = False
self.is_uncapped_override = False
# raise NoData('The character of the user does not exist.')
else:
self.is_uncapped = x[0] == 1
self.is_uncapped_override = x[1] == 1
def select_character_info(self, user=None):
# parameter: user - User类或子类的实例
# 获取所给用户此角色信息
if user:
self.user = user
self.c.execute(f'''select * from {self.database_table_name} a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''',
(self.user.user_id, self.character_id))
y = self.c.fetchone()
if y is None:
raise NoData('The character of the user does not exist.')
self.name = y[8]
self.char_type = y[23]
self.is_uncapped = y[4] == 1
self.is_uncapped_override = y[5] == 1
self.level.level = y[2]
self.level.exp = y[3]
self.level.max_level = y[9]
self.frag.set_parameter(y[10], y[13], y[16])
self.prog.set_parameter(y[11], y[14], y[17])
self.overdrive.set_parameter(y[12], y[15], y[18])
self.skill.skill_id = y[19]
self.skill.skill_id_uncap = y[22]
self.skill.skill_unlock_level = y[20]
self.skill.skill_requires_uncap = y[21] == 1
self.skill_flag = y[6] == 1
if self.character_id in (21, 46):
self.voice = [0, 1, 2, 3, 100, 1000, 1001]
if self.character_id == 55:
# fatalis 提升数值
# prog & overdrive += 世界模式中完成的所有非无限地图的台阶数之和 / 30
if Config.CHARACTER_FULL_UNLOCK:
addition = Constant.FATALIS_MAX_VALUE
self.fatalis_is_limited = True
else:
kvd = UserKVTable(self.c, self.user.user_id, 'world')
steps = kvd['total_step_count'] or 0
addition = steps / 30
if addition >= Constant.FATALIS_MAX_VALUE:
addition = Constant.FATALIS_MAX_VALUE
self.fatalis_is_limited = True
self.prog.addition = addition
self.overdrive.addition = addition
self.select_character_core()
if self.character_id == 72:
self.update_insight_state()
def to_dict(self) -> dict:
if self.char_type is None:
self.select_character_info(self.user)
r = {'base_character': self.is_base_character,
"is_uncapped_override": self.is_uncapped_override,
"is_uncapped": self.is_uncapped,
"uncap_cores": self.uncap_cores_to_dict(),
"char_type": self.char_type,
"skill_id_uncap": self.skill.skill_id_uncap,
"skill_requires_uncap": self.skill.skill_requires_uncap,
"skill_unlock_level": self.skill.skill_unlock_level,
"skill_id": self.skill.skill_id,
"overdrive": self.overdrive.get_value(self.level),
"prog": self.prog.get_value(self.level),
"frag": self.frag.get_value(self.level),
"level_exp": self.level.level_exp,
"exp": self.level.exp,
"level": self.level.level,
"name": self.name,
"character_id": self.character_id
}
if self.voice:
r['voice'] = self.voice
if self.character_id == 55:
r['fatalis_is_limited'] = self.fatalis_is_limited
if self.character_id in [1, 6, 7, 17, 18, 24, 32, 35, 52]:
r['base_character_id'] = 1
x = self.skill_state
if x:
r['skill_state'] = x
return r
def change_uncap_override(self, user=None):
# parameter: user - User类或子类的实例
# 切换觉醒状态
if user:
self.user = user
self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''',
{'a': self.user.user_id, 'b': self.character_id})
x = self.c.fetchone()
if x is None or x[0] == 0:
raise ArcError('Unknown Error')
self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id})
self.c.execute(f'''update {self.database_table_name} set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id, 'c': self.character_id})
self.is_uncapped_override = x[1] == 0
def character_uncap(self, user=None):
# parameter: user - User类或子类的实例
# 觉醒角色
if user:
self.user = user
if Config.CHARACTER_FULL_UNLOCK:
# 全解锁了你觉醒个鬼啊
raise ArcError('All characters are available.')
if not self.uncap_cores:
self.select_character_core()
if self.is_uncapped is None:
self.c.execute(
'''select is_uncapped from user_char where user_id=? and character_id=?''', (self.user.user_id, self.character_id))
x = self.c.fetchone()
if x and x[0] == 1:
raise ArcError('The character has been uncapped.')
elif self.is_uncapped:
raise ArcError('The character has been uncapped.')
for i in self.uncap_cores:
self.c.execute(
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (self.user.user_id, i.item_id))
y = self.c.fetchone()
if i.amount > 0 and (not y or i.amount > y[0]):
raise ItemNotEnough('The cores are not enough.')
for i in self.uncap_cores:
i.user_claim_item(self.user, reverse=True)
self.c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
(self.user.user_id, self.character_id))
self.is_uncapped = True
self.is_uncapped_override = False
def upgrade(self, user=None, exp_addition: float = 0) -> None:
# parameter: user - User类或子类的实例
# 升级角色
if user:
self.user = user
if exp_addition == 0:
return None
if Config.CHARACTER_FULL_UNLOCK:
# 全解锁了你升级个鬼啊
raise ArcError('All characters are available.')
if self.level.exp is None:
self.select_character_info(self.user)
if self.is_uncapped is None:
self.c.execute(
'''select is_uncapped from user_char where user_id=? and character_id=?''', (self.user.user_id, self.character_id))
x = self.c.fetchone()
if x:
self.is_uncapped = x[0] == 1
self.level.max_level = 30 if self.is_uncapped else 20
self.level.add_exp(exp_addition)
self.c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(self.level.level, self.level.exp, self.user.user_id, self.character_id))
if self.character_id == 72:
self.update_insight_state()
def upgrade_by_core(self, user=None, core=None):
'''
以太之滴升级注意这里core.amount应该是负数
parameter: `user` - `User`类或子类的实例
`core` - `ItemCore`类或子类的实例
'''
if user:
self.user = user
if not core:
raise InputError('No `core_generic`.')
if core.item_id != 'core_generic':
raise ArcError('Core type error.')
if core.amount >= 0:
raise InputError(
'The amount of `core_generic` should be negative.')
core.user_claim_item(self.user)
self.upgrade(self.user, - core.amount * Constant.CORE_EXP)
def change_skill_state(self) -> None:
'''翻转技能状态,目前为 skill_miya 专用'''
self.skill_flag = not self.skill_flag
self.c.execute(f'''update {self.database_table_name} set skill_flag = ? where user_id = ? and character_id = ?''', (
1 if self.skill_flag else 0, self.user.user_id, self.character_id))
def update_insight_state(self) -> None:
if self.level.level == self.level.max_level:
if self.user.insight_state == 3:
self.user.update_user_one_column('insight_state', 5)
elif self.user.insight_state == 4:
self.user.update_user_one_column('insight_state', 6)
class UserCharacterList:
'''
用户拥有角色列表类
properties: `user` - `User`类或子类的实例
'''
database_table_name = 'user_char_full' if Config.CHARACTER_FULL_UNLOCK else 'user_char'
def __init__(self, c=None, user=None):
self.c = c
self.user = user
self.characters: list = []
def select_user_characters(self):
self.c.execute(
f'''select character_id from {self.database_table_name} where user_id=?''', (self.user.user_id,))
x = self.c.fetchall()
self.characters: list = []
if x:
for i in x:
self.characters.append(UserCharacter(self.c, i[0], self.user))
def select_characters_info(self):
for i in self.characters:
i.select_character_info(self.user)