618 lines
23 KiB
Python
618 lines
23 KiB
Python
from copy import copy
|
||
from collections.abc import Callable
|
||
from functools import cache
|
||
import json
|
||
import re
|
||
from typing import Any, Literal, TYPE_CHECKING
|
||
from core.character.refs.base import SkillRef, resolve_skill
|
||
from .formula import 武器冷却惩罚
|
||
from database.connect import get_redis_client
|
||
from api.core.Redis import get_redis_info
|
||
from api.utils.openapi_data import OPENAPI_CUSTOM_CN_PATH, logical_skill_detail_path
|
||
from .skill_effects import CHARACTER_ATTRIBUTES, INPUT_ATTRIBUTES
|
||
from .skill_effect_types import CharacterEffect, EffectDeclaration, SkillEffect
|
||
|
||
if TYPE_CHECKING:
|
||
from core.basic.character import Character
|
||
|
||
characterLv = 115 + 5
|
||
|
||
|
||
def _identity(value: Any) -> Any:
|
||
return value
|
||
|
||
|
||
@cache
|
||
def _load_custom_cn() -> list[dict[str, Any]]:
|
||
with OPENAPI_CUSTOM_CN_PATH.open(encoding='utf-8') as f:
|
||
return json.load(f)
|
||
|
||
|
||
@cache
|
||
def _load_skill_detail(prefix: str) -> dict[str, Any]:
|
||
with logical_skill_detail_path(prefix).open(encoding='utf-8') as f:
|
||
return json.load(f)
|
||
|
||
|
||
@cache
|
||
def _get_skill_data_suffixes(skill_class: type) -> tuple[str, ...]:
|
||
return tuple(key.replace('data', '') for key in dir(skill_class) if key.startswith('data') and not key.startswith('plus'))
|
||
|
||
|
||
@cache
|
||
def _get_skill_damage_suffixes(skill_class: type) -> tuple[str, ...]:
|
||
return tuple(key.replace('data', '') for key in dir(skill_class) if key.startswith('data') and not key.startswith('dataplus') and not key.startswith('dataType'))
|
||
|
||
|
||
@cache
|
||
def _get_skill_dataplus_suffixes(skill_class: type) -> tuple[str, ...]:
|
||
return tuple(key.replace('dataplus', '') for key in dir(skill_class) if key.startswith('dataplus'))
|
||
|
||
|
||
@cache
|
||
def get_data(prefix: str, key: int | str, func: Callable[[Any], Any] = _identity):
|
||
prefix_parts = prefix.split('/')
|
||
job = prefix_parts[0]
|
||
jobGrow = prefix_parts[1]
|
||
skill = prefix_parts[-1]
|
||
|
||
def get_skill_info():
|
||
skill_data = _load_skill_detail(prefix)
|
||
custom_cn = get_redis_info(redis, 'dcalc:custom_cn', _load_custom_cn, without_gzip=True)
|
||
try:
|
||
if isinstance(key, int):
|
||
data = [0] + [row["optionValue"].get("value" + str(key+1), 0) for row in skill_data["levelInfo"]["rows"]]
|
||
data = [0 if x is None or x == "-" else x for x in data]
|
||
elif key == "vps":
|
||
data = [ {"name":x["name"],"desc":x["desc"]} for x in skill_data.get("evolution",[])]
|
||
elif key == "custom":
|
||
data = next((item.get('custom', 1.0) for item in custom_cn or []
|
||
if item['job'] == job and item['jobGrow'] == jobGrow and item['skillId'] == skill), 1.0)
|
||
elif key == "cdList":
|
||
data = [0] + [row.get("coolTime", 0) for row in skill_data["levelInfo"]["rows"]]
|
||
except Exception:
|
||
# print(f"get skill {prefix}/{key} data error")
|
||
data = None
|
||
if isinstance(key, int) and not data:
|
||
return [0]
|
||
if key == 'vps' and data is None:
|
||
return []
|
||
if key == 'custom' and data is None:
|
||
return 1.0
|
||
return data
|
||
|
||
with get_redis_client() as redis:
|
||
data = get_redis_info(redis, f'dcalc:skill:{prefix}:{key}', get_skill_info, without_gzip=True)
|
||
return func(data)
|
||
|
||
|
||
class Skill:
|
||
def __setattr__(self, name, value):
|
||
char = self.__dict__.get('char')
|
||
engine = getattr(char, 'skill_effects', None)
|
||
if name != 'lv' and name in INPUT_ATTRIBUTES and engine is not None and engine.owns(self) and not engine.resolving:
|
||
engine.set_attribute(self, name, value)
|
||
else:
|
||
object.__setattr__(self, name, value)
|
||
|
||
name: str = ''
|
||
"""技能名称"""
|
||
_lv: int = 0
|
||
ref: SkillRef | None = None
|
||
"""技能身份引用;职业类绑定 Ref 成员,code 由此派生。"""
|
||
|
||
@property
|
||
def code(self) -> str | None:
|
||
"""技能树原始 code;未迁移的技能返回 None,不单独维护副本。"""
|
||
return self.ref.code if self.ref is not None else None
|
||
|
||
level_source: SkillRef | str | None = None
|
||
"""共享等级的源技能;目标不独立加级,也不使用自身 maxLv 截断。"""
|
||
association_source: bool | None = None
|
||
"""共享等级技能默认仅复用等级;需要独立提供关联效果时显式开启。"""
|
||
id: int = 0
|
||
"""技能ID"""
|
||
icon: str = '$char/$uuid'
|
||
"""技能图标,默认当前职业下该技能名称对应的图标,支持以下模板字符串
|
||
$common 通用图标路径
|
||
$char 当前职业图标路径
|
||
$name 技能名称
|
||
$id 技能ID
|
||
"""
|
||
type: Literal['active', 'passive'] = 'active'
|
||
"""技能类型 active主动 passive被动"""
|
||
learnLv: int = 0
|
||
"""学习等级"""
|
||
masterLv: int = 0
|
||
"""精通等级"""
|
||
maxLv: int = 0
|
||
"""最大等级"""
|
||
line: int = None
|
||
"""技能行"""
|
||
position: int = 0
|
||
"""技能树位置"""
|
||
rangeLv: int = 1
|
||
"""学习间隔"""
|
||
cube: int = 0
|
||
"""无色消耗"""
|
||
skillRation: int = 1
|
||
"""技能面板倍率 [改变技能面板]"""
|
||
skillDamage: int = 1
|
||
"""技能技攻 [不改变技能面板]"""
|
||
char: 'Character' = None
|
||
"""技能所属角色"""
|
||
damage: bool = False
|
||
"""是否是伤害技能"""
|
||
cd: float = 0
|
||
"""技能冷却时间"""
|
||
cdCut: float = 0
|
||
"""技能冷却时间直接减少"""
|
||
cdReduce: float = 1
|
||
"""技能冷却缩减"""
|
||
cdRatio: float = 1
|
||
"""技能冷却倍率,直接体现在面板上,不被上限影响"""
|
||
cdRecover: float = 1
|
||
"""技能冷却恢复"""
|
||
associate: list[dict[str, Any] | EffectDeclaration] = []
|
||
"""关联声明;维护示例和扩展边界见 core/basic/skill_effects.py 文件顶部说明。
|
||
|
||
新规则使用 SkillEffect/CharacterEffect 和 Ref 目标数组,兼容旧字典及名称字符串。
|
||
新声明 value 是实际倍率/增量/覆盖值,以下 type、data、ratio 约定仅适用于旧字典。
|
||
type 默认 '*skillDamage':*乘算、+加算、=覆盖;$*PAtkP 等作用于角色。
|
||
skills 默认 '*',表示所有伤害技能;显式列表使用技能名称,可指定被动技能。
|
||
exceptSkills 默认 [];weapon 默认职业武器选项;precondition() 控制前置条件。
|
||
data 按来源等级取值;ratio 是数据除数,默认 100,与技能的 ratio 属性不同。
|
||
+lv 的整数加级通常设置 ratio=1;=覆盖直接使用 data 值,不除以 ratio。
|
||
动态 effect 以 (0, 最终等级) 多次收集,只构造声明并调用 super().effect。
|
||
自定义 type 方法仍有兼容入口,但直接修改目标会绕过贡献收集,新增规则勿照搬。
|
||
"""
|
||
mode: list[str] = ['']
|
||
currentMode: str = ''
|
||
"""技能模式"""
|
||
buffer: bool = False
|
||
"""是否是增益技能"""
|
||
bind: bool = None
|
||
uuid: str = None
|
||
hasVP: bool = None
|
||
"""是否有VP形态"""
|
||
hasUP: bool = None
|
||
"""是否有技能强化"""
|
||
upType: Literal['damage', 'buff', 'heal'] = 'damage'
|
||
vps: list = []
|
||
custom: float = 1.0
|
||
"""国服特色加强"""
|
||
|
||
def __init__(self, char):
|
||
self.char = char
|
||
# Shared-level display skills override the setter with a no-op. They
|
||
# did not apply their own associations in the original calculation.
|
||
if self.association_source is None:
|
||
self.association_source = self.level_source is None and type(self).lv.fset is Skill.lv.fset
|
||
self.id = self._extract_id()
|
||
self.icon = self._generate_icon()
|
||
if self.line is None:
|
||
self.line = self.learnLv
|
||
if self.bind is None:
|
||
self.bind = False
|
||
if self.learnLv in [50, 85, 100]:
|
||
self.bind = True
|
||
if self.hasVP is None:
|
||
if self.learnLv not in [50, 85, 100] and self.learnLv >= 35 and self.learnLv <= 80 and self.damage:
|
||
self.hasVP = True
|
||
else:
|
||
self.hasVP = False
|
||
if self.hasUP is None:
|
||
if self.learnLv not in [50, 85, 100] and self.learnLv >= 15 and self.learnLv <= 80 and self.damage:
|
||
self.hasUP = True
|
||
else:
|
||
self.hasUP = False
|
||
# self.lv = self._calculate_lv()
|
||
pass
|
||
|
||
def _extract_id(self):
|
||
className = self.__class__.__name__
|
||
return int(re.search(r'\d+', className).group()) if re.search(r'\d+', className) else 0
|
||
|
||
def _generate_icon(self):
|
||
icon = self.icon or '$char/$uuid'
|
||
icon = icon.replace('$char', f'/characters/{self.char.role}/{self.char.name}/skillCN')
|
||
icon = icon.replace('$common', '/characters/common')
|
||
icon = icon.replace('$name', f'{self.name}')
|
||
icon = icon.replace('$uuid', f'{self.uuid}')
|
||
icon = icon.replace('$id', f'{self.id}')
|
||
return f'{icon}.png'
|
||
|
||
def calculate_lv(self):
|
||
return min(int((characterLv - (15 if self.learnLv is None else self.learnLv)) / max(1,(1 if self.rangeLv is None else self.rangeLv)) + 1), self.masterLv) if self._lv == 0 else self._lv
|
||
|
||
@property
|
||
def lv(self):
|
||
"""技能等级"""
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.owns(self):
|
||
engine.read_level(self)
|
||
elif self.level_source is not None:
|
||
return resolve_skill(self.char, self.level_source).lv
|
||
return self._lv
|
||
|
||
@lv.setter
|
||
def lv(self, value):
|
||
if self.level_source is not None:
|
||
return
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.owns(self) and not engine.resolving:
|
||
engine.set_level(self, max(0, int(value)))
|
||
return
|
||
value = int(max(0, min(self.maxLv, value)))
|
||
if self._lv != value:
|
||
self.effect(self._lv, value)
|
||
self._lv = value
|
||
|
||
def effect(self, old: int, new: int):
|
||
weapon_type = self.char.GetWeaponType()[0]
|
||
weapon_options = self.char.武器选项
|
||
for assoc in self.associate:
|
||
if isinstance(assoc, (SkillEffect, CharacterEffect)):
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is None:
|
||
raise RuntimeError('类型化效果需要 SkillEffectEngine')
|
||
engine.collect_effect(self, assoc)
|
||
continue
|
||
type = assoc.get('type', '*skillDamage')
|
||
skills = assoc.get('skills', '*')
|
||
data = assoc.get('data', [0] * self.maxLv)
|
||
ratio = assoc.get('ratio', 100)
|
||
weapon = assoc.get('weapon', weapon_options)
|
||
exceptSkills = assoc.get('exceptSkills', [])
|
||
if type[0] not in ['*', '+', '=', '$']:
|
||
if self.precondition() and (weapon_type in weapon or len(weapon) == len(weapon_options)):
|
||
getattr(self, type)(old, new, data, skills, exceptSkills, ratio, weapon)
|
||
continue
|
||
if self.precondition():
|
||
self.apply_association(type, old, new, data, skills, exceptSkills, ratio, weapon, weapon_type, weapon_options)
|
||
|
||
def precondition(self):
|
||
"""effect的前置条件"""
|
||
return True
|
||
|
||
def apply_association(self, type, old, new, data, skills, exceptSkills, ratio=100, weapon=(), weapon_type=None, weapon_options=()):
|
||
try:
|
||
if (weapon_type not in weapon) and not (weapon_type is None and len(weapon) == len(weapon_options)):
|
||
return
|
||
if type.startswith('$*'):
|
||
if 'cdReduce' in type:
|
||
value = (1 - self.get_data_value(data, new) / ratio) / (1 - self.get_data_value(data, old) / ratio)
|
||
else:
|
||
value = (1 + self.get_data_value(data, new) / ratio) / (1 + self.get_data_value(data, old) / ratio)
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.collecting is self:
|
||
if type[2:] in CHARACTER_ATTRIBUTES:
|
||
engine.record(self, self.char, type[2:], '*', value)
|
||
else:
|
||
self.char.SetStatus(**{type[2:]: value})
|
||
elif type.startswith('$+'):
|
||
value = self.get_data_value(data, new) / ratio - self.get_data_value(data, old) / ratio
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.collecting is self:
|
||
if type[2:] in CHARACTER_ATTRIBUTES:
|
||
engine.record(self, self.char, type[2:], '+', value)
|
||
else:
|
||
self.char.SetStatus(**{type[2:]: value})
|
||
else:
|
||
exceptSkills = set(exceptSkills)
|
||
if skills == '*':
|
||
for skill in self.char.GetSkills('all', True):
|
||
if skill.name in exceptSkills:
|
||
continue
|
||
self.update_skill_attribute(skill, type, old, new, data, ratio, weapon, weapon_type, weapon_options)
|
||
else:
|
||
for name in skills:
|
||
if name in exceptSkills:
|
||
continue
|
||
skill = self.char.GetSkillByName(name)
|
||
if skill is not None:
|
||
self.update_skill_attribute(skill, type, old, new, data, ratio, weapon, weapon_type, weapon_options)
|
||
else:
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.collecting is self:
|
||
engine.missing_target(self, name)
|
||
except Exception as e:
|
||
# print(f"Error applying association {type} for skill {self.name}: {e}")
|
||
raise e
|
||
|
||
def update_skill_attribute(self, skill, type, old, new, data, ratio=100, weapon=(), weapon_type=None, weapon_options=()):
|
||
if (weapon_type not in weapon) and not (weapon_type is None and len(weapon) == len(weapon_options)):
|
||
return
|
||
old_value = self.get_data_value(data, old)
|
||
new_value = self.get_data_value(data, new)
|
||
engine = getattr(self.char, 'skill_effects', None)
|
||
if engine is not None and engine.collecting is self:
|
||
if type[1:] == 'lv' and skill.level_source is not None:
|
||
return
|
||
if type.startswith('*'):
|
||
if 'cdReduce' in type or 'cdRatio' in type:
|
||
value = (1 - new_value / ratio) / (1 - old_value / ratio)
|
||
else:
|
||
value = (1 + new_value / ratio) / (1 + old_value / ratio)
|
||
elif type.startswith('+'):
|
||
if type[1:] == 'lv' and not engine.is_learned(skill):
|
||
return
|
||
value = (new_value - old_value) / ratio
|
||
else:
|
||
value = new_value
|
||
engine.record(self, skill, type[1:], type[0], value)
|
||
return
|
||
if type.startswith('*'):
|
||
if 'cdReduce' in type or type == 'cd' or 'cdRatio' in type:
|
||
setattr(skill, type[1:], getattr(skill, type[1:]) * (1 - new_value / ratio) / (1 - old_value / ratio))
|
||
else:
|
||
setattr(skill, type[1:], getattr(skill, type[1:]) * (1 + new_value / ratio) / (1 + old_value / ratio))
|
||
elif type.startswith('+'):
|
||
if type[1:] == 'lv' and self.char.setInfo.get('skills', {}).get(str(skill.id),{}).get('lv',0) == 0:
|
||
return
|
||
else:
|
||
setattr(skill, type[1:], getattr(skill, type[1:]) + new_value / ratio - old_value / ratio)
|
||
elif type.startswith('='):
|
||
setattr(skill, type[1:], new_value)
|
||
|
||
@staticmethod
|
||
def get_data_value(data, index: int):
|
||
if 0 <= index < len(data):
|
||
return data[index]
|
||
return 0
|
||
|
||
def skillInfo(self, mode: str | None = None):
|
||
pass
|
||
|
||
|
||
class ActiveSkill(Skill):
|
||
type: str = 'active'
|
||
"""技能类型 active主动 passive被动"""
|
||
damage: bool = True
|
||
"""是否是伤害技能"""
|
||
buffer: bool = False
|
||
"""是否是buff技能"""
|
||
vp: int = 0
|
||
"""技能VP形态"""
|
||
up: int = 0
|
||
"""技能强化"""
|
||
|
||
def __init__(self, char):
|
||
super().__init__(char)
|
||
for i in _get_skill_data_suffixes(self.__class__):
|
||
power = getattr(self, f'power{i}', None)
|
||
if power is None:
|
||
setattr(self, f'power{i}', 1)
|
||
hit = getattr(self, f'hit{i}', None)
|
||
if hit is None:
|
||
setattr(self, f'hit{i}', 0)
|
||
|
||
def skillInfo(self, mode: str | None = None):
|
||
basic = copy(self)
|
||
for key, value in self.__dict__.items():
|
||
if isinstance(value, list):
|
||
setattr(basic, key, value.copy())
|
||
elif isinstance(value, dict):
|
||
setattr(basic, key, value.copy())
|
||
basic.setUP()
|
||
if mode is not None:
|
||
basic.currentMode = mode
|
||
basic.setMode(mode)
|
||
basic.setVP()
|
||
date = basic.getSkillData(self.lv)
|
||
cd = basic.getSkillCD(mode)
|
||
return date * basic.skillRation * basic.custom, basic.skillDamage, cd
|
||
|
||
def setMode(self, mode: str):
|
||
pass
|
||
|
||
def setVP(self):
|
||
if self.vp == 1:
|
||
self.vp_1()
|
||
elif self.vp == 2:
|
||
self.vp_2()
|
||
|
||
def setUP(self):
|
||
if not self.hasUP:
|
||
return
|
||
if self.up == 1:
|
||
if self.learnLv < 35:
|
||
self.skillRation *= 1.6
|
||
else:
|
||
self.skillRation *= 1.55
|
||
elif self.up == 2:
|
||
self.cdReduce *= 1 - 0.15
|
||
if self.learnLv < 35:
|
||
self.skillRation *= 1.43
|
||
else:
|
||
self.skillRation *= 1.38
|
||
|
||
def getSkillData(self, lv: int = 0):
|
||
res = 0
|
||
for i in _get_skill_damage_suffixes(self.__class__):
|
||
data = getattr(self, f'data{i}', [])
|
||
plus = getattr(self, f'plus{i}', 0)
|
||
hit = getattr(self, f'hit{i}', 0)
|
||
if len(data) == 0 or hit == 0:
|
||
continue
|
||
if lv < len(data):
|
||
hit = getattr(self, f'hit{i}', 0)
|
||
power = getattr(self, f'power{i}', 1)
|
||
res += hit * power * (data[lv] + plus)
|
||
for i in _get_skill_dataplus_suffixes(self.__class__):
|
||
data = getattr(self, f'dataplus{i}', 0)
|
||
hit = getattr(self, f'hitplus{i}', 1)
|
||
power = getattr(self, f'powerplus{i}', 1)
|
||
res += hit * power * data
|
||
return res
|
||
|
||
def getWeaponCDRatio(self):
|
||
weapon = self.char.charEquipInfo['武器'].equInfo
|
||
if weapon is None or '传世武器' in weapon.categorize:
|
||
return 1
|
||
else:
|
||
return 武器冷却惩罚(weapon.itemDetailType, self.char.输出类型)
|
||
|
||
def getQuickCDRatio(self):
|
||
return 1.0
|
||
cdr = 0
|
||
if 15 <= self.learnLv <= 30:
|
||
cdr = 0.01
|
||
elif 35 <= self.learnLv <= 70:
|
||
cdr = 0.02
|
||
elif 75 <= self.learnLv <= 100:
|
||
cdr = 0.05
|
||
if self.learnLv in [50, 85, 100]:
|
||
cdr = 0.05
|
||
return 1 - cdr
|
||
|
||
def getSkillCD(self, mode=None):
|
||
if not isinstance(self.cd, (int, float)) or not self.cd:
|
||
return '-'
|
||
return max(0, round(max(self.cd * 0.3, self.cd * self.cdReduce / self.cdRecover * self.getWeaponCDRatio() * self.getQuickCDRatio()) * self.cdRatio - self.cdCut / self.cdRecover , 2))
|
||
|
||
def vp_1(self):
|
||
pass
|
||
|
||
def vp_2(self):
|
||
pass
|
||
|
||
|
||
class PassiveSkill(Skill):
|
||
type: str = 'passive'
|
||
"""技能类型 active主动 passive被动"""
|
||
damage: bool = False
|
||
"""是否是伤害技能"""
|
||
buffer: bool = False
|
||
"""是否是buff技能"""
|
||
|
||
|
||
class BuffSkill(Skill):
|
||
buffer: bool = True
|
||
"""是否是增益技能"""
|
||
damage: bool = False
|
||
"""是否是伤害技能"""
|
||
buffType = 'normal'
|
||
"""增益类型 normal普通 buff 主BUFF awake 觉醒技能"""
|
||
|
||
INT: list[float] = []
|
||
"""智力"""
|
||
STR: list[float] = []
|
||
"""力量"""
|
||
Spirit: list[float] = []
|
||
"""精神"""
|
||
Vitality: list[float] = []
|
||
"""体力"""
|
||
"""主要属性"""
|
||
|
||
ATK: list[float] = []
|
||
"""三攻加成"""
|
||
ATKRatio: float = 1
|
||
"""三攻*算倍率"""
|
||
ATKPLUS: float = 0
|
||
"""三攻加算加成"""
|
||
|
||
STRINT: list[float] = []
|
||
"""力量智力加成"""
|
||
STRINTRatio: float = 1
|
||
"""力量智力*算倍率"""
|
||
STRINTPLUS: float = 0
|
||
"""力量智力加算加成"""
|
||
|
||
CarryRatio: list[float] = []
|
||
"""对C的额外增伤倍率"""
|
||
vp: int = 0
|
||
"""技能VP形态"""
|
||
up: int = 0
|
||
"""技能强化"""
|
||
|
||
def __init__(self, char):
|
||
super().__init__(char)
|
||
for i in _get_skill_data_suffixes(self.__class__):
|
||
power = getattr(self, f'power{i}', None)
|
||
if power is None:
|
||
setattr(self, f'power{i}', 1)
|
||
hit = getattr(self, f'hit{i}', None)
|
||
if hit is None:
|
||
setattr(self, f'hit{i}', 0)
|
||
|
||
def getSkillCD(self, mode=None):
|
||
return '-'
|
||
|
||
def skillInfo(self, mode: str | None = None):
|
||
"""
|
||
Returns:
|
||
tuple: (奶自己的面板加成,[三攻,三攻倍率,三攻额外加成],[力智,力智倍率,力智额外加成],对C增伤倍率,cd)
|
||
"""
|
||
lv = self.lv
|
||
value = 0
|
||
value1 = 0 if lv >= len(self.ATK) else self.ATK[lv]
|
||
value2 = 0 if lv >= len(self.STRINT) else self.STRINT[lv]
|
||
value3 = 0 if lv >= len(self.CarryRatio) else self.CarryRatio[lv]
|
||
self.setUP()
|
||
self.setVP()
|
||
if self.char.适用属性 == '智力':
|
||
value = 0 if lv >= len(self.INT) else self.INT[lv]
|
||
elif self.char.适用属性 == '力量':
|
||
value = 0 if lv >= len(self.STR) else self.STR[lv]
|
||
elif self.char.适用属性 == '体力':
|
||
value = 0 if lv >= len(self.Vitality) else self.Vitality[lv]
|
||
elif self.char.适用属性 == '精神':
|
||
value = 0 if lv >= len(self.Spirit) else self.Spirit[lv]
|
||
return value, [value1, self.ATKRatio, self.ATKPLUS], [value2, self.STRINTRatio, self.STRINTPLUS], value3, self.getSkillCD(mode)
|
||
|
||
def setVP(self):
|
||
if self.vp == 1:
|
||
self.vp_1()
|
||
elif self.vp == 2:
|
||
self.vp_2()
|
||
|
||
def setUP(self):
|
||
if not self.hasUP:
|
||
return
|
||
self.char.SetStatus(四维=40)
|
||
if self.up == 1:
|
||
...
|
||
elif self.up == 2:
|
||
if self.upType == 'damage':
|
||
self.cdReduce *= 1 - 0.15
|
||
elif self.upType == 'heal':
|
||
self.cdReduce *= 1 - 0.1
|
||
|
||
def vp_1(self):
|
||
pass
|
||
|
||
def vp_2(self):
|
||
pass
|
||
|
||
|
||
class PassiveBufferSkill(BuffSkill):
|
||
type: str = 'passive'
|
||
|
||
|
||
class ActiveBufferSkill(BuffSkill):
|
||
type: str = 'active'
|
||
|
||
def getWeaponCDRatio(self):
|
||
weapon = self.char.charEquipInfo['武器'].equInfo
|
||
if weapon is None or '传世武器' in weapon.categorize:
|
||
return 1
|
||
else:
|
||
return 武器冷却惩罚(weapon.itemDetailType, self.char.输出类型)
|
||
|
||
def getQuickCDRatio(self):
|
||
return 1.0
|
||
cdr = 0
|
||
if 15 <= self.learnLv <= 30:
|
||
cdr = 0.01
|
||
elif 35 <= self.learnLv <= 70:
|
||
cdr = 0.02
|
||
elif 75 <= self.learnLv <= 100:
|
||
cdr = 0.05
|
||
if self.learnLv in [50, 85, 100]:
|
||
cdr = 0.05
|
||
return 1 - cdr
|
||
|
||
def getSkillCD(self, mode=None):
|
||
return max(0, round(max(self.cd * 0.3, self.cd * self.cdReduce / self.cdRecover * self.getWeaponCDRatio() * self.getQuickCDRatio(), 1) - self.cdCut, 2))
|