diff --git a/backend/core/basic/character/__init__.py b/backend/core/basic/character/__init__.py new file mode 100644 index 00000000..6106bb9a --- /dev/null +++ b/backend/core/basic/character/__init__.py @@ -0,0 +1,43 @@ +""" +core.basic.character 包 +- jade.py : Jade(辟邪玉)数据类 +- base.py : CharacterBase — 属性声明、__init__、基础状态设置、技能操作 +- calc_equip.py : CalcEquipMixin — 套装/装备/时装/基础强化/辟邪玉/杂项计算 + calc 主入口 +- calc_carry.py : CalcCarryMixin — 输出角色伤害系数与技能伤害计算 +- calc_buffer.py : CalcBufferMixin — 辅助角色 Buff 技能计算 +- info.py : CharacterInfoMixin — 前端展示信息查询 +""" + +import importlib + +from .jade import Jade +from .calc_equip import CalcEquipMixin +from .calc_carry import CalcCarryMixin +from .calc_buffer import CalcBufferMixin +from .info import CharacterInfoMixin + + +class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMixin): + """ + 角色类,通过多重继承组合各模块能力。 + + MRO(方法解析顺序): + Character → CalcEquipMixin → CalcCarryMixin → CalcBufferMixin + → CharacterInfoMixin → CharacterBase → CharacterProperty + """ + + +def createCharacter(alter: str, equVersion: str = '0') -> 'Character': + character: Character = None + module_name = 'core.character.' + alter + if equVersion is None or equVersion == '': + equVersion = '0' + try: + character = importlib.import_module(module_name).classChange(equVersion) + except Exception as ex: + print(ex) + print('create character ' + module_name + ' error.') + return character + + +__all__ = ['Character', 'Jade', 'createCharacter'] diff --git a/backend/core/basic/character/base.py b/backend/core/basic/character/base.py new file mode 100644 index 00000000..7609349c --- /dev/null +++ b/backend/core/basic/character/base.py @@ -0,0 +1,338 @@ +from collections.abc import Mapping +from typing import Any, Literal + +from core.abstract.character import CharacterProperty +from core.basic.skill import Skill +from core.basic.equipment import EquEffect +from ..formula import 获取基础属性, 获取唤醒属性 +from ..roleinfo import CharacterEquipInfo, get_key_by_value, CharacterOathInfo +from .jade import Jade + + +class CharacterBase(CharacterProperty): + # region 额外类属性(CharacterProperty 中未声明的) + + BindAwake: int + """绑定觉醒等级""" + skills: list['Skill'] = [] + skills_dict: dict[str, 'Skill'] = {} + equVersion: str = '0' + """装备版本""" + charEquipInfo: dict[str, 'CharacterEquipInfo'] + """装备打造信息""" + charOathInfo: list['CharacterOathInfo'] + """誓约打造信息""" + equ_effect: list['EquEffect'] = [] + """装备效果列表""" + equ_options: dict[str, int] = {} + """装备选项列表""" + EquEffectRatio = 1.0 + """装备效果倍率""" + max_point = 0 + """套装效果点数""" + buffer: bool = False + + # region 职业属性 + 实际名称: str = '' + 名称: str = '' + 角色: str = '' + 角色类型: str = '' + 职业: str = '' + 武器选项: list[str] = [] + 副武器选项: list[str] = [] + 输出类型选项: list[str] = [] + 防具精通属性: list[str] = [] + 输出类型: str = '物理百分比' + # endregion + + # endregion + + # region 跨 Mixin 方法前向声明(供静态类型检查使用,由对应 Mixin 实现) + + def load_skills(self) -> None: + """加载技能(由 CalcEquipMixin 实现)""" + + def calc_carry_skills(self, DSB: bool, BUFF: bool) -> list: + """计算输出技能(由 CalcCarryMixin 实现)""" + return [] + + def get_carry_info(self) -> list: + """获取输出信息(由 CalcCarryMixin 实现)""" + return [] + + def calc_buffer_skills(self) -> list: + """计算辅助技能(由 CalcBufferMixin 实现)""" + return [] + + def get_buffer_info(self) -> list: + """获取辅助信息(由 CalcBufferMixin 实现)""" + return [] + + # endregion + + def __init__(self, equVersion, moduleName) -> None: + self.equVersion = equVersion + self.moduleName = moduleName + # 工会四维 + self.STR = 0 + 120 + self.INT = 0 + 120 + self.Spirit = 0 + 120 + self.Vitality = 0 + 120 + # 唤醒的情况下的基础数据 + self.AtkP = 0 + 65 + self.AtkM = 0 + 65 + self.AtkI = 1116 + 65 + self.PAtkP = 1.0 + self.PAtkM = 1.0 + self.PAtkI = 1.0 + self.ElementA = { + '火': False, + '冰': False, + '光': False, + '暗': False, + } + self.ElementDB = { + '火': 13, + '冰': 13, + '光': 13, + '暗': 13, + } + self.ElementR = { + '火': 0, + '冰': 0, + '光': 0, + '暗': 0, + } + self.Abnormal = { + '出血': False, + '灼伤': False, + '感电': False, + '中毒': False, + } + self.CriticalM = 0 + self.CriticalP = 0 + self.SkillAttack = 1 + self.SpeedA = 0 + self.SpeedM = 0 + self.SpeedR = 0 + self.HitP = 0 + self.Hit = 0 + self.MonsterInfo = { + 'DefenseP': 0, + 'DefenseM': 0, + '火抗': 0, + '冰抗': 0, + '光抗': 0, + '暗抗': 0, + } + self.Buffer = 0 + self.BufferP = 1.0 + self.Attack = 0 + self.AttackP = 1.0 + self.ElementIncrease = 1.0 + self.equs = {} + self.jade_effect = Jade() + self.equ_effect = [] + self.equ_options = {} + self.max_point = 0 + self.skills = [] + self.skills_dict = {} + self.load_skills() + + # region 角色属性设置 + + def SetBaseStatus(self) -> None: + """设角色基础属性""" + temp = 获取基础属性(self.角色, self.职业) + 唤醒 = 获取唤醒属性() + for index, i in enumerate([get_key_by_value(i) for i in ['力量', '体力', '智力', '精神']]): + setattr(self, i, getattr(self, i) + temp[index] + 唤醒) + + def SetStatus( + self, + 力量=0, + 智力=0, + 力智=0, + 体力=0, + 精神=0, + 体精=0, + 四维=0, + 物攻=0, + 魔攻=0, + 独立=0, + 三攻=0, + 技攻=0, + 全属强=0, + 全属抗=0, + 移动速度=0, + 攻击速度=0, + 施放速度=0, + 三速=0, + 命中=0, + 命中率=0, + 物暴=0, + 魔暴=0, + 攻击强化=0, + 攻击强化P=0, + 增益量=0, + 增益量P=0, + **kwargs: Mapping[ + Literal[ + 'STR', + 'INT', + 'AtkP', + 'AtkM', + 'AtkI', + 'PAtkP', + 'PAtkM', + 'PAtkI', + 'PATKIPDamage', + 'PDamageC', + 'PSTR', + 'PINT', + 'PDamageB', + 'ElementA', + 'ElementDB', + 'ElementR', + 'CriticalMP', + 'CriticalM', + 'CriticalPP', + 'CriticalP', + 'EquipmentSkillAttack', + 'SkillAttack', + 'SpeedA', + 'SpeedM', + 'SpeedR', + 'HitP', + 'Hit', + 'Attack', + 'AttackP', + 'Buffer', + 'BufferP', + 'Spirit', + 'Vitality', + 'EquEffectRatio', + '火强', + '冰强', + '光强', + '暗强', + '火属性抗性', + '冰属性抗性', + '光属性抗性', + '暗属性抗性', + '火属性攻击', + '冰属性攻击', + '光属性攻击', + '暗属性攻击', + ], + Any, + ], + ) -> None: + """设置角色属性""" + self.STR += 力量 + kwargs.get('STR', 0) + 四维 + 力智 + self.INT += 智力 + kwargs.get('INT', 0) + 四维 + 力智 + self.Spirit += 精神 + kwargs.get('Spirit', 0) + 四维 + 体精 + self.Vitality += 体力 + kwargs.get('Vitality', 0) + 四维 + 体精 + self.AtkP += 物攻 + kwargs.get('AtkP', 0) + 三攻 + self.AtkM += 魔攻 + kwargs.get('AtkM', 0) + 三攻 + self.AtkI += 独立 + kwargs.get('AtkI', 0) + 三攻 + self.PAtkP *= kwargs.get('PAtkP', 1.0) + self.PAtkM *= kwargs.get('PAtkM', 1.0) + self.PAtkI *= kwargs.get('PAtkI', 1.0) + for elem in ['火', '冰', '光', '暗']: + self.ElementA[elem] = kwargs.get(f'{elem}属性攻击', False) or kwargs.get('ElementA', {}).get(elem, False) or self.ElementA[elem] + self.ElementDB[elem] += kwargs.get(f'{elem}强', 0) + kwargs.get('ElementDB', {}).get(elem, 0) + 全属强 + self.ElementR[elem] += kwargs.get(f'{elem}抗', 0) + kwargs.get('ElementR', {}).get(elem, 0) + 全属抗 + self.CriticalM += kwargs.get('CriticalM', 0) + 魔暴 + self.CriticalP += kwargs.get('CriticalP', 0) + 物暴 + self.SkillAttack *= (1 + 技攻) * (1 + kwargs.get('SkillAttack', 0)) + self.SpeedA += 攻击速度 + kwargs.get('SpeedA', 0) + 三速 + self.SpeedM += 移动速度 + kwargs.get('SpeedM', 0) + 三速 + self.SpeedR += 施放速度 + kwargs.get('SpeedR', 0) + 三速 + self.HitP += 命中率 + kwargs.get('HitP', 0) + self.Hit += 命中 + kwargs.get('Hit', 0) + self.Attack += 攻击强化 + kwargs.get('Attack', 0) + self.AttackP += 攻击强化P + kwargs.get('AttackP', 0.0) + self.Buffer += 增益量 + kwargs.get('Buffer', 0) + self.BufferP += 增益量P + kwargs.get('BufferP', 0) + self.EquEffectRatio += kwargs.get('EquEffectRatio', 0.0) + + def AddElementDB(self, element: str, value: float, type: int = 0) -> None: + """ + 增加属性强化 + type: 0 站街 1 进图 + """ + if element not in self.ElementDB: + raise ValueError(f'Invalid element: {element}') + if type == 0: + value = value * (1 + self.jade_effect.ElementIncrease) + if type == 1: + value = int(value * (1 + self.jade_effect.ElementIncrease)) + self.ElementDB[element] += value + + def AddSkillLv(self, min: int, max: int, lv: int, type=-1, exceptSkills: list[str] = []) -> None: + """ + 增加技能等级 + type: -1 全部, 0 被动, 1 主动 + """ + skillType = 'all' if type == -1 else ('active' if type == 1 else 'passive') + for skill in self.skills: + if min <= skill.learnLv <= max: + if (skillType == 'all' or skill.type == skillType) and skill.lv > 0 and skill.name not in exceptSkills: + skill.lv += lv + + def SetSkillCD(self, min=1, max=100, cd=0, exclude=[50, 85, 100]) -> None: + """设置技能CD""" + for skill in self.skills: + if min <= skill.learnLv <= max and (skill.type == 'active' or skill.damage) and skill.learnLv not in exclude: + skill.cdReduce *= 1 - cd + + def SetSkillCDRecover(self, min=1, max=100, cd=0, exclude=[50, 85, 100]) -> None: + """设置技能CD恢复""" + for skill in self.skills: + if min <= skill.learnLv <= max and (skill.type == 'active' or skill.damage) and skill.learnLv not in exclude: + skill.cdRecover += cd + + def SetSkillRation(self, min=1, max=100, ratio=1, type=-1) -> None: + """设置技能倍率 + 0 修改技能面板 1 不修改技能面板 + type: -1 全部, 0 被动, 1 主动 + """ + for skill in self.skills: + if min <= skill.learnLv <= max and skill.damage and skill.type == 'active': + if type == 0: + skill.skillRation *= 1 + ratio + else: + skill.skillDamage *= 1 + ratio + + # endregion + + # region 技能查询 + + def GetSkillByName(self, name) -> 'Skill': + """通过技能名获取技能""" + return self.skills_dict.get(name, None) + + def GetSkillByID(self, id: str) -> 'Skill': + """通过技能ID获取技能""" + skill = list(filter(lambda x: str(x.id) == id, self.skills)) + return skill[0] if len(skill) > 0 else None + + def GetSkillByUUID(self, uuid: str) -> 'Skill': + """通过技能UUID获取技能""" + skill = list(filter(lambda x: str(x.uuid) == uuid, self.skills)) + return skill[0] if len(skill) > 0 else None + + def GetSkillNames(self, type: Literal['active', 'passive', 'all'] = 'all', damage: Literal[True, False, 'all'] = 'all') -> list[str]: + """获取技能名字""" + return [skill.name for skill in self.skills if (type == 'all' or skill.type == type) and (damage == 'all' or skill.damage == damage)] + + def GetWeaponType(self) -> tuple[str, str]: + """获取武器类型""" + weapon = self.charEquipInfo['武器'].equInfo + if weapon is None: + return None, None + else: + return weapon.itemDetailType, weapon.categorize + + # endregion diff --git a/backend/core/basic/character/calc_buffer.py b/backend/core/basic/character/calc_buffer.py new file mode 100644 index 00000000..ebfa4b75 --- /dev/null +++ b/backend/core/basic/character/calc_buffer.py @@ -0,0 +1,64 @@ +from core.basic.property import CharacterInfo +from ..roleinfo import get_key_by_value +from .base import CharacterBase + + +class CalcBufferMixin(CharacterBase): + """辅助角色 Buff 技能计算相关方法""" + + # region 辅助计算项 + + def calc_buffer_skills(self): + skillInfos = [] + # 适用属性 + main = getattr(self, get_key_by_value(self.适用属性)) + # 增益量 + buffer_power = self.Buffer * self.BufferP + # buff系数 + buff_ratio_new = (main / 2993 + 1) * (buffer_power / 4800 + 1) if buffer_power > 0 else 0 + buff_ratio_old = main / 2993 + 1 + # 觉醒系数 + awake_ratio_new = ((main + 7500) / 7500 + 1) * ((buffer_power + 68000) / 8500 + 1) if buffer_power > 0 else 0 + awake_ratio_old = main / 7500 + 1 + for i in self.skills: + if i.lv > 0 and i.hasUP and i.up is not None and i.up > 0: + self.SetStatus(四维=40) + if i.buffer and i.lv > 0: + info = i.skillInfo() + value = info[0] + if i.buffType == 'buff' or i.buffType == 'buffSub': + value1 = (info[1][0] + info[1][2]) * info[1][1] * buff_ratio_old + info[1][0] * buff_ratio_new + value2 = (info[2][0] + info[2][2]) * info[2][1] * buff_ratio_old + info[2][0] * buff_ratio_new + elif i.buffType == 'awake' or i.buffType == 'awakeSub': + value1 = (info[1][0] + info[1][2]) * info[1][1] * awake_ratio_old + info[1][0] * awake_ratio_new + value2 = (info[2][0] + info[2][2]) * info[2][1] * awake_ratio_old + info[2][0] * awake_ratio_new + else: + value1 = (info[1][0] + info[1][2]) * info[1][1] + value2 = (info[2][0] + info[2][2]) * info[2][1] + value3 = info[3] + value4 = info[4] + if i.buffType == 'awake' and self.bindAwake == 50: + pass + else: + skillInfos.append( + { + 'name': i.name, + 'cd': value4, + 'icon': i.icon, + 'lv': i.lv, + 'buff': [value, value1, value2, value3], + 'type': i.type, + } + ) + return skillInfos + + def get_buffer_info(self): + attrs = [get_key_by_value(self.适用属性), 'Buffer', 'BufferP'] + info = [] + for attr in attrs: + temp = getattr(CharacterInfo, attr) + value = getattr(self, attr) + info.append({'name': temp.name, 'value': temp.value(value)}) + return info + + # endregion diff --git a/backend/core/basic/character/calc_carry.py b/backend/core/basic/character/calc_carry.py new file mode 100644 index 00000000..956d41d5 --- /dev/null +++ b/backend/core/basic/character/calc_carry.py @@ -0,0 +1,103 @@ +from ..formula import 获取基础属性 +from core.basic.property import CharacterInfo +from .base import CharacterBase + + +class CalcCarryMixin(CharacterBase): + """输出角色技能伤害计算相关方法""" + + # region 输出计算项 + + def calc_damage_ration(self, DSB: bool, BUFF: bool): + """计算属性系数""" + attrs = [] + if self.输出类型 == '物理百分比': + attrs.extend(['STR', 'AtkP', 'PAtkP']) + if self.输出类型 == '魔法百分比': + attrs.extend(['INT', 'AtkM', 'PAtkM']) + if self.输出类型 == '物理固伤': + attrs.extend(['STR', 'AtkI', 'PAtkI']) + if self.输出类型 == '魔法固伤': + attrs.extend(['INT', 'AtkI', 'PAtkI']) + # 力智系数 + value0_1 = getattr(self, attrs[0]) + value0_2 = 获取基础属性(self.角色, self.职业)[0] if attrs[0] == 'STR' else 获取基础属性(self.角色, self.职业)[1] + # 系统奶及奶系增幅 + value = value0_1 + ((value0_1 - value0_2) * 3.08 + 2886) * (1 if DSB else 0) + (170000 + 300000) * (1 if BUFF else 0) + ratio_0: float = value / 250 + 1 + # 物理/魔法/独立攻击力 + ratio_1: float = getattr(self, attrs[1]) + 30000 * (1 if BUFF else 0) + # 技能 物理/魔法/独立攻击力% + ratio_2: float = getattr(self, attrs[2]) + # 属强系数 + ratio_3 = max(self.ElementDB.values()) * 0.0045 + 1.05 + # 暴击系数 + ratio_4 = 1.5 + # BUFF系数 + ratio_5 = self.buff + # 技攻系数 + ratio_6 = self.SkillAttack * (self.jade_effect.SkillAttack + 1) + # 攻击强化 + ratio_7 = 1 + self.Attack / 100 * (self.AttackP + self.jade_effect.AttackP) + # 防御系数,暂定145沙袋防御 + monster_defense = 81417275817 + ratio_8 = 1 - monster_defense / (monster_defense + 200 * 100) + # 杂项 + ratio_9 = 1.0 * self.ElementIncrease + return (ratio_0, ratio_1, ratio_2, ratio_3, ratio_4, ratio_5, ratio_6, ratio_7, ratio_8, ratio_9) + + def calc_carry_skills(self, DSB: bool, BUFF: bool): + skillInfos = [] + for i in self.skills: + if i.damage and i.lv > 0: + for mode in i.mode: + temp = i.skillInfo(mode) + if temp[0] > 0: + skillInfos.append( + { + 'name': i.name, + 'icon': i.icon, + 'lv': i.lv, + 'data': temp[0], + 'ratio': temp[1], + 'cd': temp[2], + 'mode': mode, + 'learnLv': i.learnLv, + 'type': i.type, + } + ) + ratios = self.calc_damage_ration(DSB, BUFF) + ratio_char_skill = ratios[0] * ratios[1] * ratios[2] * ratios[3] * ratios[4] * ratios[5] * ratios[6] * ratios[7] * ratios[8] * ratios[9] / 1000 + ratuio_equ_skill = ratios[0] * ratios[1] * ratios[3] * ratios[4] * ratios[6] * ratios[7] * ratios[8] * ratios[9] / 1000 + for i in skillInfos: + i['damage'] = ratio_char_skill * i['data'] * i['ratio'] / 100 + for i in self.equ_effect: + skillInfos.append({'name': i.name, 'icon': i.icon, 'lv': 0, 'data': i.data, 'ratio': 10.0, 'cd': i.cd, 'damage': ratuio_equ_skill * i.data * 10 * self.EquEffectRatio / 100, 'mode': ''}) + return skillInfos + + def get_carry_info(self): + attrs = [] + if self.输出类型 == '物理百分比': + attrs.extend(['STR', 'AtkP']) + if self.输出类型 == '魔法百分比': + attrs.extend(['INT', 'AtkM']) + if self.输出类型 == '物理固伤': + attrs.extend(['STR', 'AtkI']) + if self.输出类型 == '魔法固伤': + attrs.extend(['INT', 'AtkI']) + info = [] + attrs.extend(['Attack', 'AttackP', 'SkillAttack', 'EquEffectRatio', 'ElementDB']) + for attr in attrs: + temp = getattr(CharacterInfo, attr) + if attr.startswith('Atk'): + info.append({'name': temp.name, 'value': temp.value(getattr(self, attr) * getattr(self, f'P{attr}'))}) + else: + value = getattr(self, attr) + if attr == 'SkillAttack': + value *= self.jade_effect.SkillAttack + 1 + if attr == 'AttackP': + value += self.jade_effect.AttackP + info.append({'name': temp.name, 'value': temp.value(value)}) + return info + + # endregion diff --git a/backend/core/basic/character/calc_equip.py b/backend/core/basic/character/calc_equip.py new file mode 100644 index 00000000..7c435776 --- /dev/null +++ b/backend/core/basic/character/calc_equip.py @@ -0,0 +1,314 @@ +import sys +from uuid import uuid1 + +from core.basic.equipment import get_equipment +from ..formula import 增幅计算, 强化技攻, 武器强化计算, 精通计算, 耳环计算, 左右计算, 锻造计算 +from ..roleinfo import CharacterEquipInfo, CharacterOathInfo, get_key_by_value +from .base import CharacterBase + + +class CalcEquipMixin(CharacterBase): + """装备计算、角色初始化相关方法""" + + # region 打造信息导入 + + def SetDetail(self, info: dict[str, dict]) -> None: + """打造信息导入""" + self.charEquipInfo = {} + self.charOathInfo = [] + self.bindAwake = info.get('bindAwake', 50) + for key in info['equips']: + self.charEquipInfo[key] = CharacterEquipInfo(info['equips'][key], self.equVersion, key) + for key in info['oaths']: + # 导入誓约打造信息、誓约信息 + self.charOathInfo.append(CharacterOathInfo(info['oaths'][key], self.equVersion, key)) + + def calc_init(self, setInfo: dict[str, dict]): + self.equ_options = setInfo.get('options', {}) + self.setInfo = setInfo + self.SetDetail(setInfo) + self.SetSkills(setInfo.get('skills', {})) + + def SetSkills(self, skills: dict[str, dict]): + """设置技能等级""" + for key in skills: + skill = self.GetSkillByID(key) + if skill is not None: + if skill.hasUP: + skill.up = skills[key].get('up', 0) + if skill.hasVP: + skill.vp = skills[key].get('vp', 0) + skill.lv += skills[key].get('lv', 0) + + def load_skills(self) -> None: + """加载技能""" + module = sys.modules[self.moduleName] + for name in dir(module): + memberClass = getattr(module, name) + if isinstance(memberClass, type) and memberClass.__name__.startswith('Skill'): + skill = memberClass(char=self) + self.skills_dict[skill.name] = skill + self.skills.append(skill) + + # endregion + + # region 套装 / 装备 / 时装 / 基础属性 / 辟邪玉 / 杂项 + + def calc_suits(self): + """套装效果统计""" + suitInfo = {} + oathSuitInfo = {} + equs = get_equipment(self.equVersion) + for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values()): + partEqu = item.equInfo + # partFusion = item.fusionInfo + if partEqu is not None: + for suit in partEqu.suit: + if suitInfo.get(suit, None) is None: + suitInfo[suit] = {'point': 0, 'count': 0} + if partEqu.Point == 0: + suitInfo[suit]['count'] += 1 + else: + suitInfo[suit]['point'] += partEqu.Point + for item in self.charOathInfo: + partOath = item.oathInfo + if partOath is not None: + for suit in partOath.suit: + if oathSuitInfo.get(suit, None) is None: + oathSuitInfo[suit] = {'point': 0, 'count': 0} + if partOath.Point == 0: + oathSuitInfo[suit]['count'] += 1 + else: + oathSuitInfo[suit]['point'] += partOath.Point + max_point_suit = max((suit for suit in suitInfo if suitInfo[suit]['point'] > 0), key=lambda suit: suitInfo[suit]['point'], default=None) + max_point_oath_suit = max((suit for suit in oathSuitInfo if oathSuitInfo[suit]['point'] > 0), key=lambda suit: oathSuitInfo[suit]['point'], default=None) + zero_point_suits = [suit for suit in suitInfo if suitInfo[suit]['point'] == 0] + # 套装没有到太初的时候,优先把誓约套装的点数加到装备套装上,以达到更高的套装等级 + if 0 < suitInfo[max_point_suit]['point'] < 2550: + suitInfo[max_point_suit]['point'] += min(2550 - suitInfo[max_point_suit]['point'], oathSuitInfo[max_point_oath_suit]['point']) + oathSuitInfo[max_point_oath_suit]['point'] -= min(2550 - suitInfo[max_point_suit]['point'], oathSuitInfo[max_point_oath_suit]['point']) + pass + suits = zero_point_suits + ([max_point_suit] if max_point_suit is not None else []) + suitList = [] + oathSuitList = [] + res = [] + for suit in suits: + if suitInfo[suit]['point'] == 0: + suitList += equs.get_suit_info(suit, 0, suitInfo[suit]['count']) + else: + suitList += equs.get_suit_info(suit, suitInfo[suit]['point'], 0) + if len(suitList) == 0: + continue + temp = suitList[-1] + res.append( + { + 'id': temp.id, + 'name': temp.name, + 'rarity': temp.rarity, + 'point': suitInfo[suit]['point'], + 'count': suitInfo[suit]['count'], + 'level': temp.level, + 'imageUrl': temp.imageUrl, + 'value': temp.value, + } + ) + + for suit in oathSuitInfo: + if oathSuitInfo[suit]['point'] == 0: + oathSuitList += equs.get_oath_suit_info(suit, 0, oathSuitInfo[suit]['count']) + else: + oathSuitList += equs.get_oath_suit_info(suit, oathSuitInfo[suit]['point'], 0) + if len(oathSuitList) == 0: + continue + print(oathSuitList[0].__dict__) + suits_effect = [i.id for i in suitList] + for i in suits_effect: + func = equs.funs.execture(f'suit_{i}') + suit = next((x for x in equs.suits if str(x.id) == str(i)), None) + if func is not None: + func(self) + if suit is not None: + filtered_dict = {k: v for k, v in suit.__dict__.items() if k[0].isupper()} + self.SetStatus(**filtered_dict) + self.max_point = max([i['point'] for i in res], default=0) + if self.max_point >= 2550: + skillAttack = (self.max_point - 2550) // 70 * 0.01 + buffer = (self.max_point - 2550) // 70 * 100 + self.SetStatus(SkillAttack=skillAttack, Buffer=buffer) + return res + + def calc_equs(self): + """计算装备基础效果、附魔""" + effects = get_equipment(self.equVersion).funs + # for detail in [(item.equInfo, item.fusionInfo, item.enchant, item.emblem_0, item.emblem_1) for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values())]: + for detail in [(item.equInfo, item.enchant) for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values())]: + equ = detail[0] + if equ is None or equ.itemType == '副武器': + continue + enchat = detail[1] + fun = effects.execture(f'equ_{equ.id}') + if fun is None: + continue + filtered_dict = {k: v for k, v in equ.__dict__.items() if k[0].isupper()} + self.SetStatus(**filtered_dict) + fun(self) + fun = effects.execture(f'enchant_{enchat}') + if fun is not None: + fun(self) + # emblem_0 = detail[3] + # emblem_1 = detail[4] + # if str(emblem_0).isdigit(): + # fun = effects.execture(f'emblem_{emblem_0}') + # if fun is not None: + # fun(self) + # else: + # skill = self.GetSkillByName(emblem_0) + # if skill is not None: + # skill.lv += 1 + # self.SetStatus(四维=8) + # fun = effects.execture(f'emblem_{emblem_1}') + # if fun is not None: + # fun(self) + # if fusion is None: + # continue + # fun = effects.execture(f'stone_{fusion.id}') + # filtered_dict = {k: v for k, v in fusion.__dict__.items() if k[0].isupper()} + # self.SetStatus(**filtered_dict) + # if fun is not None: + # fun(self) + + def calc_oaths(self): + pass + + def calc_avatar(self, avatar: dict): + """计算时装效果""" + equ = get_equipment(self.equVersion) + dress = {key: value for key, value in avatar.items() if key in ['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋']} + equ.funs.calc_dress_effect(dress, self) + avatarElse = {key: value for key, value in avatar.items() if key not in ['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋']} + for key in avatarElse: + value = avatarElse[key].get('enchant', None) + emblem_0 = avatarElse[key].get('emblem_0', None) + emblem_1 = avatarElse[key].get('emblem_1', None) + if value is not None: + fun = equ.funs.execture(f'enchant_{value}') + if fun is not None: + fun(self) + for i in [emblem_0, emblem_1]: + if i is None: + continue + fun = equ.funs.execture(f'emblem_{i}') + if fun is not None: + fun(self) + + def calc_weapon(self, cur: 'CharacterEquipInfo'): + """武器强化、锻造计算""" + value = 锻造计算(115, '史诗', cur.refine) + self.SetStatus(AtkI=value) + if '传世武器' in cur.equInfo.categorize: + value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '物理', 1.12) + self.SetStatus(AtkP=value, AtkM=value) + else: + value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '物理') + self.SetStatus(AtkP=value) + value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '魔法') + self.SetStatus(AtkM=value) + + def calc_basic(self): + """计算基础属性:防具精通、增幅、强化等""" + for part in ['上衣', '头肩', '下装', '腰带', '鞋']: + cur = self.charEquipInfo[part] + if cur.equInfo is None: + continue + equ = cur.equInfo + reinforce = self.charEquipInfo[part].reinforce + if equ is None: + continue + for key in self.防具精通属性: + value = 精通计算(115, equ.rarity, reinforce, equ.itemDetailType, self.防具类型, self.buffer, key) + key = get_key_by_value(key) + setattr(self, key, getattr(self, key) + value) + for part in self.charEquipInfo.keys(): + cur = self.charEquipInfo[part] + if cur.equInfo is None: + continue + if cur.reinforceType == 1: + value = 增幅计算(115, '史诗', cur.reinforce) + self.SetStatus(STR=value, INT=value, Spirit=value, Vitality=value) + value = 强化技攻(cur.reinforce, cur.reinforceType, part) + self.SetStatus(SkillAttack=value) + if '武器' in part: + self.calc_weapon(cur) + if part == '耳环': + value = 耳环计算(115, '史诗', cur.reinforce) + self.SetStatus(AtkM=value, AtkP=value, AtkI=value) + if part in ['辅助装备', '魔法石']: + value = 左右计算(115, '史诗', cur.reinforce) + self.SetStatus(STR=value, INT=value, Spirit=value, Vitality=value) + + def calc_jade(self, jades): + """计算辟邪玉效果""" + funs = get_equipment(self.equVersion).funs + for key in jades: + jade = jades[key] + fun = funs.execture(f'jade_{jade.get("id", 0)}') + if fun is not None: + fun(self, jade.get('value', 0)) + + def calc_sundry(self, sundry: dict): + """计算杂项效果""" + funs = get_equipment(self.equVersion).funs + funs.execture('sundry_0')( + self, + sundry.get('medal_rarity', 0), + sundry.get('medal_reinforce', 0), + [sundry.get('medal_gem_0', 0), sundry.get('medal_gem_1', 0), sundry.get('medal_gem_2', 0), sundry.get('medal_gem_3', 0)], + ) + funs.execture('sundry_6')(self, sundry.get('adventure', 1)) + funs.execture('sundry_5')(self, sundry.get('fog', 1)) + funs.execture('sundry_7')(self, sundry.get('marriage_house', 0), sundry.get('marriage_ring', 0)) + funs.execture('sundry_9')(self, sundry.get('contract', 0)) + funs.execture('sundry_10')( + self, + sundry.get('collection_type', 0), + sundry.get('collection_num_0', 0), + sundry.get('collection_num_1', 0), + ) + funs.execture('sundry_13')(self, sundry.get('costume_card', 0)) + + # endregion + + # region 主计算入口 + + def calc(self, setInfo: dict[str, dict]): + self.calc_init(setInfo) + self.SetBaseStatus() + self.calc_jade(setInfo.get('jades', [])) + self.calc_avatar(setInfo.get('avatar', {})) + self.calc_basic() + self.calc_sundry(setInfo.get('sundry', {})) + suit = self.calc_suits() + self.calc_equs() + self.calc_oaths() + skillInfos = [] + if self.buffer: + skillInfos = self.calc_buffer_skills() + info = self.get_buffer_info() + else: + if len(self.输出类型选项) > 1: + temp = self.输出类型选项[0] + if '固伤' in temp: + if self.INT > self.STR: + self.输出类型 = '魔法固伤' + else: + self.输出类型 = '物理固伤' + else: + if self.INT > self.STR and self.AtkM > self.AtkP: + self.输出类型 = '魔法百分比' + else: + self.输出类型 = '物理百分比' + skillInfos = self.calc_carry_skills(setInfo.get('DSB', False), setInfo.get('BUFF', False)) + info = self.get_carry_info() + return {'uuid': uuid1().hex, 'skills': skillInfos, 'info': info, 'suits': suit, 'buffer': self.buffer} + + # endregion diff --git a/backend/core/basic/character/info.py b/backend/core/basic/character/info.py new file mode 100644 index 00000000..ca24b24f --- /dev/null +++ b/backend/core/basic/character/info.py @@ -0,0 +1,120 @@ +from core.basic.equipment import get_equipment +from core.basic.property import CharacterInfo +from .base import CharacterBase + + +class CharacterInfoMixin(CharacterBase): + """角色信息查询相关方法(用于前端展示)""" + + def getInfo(self): + """返回到前端信息""" + info = {} + info['role'] = self.role + info['alter'] = self.name + info['equVersion'] = self.equVersion + info['name'] = self.nameCN + info['weapons'] = self.武器选项 + info['subweapons'] = self.副武器选项 + info['buffer'] = self.buffer + if self.buffer: + info['properties'] = self.防具精通属性 + info['properties'] = self.输出类型选项 + skillInfo = [] + skill_clothes = [] + platinum = [] + for skill in self.skills: + if 15 <= skill.learnLv <= 70 and skill.learnLv not in [48, 50]: + platinum.append(skill.name) + if skill.learnLv <= 95: + skill_clothes.append(skill.name) + skillInfo.append( + { + 'id': skill.id, + 'name': skill.name, + 'icon': skill.icon, + 'type': skill.type, + 'learnLv': skill.learnLv, + 'masterLv': skill.masterLv, + 'maxLearnLv': skill.calculate_lv(), + 'maxLv': skill.maxLv, + 'position': skill.position, + 'line': skill.line, + 'bind': skill.bind, + 'hasVP': skill.hasVP, + 'hasUP': skill.hasUP, + 'upType': skill.upType, + 'uuid': skill.uuid, + 'vps': [{'name': i['name'], 'desc': i['desc']} for i in skill.vps], + } + ) + info['skills'] = skillInfo + equInfos = get_equipment(self.equVersion) + equs = [] + suits = [] + stones = [] + for i in equInfos.equs: + if i.itemType == '武器' and i.itemDetailType not in self.武器选项: + continue + equs.append(i.__dict__) + for i in equInfos.suits: + suits.append(i.__dict__) + for i in equInfos.stones: + stones.append(i.__dict__) + info['equips'] = equs + info['suits'] = suits + info['stones'] = stones + key = '辅助' if self.buffer else '输出' + info['enchants'] = list(filter(lambda x: key in x['categorize'], equInfos.enchants)) + info['emblems'] = [i for i in equInfos.emblems] + info['avatar'] = equInfos.funs.get_dress_list(skill_clothes) + info['jades'] = equInfos.jades + info['options'] = equInfos.funs.options + info['sundry'] = equInfos.funs.sundryList + for skill in platinum: + info['emblems'].append( + { + 'id': skill, + 'fame': 232, + 'position': ['辅助装备', '魔法石'], + 'detail': skill + ' Lv+1' + ' 四维 + 8', + 'categorize': ['技能'], + 'rarity': '白金', + } + ) + return info + + def getBasicInos(self): + res = [] + attrs = [] + if self.输出类型.includes('物理'): + attrs = ['STR', 'PSTR', 'AtkP', 'PAtkP', 'CriticalPP', 'CriticalP'] + if self.输出类型.includes('魔法'): + attrs = ['INT', 'PINT', 'AtkM', 'PAtkM', 'CriticalMP', 'CriticalM'] + for attr in attrs + ['PDamage', 'PDamageC', 'PDamageB', 'SpeedA', 'SpeedM', 'SpeedR']: + temp = getattr(CharacterInfo, attr) + res.append( + { + 'name': temp.name, + 'value': temp.value(getattr(self, attr)), + 'icon': temp.icon, + } + ) + keys = ['火', '冰', '光', '暗'] + for index in range(0, 4): + temp = getattr(CharacterInfo, f'ElementDB{index}') + res.append( + { + 'name': temp.name, + 'value': temp.value(getattr(self, 'ElementDB')[keys[index]]), + 'icon': temp.icon, + } + ) + temp = getattr(CharacterInfo, f'ElementR{index}') + res.append( + { + 'name': temp.name, + 'value': temp.value(getattr(self, 'ElementR')[keys[index]]), + 'icon': temp.icon, + } + ) + return res diff --git a/backend/core/basic/character/jade.py b/backend/core/basic/character/jade.py new file mode 100644 index 00000000..301e6a7f --- /dev/null +++ b/backend/core/basic/character/jade.py @@ -0,0 +1,12 @@ +class Jade: + ElementIncrease: float + """属性增幅""" + AttackP: float + """攻击强化%""" + SkillAttack: float + """技能攻击力%""" + + def __init__(self) -> None: + self.ElementIncrease = 0.0 + self.AttackP = 0.0 + self.SkillAttack = 0.0 diff --git a/backend/core/basic/character.py b/backend/core/basic/character_old.py similarity index 98% rename from backend/core/basic/character.py rename to backend/core/basic/character_old.py index e1654a97..f140d5b2 100644 --- a/backend/core/basic/character.py +++ b/backend/core/basic/character_old.py @@ -1,3 +1,15 @@ +# ⚠️ 此文件已被 character/ 包目录取代。 +# Python 会优先加载 character/__init__.py,此文件不会被 import 执行。 +# +# 模块拆分结构(位于 core/basic/character/): +# jade.py — Jade 辟邪玉类 +# base.py — CharacterBase(属性 / __init__ / 基础状态方法 / 技能操作) +# calc_equip.py — CalcEquipMixin(套装/装备/时装/强化/辟邪玉/杂项 + calc 主入口) +# calc_carry.py — CalcCarryMixin(输出伤害计算) +# calc_buffer.py — CalcBufferMixin(辅助 Buff 技能计算) +# info.py — CharacterInfoMixin(前端信息查询) +# __init__.py — 组装 Character 类 + createCharacter 工厂函数 + from functools import cache import importlib import sys diff --git a/backend/core/basic/equipment.py b/backend/core/basic/equipment.py index 1c652067..39f18b9c 100644 --- a/backend/core/basic/equipment.py +++ b/backend/core/basic/equipment.py @@ -89,15 +89,16 @@ class Equipments: self.emblems = [] self.suits: list[Suit] = [] self.suit_dict: dict[str, Suit] = {} - self.oath_suits: list[Suit] = [] + self.oath_suits: list[OathSuit] = [] + self.oath_suit_dict: dict[str, OathSuit] = {} self.init_equs() self.init_suits() self.init_enchants() self.init_stones() self.init_emblem() self.init_jades() - # self.init_oaths() - # self.init_oath_suits() + self.init_oaths() + self.init_oath_suits() self.engine.dispose() def init_equs(self): @@ -235,7 +236,9 @@ class Equipments: buffers = parse_to_number_list(suit['Buffer'], [0]) suit['Buffer'] = 0 if i - 1 >= len(buffers) else buffers[i - 1] suit['skills'] = [OathSuitSkill(**{k: v for k, v in skill.__dict__.items() if not k.startswith('_')}) for skill in skills] - self.oath_suits.append(suit) + oath_suit = OathSuit(**suit) + self.oath_suits.append(oath_suit) + self.oath_suit_dict.setdefault(str(suit['suitId']), []).append(oath_suit) pass pass @@ -263,7 +266,7 @@ class Equipments: result[suit.count] = suit return list(result.values()) - def get_oath_suit_info(self, suitId: str | int, point: int = 0, count: int = 0) -> list[Suit]: + def get_oath_suit_info(self, suitId: str | int, point: int = 0, count: int = 0) -> list['OathSuit']: """根据套装点数返回对应适用的誓约套装属性\n 新套装只需要传入suitID和point即可,count默认为0\n 老套装需要传入suitID和count即可,point默认为0 @@ -271,8 +274,8 @@ class Equipments: if suitId not in self.oath_suit_dict: return [] # 先筛选出点数小于等于point和数量小于等于count的套装 - suits: list[Suit] = [suit for suit in self.oath_suit_dict[str(suitId)] if suit.point <= point and suit.count <= count] - result: dict[int, Suit] = {} + suits: list[OathSuit] = [suit for suit in self.oath_suit_dict[str(suitId)] if suit.point <= point and suit.count <= count] + result: dict[int, OathSuit] = {} # 取每种count套装中点数最高的 for suit in suits: if suit.count not in result or suit.point > result[suit.count].point: diff --git a/backend/core/basic/roleinfo.py b/backend/core/basic/roleinfo.py index 6087d36e..413af6d5 100644 --- a/backend/core/basic/roleinfo.py +++ b/backend/core/basic/roleinfo.py @@ -13,32 +13,32 @@ class CharacterEquipInfo: """强化类型""" enchant: str """附魔""" - emblem_0: str - """徽章1""" - emblem_1: str - """徽章2""" - fusion: int | None - """贴膜ID""" + # emblem_0: str + # """徽章1""" + # emblem_1: str + # """徽章2""" + # fusion: int | None + # """贴膜ID""" refine: int | None """锻造等级""" - weaponFusion: int | None - """武器融合详情""" + # weaponFusion: int | None + # """武器融合详情""" adaptation: int """调适等级""" equInfo: Union['Equ', None] """装备信息""" - fusionInfo: Union['Equ', None] - """贴膜信息""" + # fusionInfo: Union['Equ', None] + # """贴膜信息""" precision: float """秘宝精度""" def __init__(self, info={}, equVerison=0, part='') -> None: - info['fusion'] = info.get('fusion', None) + # info['fusion'] = info.get('fusion', None) info['precision'] = info.get('precision', 0) - info['weaponFusion'] = info.get('weaponFusion', None) + # info['weaponFusion'] = info.get('weaponFusion', None) self.__dict__.update(info) equ = get_equipment(equVerison).equ_dict.get(self.id, None) - fusion = get_equipment(equVerison).stone_dict.get(self.fusion, None) + # fusion = get_equipment(equVerison).stone_dict.get(self.fusion, None) if equ is not None and (equ.itemType == part or equ.itemDetailType == part or (equ.itemType == '武器' and part == '副武器')): self.equInfo = equ.adapt(self.adaptation) self.adaptation = min(self.adaptation, self.equInfo.max_adaptation) @@ -46,10 +46,10 @@ class CharacterEquipInfo: self.equInfo.itemType = '副武器' else: self.equInfo = None - if fusion is not None and (fusion.itemType == part or fusion.itemDetailType == part): - self.fusionInfo = fusion.adapt(self.adaptation) - else: - self.fusionInfo = None + # if fusion is not None and (fusion.itemType == part or fusion.itemDetailType == part): + # self.fusionInfo = fusion.adapt(self.adaptation) + # else: + # self.fusionInfo = None class CharacterOathInfo: diff --git a/backend/dataFiles/dcalc_0.db b/backend/dataFiles/dcalc_0.db index 04374e7b..7c990bbb 100644 Binary files a/backend/dataFiles/dcalc_0.db and b/backend/dataFiles/dcalc_0.db differ diff --git a/backend/test.py b/backend/test.py index e50ff94a..e5c9a404 100644 --- a/backend/test.py +++ b/backend/test.py @@ -2,8 +2,29 @@ from core.basic.character import createCharacter char = createCharacter('GF.gunner_female.spitfire_female') -char.calc_init({'oaths': {'0': {'id': '2010201', 'adaptation': 3}, '11': {'id': '2050200', 'adaptation': 0}}, 'equips': {}}) +char.calc_init( + { + 'oaths': {'0': {'id': '2010201', 'adaptation': 3}, '1': {'id': '2010201', 'adaptation': 3}, '2': {'id': '2010201', 'adaptation': 3}, '3': {'id': '2010201', 'adaptation': 3}, '4': {'id': '2010201', 'adaptation': 3}, '11': {'id': '2010200', 'adaptation': 0}}, + 'equips': { + '武器': {'id': '88', 'reinforce': 14, 'reinforceType': 1, 'enchant': 512, 'emblem_0': 0, 'emblem_1': 0, 'fusion': 0, 'refine': 8, 'adaptation': 3, 'precision': 100}, + '称号': {'id': '3017', 'reinforce': 0, 'reinforceType': 0, 'enchant': 1201, 'emblem_0': 0, 'emblem_1': 0, 'fusion': 0, 'refine': 0, 'adaptation': 3, 'precision': 100}, + '上衣': {'id': '999', 'reinforce': 13, 'reinforceType': 1, 'enchant': 105, 'emblem_0': 1, 'emblem_1': 1, 'fusion': '26', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '头肩': {'id': '1001', 'reinforce': 14, 'reinforceType': 1, 'enchant': 5, 'emblem_0': 0, 'emblem_1': 0, 'fusion': '28', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '下装': {'id': '1000', 'reinforce': 14, 'reinforceType': 1, 'enchant': 202, 'emblem_0': 1, 'emblem_1': 1, 'fusion': '27', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '鞋': {'id': '1003', 'reinforce': 14, 'reinforceType': 1, 'enchant': 309, 'emblem_0': 11, 'emblem_1': 11, 'fusion': '30', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '腰带': {'id': '1002', 'reinforce': 14, 'reinforceType': 1, 'enchant': 309, 'emblem_0': 4, 'emblem_1': 4, 'fusion': '29', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '项链': {'id': '477', 'reinforce': 13, 'reinforceType': 1, 'enchant': 609, 'emblem_0': 0, 'emblem_1': 0, 'fusion': '180', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '手镯': {'id': '483', 'reinforce': 14, 'reinforceType': 1, 'enchant': 609, 'emblem_0': 11, 'emblem_1': 11, 'fusion': '302', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '戒指': {'id': '489', 'reinforce': 14, 'reinforceType': 1, 'enchant': 609, 'emblem_0': 4, 'emblem_1': 4, 'fusion': '304', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '辅助装备': {'id': '492', 'reinforce': 14, 'reinforceType': 1, 'enchant': 905, 'emblem_0': '兵器研究', 'emblem_1': 0, 'fusion': '182', 'refine': 3, 'adaptation': 3, 'precision': 100}, + '魔法石': {'id': '495', 'reinforce': 14, 'reinforceType': 1, 'enchant': 1003, 'emblem_0': '兵器研究', 'emblem_1': 0, 'fusion': '184', 'refine': 3, 'adaptation': 3, 'precision': 100}, + '耳环': {'id': '498', 'reinforce': 14, 'reinforceType': 1, 'enchant': 1104, 'emblem_0': 0, 'emblem_1': 0, 'fusion': '186', 'refine': 0, 'adaptation': 3, 'precision': 100}, + '宠物': {'id': '4022', 'reinforce': 0, 'reinforceType': 0, 'enchant': 1304, 'emblem_0': 0, 'emblem_1': 0, 'fusion': 0, 'refine': 0, 'adaptation': 3, 'precision': 100}, + }, + } +) +char.calc_suits() -for oath in char.charOathInfo: - print(oath.id, oath.adaptation, oath.oathInfo.position, oath.oathInfo.max_adaptation, oath.oathInfo.Point) +# for oath in char.charOathInfo: +# print(oath.id, oath.adaptation, oath.oathInfo.position, oath.oathInfo.max_adaptation, oath.oathInfo.Point)