Files
dcalc/backend/core/basic/character/base.py
T

452 lines
15 KiB
Python

from collections.abc import Mapping
from typing import Any, Literal
from core.character.refs.base import SkillRef, resolve_skill
from core.abstract.character import CharacterProperty
from core.basic.skill import Skill
from core.basic.skill_effects import SkillEffectEngine, skill_effect_batch
from core.basic.equipment import EquEffect, OathSuit, OathSuitSkill, Suit
from ..formula import get_base_attributes, 获取唤醒属性
from ..roleinfo import CharacterEmblemInfo, CharacterEquipInfo, get_key_by_value, CharacterOathInfo
from .jade import Jade
ELEMENT_STATUS_KEYS = {
'ElementA',
'ElementDB',
'ElementR',
'火强',
'冰强',
'光强',
'暗强',
'火抗',
'冰抗',
'光抗',
'暗抗',
'火属性攻击',
'冰属性攻击',
'光属性攻击',
'暗属性攻击',
}
class CharacterBase(CharacterProperty):
# region 额外类属性(CharacterProperty 中未声明的)
BindAwake: int
"""绑定觉醒等级"""
skills: list['Skill']
skills_ref_dict: dict[SkillRef, 'Skill']
"""当前角色的技能引用索引;不会共享其他角色的技能实例。"""
skills_code_dict: dict[str, 'Skill']
"""当前角色的原始 code 索引。"""
skill_ref_profession: str | None
"""技能数据来源职业范围,职业变体可复用来源职业。"""
skills_dict: dict[str, 'Skill']
skills_id_dict: dict[str, 'Skill']
skills_uuid_dict: dict[str, 'Skill']
skill_names_cache: dict[tuple[str, bool | str], list[str]]
skills_cache: dict[tuple[str, bool | str], list['Skill']]
equVersion: str = '0'
"""装备版本"""
charEquipInfo: dict[str, 'CharacterEquipInfo']
"""装备打造信息"""
charEmblemInfo: list['CharacterEmblemInfo']
"""徽章打造信息"""
charOathInfo: list['CharacterOathInfo']
"""誓约打造信息"""
charOathSkillId: int
"""誓约技能ID"""
suitInfo: list['Suit']
"""套装信息"""
oathSuitInfo: list['OathSuit']
"""誓约套装信息"""
oathSkillInfo: list['OathSuitSkill']
"""誓约技能信息"""
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.PSTR = 1.0
self.PINT = 1.0
self.Spirit = 0 + 120
self.Vitality = 0 + 120
# 唤醒的情况下的基础数据
self.AtkP = 0 + 65 + 30
self.AtkM = 0 + 65 + 30
self.AtkI = 0 + 65 + 30
self.PAtkP = 1.0
self.PAtkM = 1.0
self.PAtkI = 1.0
self.ElementA = {
'火': False,
'冰': False,
'光': False,
'暗': False,
}
self.ElementDB = {
'火': 13 + 7,
'冰': 13 + 7,
'光': 13 + 7,
'暗': 13 + 7,
}
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_ref_dict = {}
self.skills_code_dict = {}
self.skill_ref_profession = None
self.skills_dict = {}
self.skills_id_dict = {}
self.skills_uuid_dict = {}
self.skill_names_cache = {}
self.skills_cache = {}
self.load_skills()
self.skill_effects = SkillEffectEngine(self)
# region 角色属性设置
def SetBaseStatus(self) -> None:
"""设角色基础属性"""
temp = get_base_attributes(self.role, self.name)
唤醒 = 获取唤醒属性()
for index, i in enumerate([get_key_by_value(i) for i in ['力量', '体力', '智力', '精神']]):
setattr(self, i, getattr(self, i) + temp[index] + 唤醒)
@skill_effect_batch
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:
"""设置角色属性"""
# if kwargs.get('SkillAttack', 0) != 0:
# frame = sys._getframe(1) # 1 表示上一级调用帧
# if 'apply_uppercase_attrs' in frame.f_code.co_name:
# frame = sys._getframe(2) # 2 表示上上级调用帧
# caller_name = frame.f_code.co_name
# print(caller_name, kwargs.get('SkillAttack', 0))
kget = kwargs.get
self.STR += 力量 + kget('STR', 0) + 四维 + 力智
self.INT += 智力 + kget('INT', 0) + 四维 + 力智
self.Spirit += 精神 + kget('Spirit', 0) + 四维 + 体精
self.Vitality += 体力 + kget('Vitality', 0) + 四维 + 体精
self.AtkP += 物攻 + kget('AtkP', 0) + 三攻
self.AtkM += 魔攻 + kget('AtkM', 0) + 三攻
self.AtkI += 独立 + kget('AtkI', 0) + 三攻
self.PAtkP *= kget('PAtkP', 1.0)
self.PAtkM *= kget('PAtkM', 1.0)
self.PAtkI *= kget('PAtkI', 1.0)
if 全属强 or 全属抗 or ELEMENT_STATUS_KEYS.intersection(kwargs):
element_a = kget('ElementA', {})
element_db = kget('ElementDB', {})
element_r = kget('ElementR', {})
for elem in ['火', '冰', '光', '暗']:
self.ElementA[elem] = kget(f'{elem}属性攻击', False) or element_a.get(elem, False) or self.ElementA[elem]
self.ElementDB[elem] += kget(f'{elem}强', 0) + element_db.get(elem, 0) + 全属强
self.ElementR[elem] += kget(f'{elem}抗', 0) + element_r.get(elem, 0) + 全属抗
self.CriticalM += kget('CriticalM', 0) + 魔暴
self.CriticalP += kget('CriticalP', 0) + 物暴
self.SkillAttack *= (1 + round(技攻,6)) * (1 +round( kget('SkillAttack', 0),6))
self.SpeedA += 攻击速度 + kget('SpeedA', 0) + 三速
self.SpeedM += 移动速度 + kget('SpeedM', 0) + 三速
self.SpeedR += 施放速度 + kget('SpeedR', 0) + 三速
self.HitP += 命中率 + kget('HitP', 0)
self.Hit += 命中 + kget('Hit', 0)
self.Attack += 攻击强化 + kget('Attack', 0)
self.AttackP += 攻击强化P + kget('AttackP', 0.0)
self.Buffer += 增益量 + kget('Buffer', 0)
self.BufferP += 增益量P + kget('BufferP', 0)
self.EquEffectRatio += kget('EquEffectRatio', 0.0)
self.PINT *= kget('PINT', 1.0)
self.PSTR *= kget('PSTR', 1.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
@skill_effect_batch
def AddSkillLv(self, min: int, max: int, lv: int, type=-1, exceptSkills: tuple[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
@skill_effect_batch
def SetSkillCD(self, min=1, max=100, cd=0, exclude: tuple[int, ...] = (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
@skill_effect_batch
def SetSkillCDRecover(self, min=1, max=100, cd=0, exclude: tuple[int, ...] = (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
@skill_effect_batch
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 GetSkill(self, reference: SkillRef | str) -> 'Skill | None':
"""按类型化引用查询当前角色技能,兼容旧显示名称字符串。"""
return resolve_skill(self, reference)
def GetSkillByRef(self, reference: SkillRef) -> 'Skill | None':
"""按技能身份查询;校验职业范围,缺失返回 None。"""
if not isinstance(reference, SkillRef):
raise TypeError('GetSkillByRef 必须传入 SkillRef')
return resolve_skill(self, reference)
def GetSkillByCode(self, code: str) -> 'Skill | None':
"""在当前角色中按原始 code 查询,不与显示名称隐式混用。"""
return self.skills_code_dict.get(code)
def GetSkillByName(self, name) -> 'Skill':
"""通过技能名获取技能"""
return self.skills_dict.get(name, None)
def GetSkillByID(self, id: str) -> 'Skill':
"""通过技能ID获取技能"""
return self.skills_id_dict.get(str(id), None)
def GetSkillByUUID(self, uuid: str) -> 'Skill':
"""通过技能UUID获取技能"""
return self.skills_uuid_dict.get(str(uuid), 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.GetSkills(type, damage)]
def GetSkills(self, type: Literal['active', 'passive', 'all'] = 'all', damage: Literal[True, False, 'all'] = 'all') -> list['Skill']:
"""获取技能对象"""
key = (type, damage)
if key not in self.skills_cache:
self.skills_cache[key] = [skill for skill in self.skills if (type == 'all' or skill.type == type) and (damage == 'all' or skill.damage == damage)]
return self.skills_cache[key]
def GetWeaponType(self) -> tuple[str, str]:
"""获取武器类型"""
weapon = self.charEquipInfo['武器'].equInfo
if weapon is None:
return None, None
else:
return weapon.itemDetailType, weapon.categorize
# endregion
def GetReinforceSum(self) -> list[int]:
"""获取强化总和"""
reinforce_0 = 0
"""防具"""
reinforce_1 = 0
"""首饰"""
reinforce_2 = 0
"""特殊装备"""
reinforce_3 = 0
"""武器"""
for part in self.charEquipInfo.keys():
if self.charEquipInfo[part] is None:
continue
num = self.charEquipInfo[part].reinforce
# if part == '武器':
# reinforce_3 += num
if part in ['上衣', '头肩', '下装', '腰带', '鞋']:
reinforce_0 += num
if part in ['手镯', '项链', '戒指']:
reinforce_1 += num
if part in ['耳环', '辅助装备', '魔法石']:
reinforce_2 += num
reinforce = reinforce_0 + reinforce_1 + reinforce_2 + reinforce_3
return [reinforce, reinforce_0, reinforce_1, reinforce_2]