diff --git a/model/damage/BuffManager.js b/model/damage/BuffManager.js index c50b566..223de00 100644 --- a/model/damage/BuffManager.js +++ b/model/damage/BuffManager.js @@ -22,26 +22,27 @@ export var buffTypeEnum; (function (buffTypeEnum) { // 通用乘区 buffTypeEnum[buffTypeEnum["\u653B\u51FB\u529B"] = 0] = "\u653B\u51FB\u529B"; - buffTypeEnum[buffTypeEnum["\u589E\u4F24"] = 1] = "\u589E\u4F24"; - buffTypeEnum[buffTypeEnum["\u6613\u4F24"] = 2] = "\u6613\u4F24"; - buffTypeEnum[buffTypeEnum["\u65E0\u89C6\u6297\u6027"] = 3] = "\u65E0\u89C6\u6297\u6027"; - buffTypeEnum[buffTypeEnum["\u65E0\u89C6\u9632\u5FA1"] = 4] = "\u65E0\u89C6\u9632\u5FA1"; - buffTypeEnum[buffTypeEnum["\u7A7F\u900F\u503C"] = 5] = "\u7A7F\u900F\u503C"; - buffTypeEnum[buffTypeEnum["\u7A7F\u900F\u7387"] = 6] = "\u7A7F\u900F\u7387"; + buffTypeEnum[buffTypeEnum["\u500D\u7387"] = 1] = "\u500D\u7387"; + buffTypeEnum[buffTypeEnum["\u589E\u4F24"] = 2] = "\u589E\u4F24"; + buffTypeEnum[buffTypeEnum["\u6613\u4F24"] = 3] = "\u6613\u4F24"; + buffTypeEnum[buffTypeEnum["\u65E0\u89C6\u6297\u6027"] = 4] = "\u65E0\u89C6\u6297\u6027"; + buffTypeEnum[buffTypeEnum["\u65E0\u89C6\u9632\u5FA1"] = 5] = "\u65E0\u89C6\u9632\u5FA1"; + buffTypeEnum[buffTypeEnum["\u7A7F\u900F\u503C"] = 6] = "\u7A7F\u900F\u503C"; + buffTypeEnum[buffTypeEnum["\u7A7F\u900F\u7387"] = 7] = "\u7A7F\u900F\u7387"; // 直伤乘区 - buffTypeEnum[buffTypeEnum["\u66B4\u51FB\u7387"] = 7] = "\u66B4\u51FB\u7387"; - buffTypeEnum[buffTypeEnum["\u66B4\u51FB\u4F24\u5BB3"] = 8] = "\u66B4\u51FB\u4F24\u5BB3"; + buffTypeEnum[buffTypeEnum["\u66B4\u51FB\u7387"] = 8] = "\u66B4\u51FB\u7387"; + buffTypeEnum[buffTypeEnum["\u66B4\u51FB\u4F24\u5BB3"] = 9] = "\u66B4\u51FB\u4F24\u5BB3"; // 异常乘区 - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u7CBE\u901A"] = 9] = "\u5F02\u5E38\u7CBE\u901A"; - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u589E\u4F24"] = 10] = "\u5F02\u5E38\u589E\u4F24"; - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u66B4\u51FB\u7387"] = 11] = "\u5F02\u5E38\u66B4\u51FB\u7387"; - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u66B4\u51FB\u4F24\u5BB3"] = 12] = "\u5F02\u5E38\u66B4\u51FB\u4F24\u5BB3"; - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u6301\u7EED\u65F6\u95F4"] = 13] = "\u5F02\u5E38\u6301\u7EED\u65F6\u95F4"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u7CBE\u901A"] = 10] = "\u5F02\u5E38\u7CBE\u901A"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u589E\u4F24"] = 11] = "\u5F02\u5E38\u589E\u4F24"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u66B4\u51FB\u7387"] = 12] = "\u5F02\u5E38\u66B4\u51FB\u7387"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u66B4\u51FB\u4F24\u5BB3"] = 13] = "\u5F02\u5E38\u66B4\u51FB\u4F24\u5BB3"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u6301\u7EED\u65F6\u95F4"] = 14] = "\u5F02\u5E38\u6301\u7EED\u65F6\u95F4"; // 其他属性,一般不直接影响伤害,但可能用于buff是否生效判断/转模 - buffTypeEnum[buffTypeEnum["\u751F\u547D\u503C"] = 14] = "\u751F\u547D\u503C"; - buffTypeEnum[buffTypeEnum["\u9632\u5FA1\u529B"] = 15] = "\u9632\u5FA1\u529B"; - buffTypeEnum[buffTypeEnum["\u51B2\u51FB\u529B"] = 16] = "\u51B2\u51FB\u529B"; - buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u638C\u63A7"] = 17] = "\u5F02\u5E38\u638C\u63A7"; + buffTypeEnum[buffTypeEnum["\u751F\u547D\u503C"] = 15] = "\u751F\u547D\u503C"; + buffTypeEnum[buffTypeEnum["\u9632\u5FA1\u529B"] = 16] = "\u9632\u5FA1\u529B"; + buffTypeEnum[buffTypeEnum["\u51B2\u51FB\u529B"] = 17] = "\u51B2\u51FB\u529B"; + buffTypeEnum[buffTypeEnum["\u5F02\u5E38\u638C\u63A7"] = 18] = "\u5F02\u5E38\u638C\u63A7"; })(buffTypeEnum || (buffTypeEnum = {})); let depth = 0, weakMapCheck = new WeakMap(); /** @@ -116,8 +117,8 @@ export class BuffManager { param.range = param.range.filter(r => typeof r === 'string'); if (!param.range.length) continue; - // buff作用范围向后覆盖 - else if (!param.range.every(ST => buffRange.some(BT => ST.startsWith(BT)))) + // buff作用范围向后覆盖,满足伤害类型range中任意一个即可 + else if (!param.range.some(ST => buffRange.some(BT => ST.startsWith(BT)))) return false; else continue; diff --git a/model/damage/BuffManager.ts b/model/damage/BuffManager.ts index ce4ad9c..2a7dca2 100644 --- a/model/damage/BuffManager.ts +++ b/model/damage/BuffManager.ts @@ -31,7 +31,7 @@ export type buffSource = 'Weapon' | 'Set' | 'Rank' | 'Talent' | 'Addition' | 'Sk export enum buffTypeEnum { // 通用乘区 - 攻击力, 增伤, 易伤, 无视抗性, 无视防御, 穿透值, 穿透率, + 攻击力, 倍率, 增伤, 易伤, 无视抗性, 无视防御, 穿透值, 穿透率, // 直伤乘区 暴击率, 暴击伤害, // 异常乘区 @@ -172,8 +172,8 @@ export class BuffManager { if (!buffRange || !param.range) continue // 对任意类型生效 param.range = param.range.filter(r => typeof r === 'string') if (!param.range.length) continue - // buff作用范围向后覆盖 - else if (!param.range.every(ST => buffRange.some(BT => ST.startsWith(BT)))) return false + // buff作用范围向后覆盖,满足伤害类型range中任意一个即可 + else if (!param.range.some(ST => buffRange.some(BT => ST.startsWith(BT)))) return false else continue } else if (key === 'element') { if (!buff.element || !param.element) continue // 对任意属性生效 @@ -235,6 +235,7 @@ export class BuffManager { filter(type: T, value: buff[T]): buff[] /** * 根据多个指定属性筛选 **启用状态** 的buff + * - 对伤害类型range数组的筛选,只要其中有一个符合即认为满足 */ filter(obj: { [key in Exclude]?: buff[key] } & { element: element }, calc?: Calculator): buff[] /** diff --git a/model/damage/Calculator copy.ts b/model/damage/Calculator copy.ts new file mode 100644 index 0000000..7c658bf --- /dev/null +++ b/model/damage/Calculator copy.ts @@ -0,0 +1,591 @@ +import type { BuffManager, anomaly, buff, element } from './BuffManager.ts' +import type { ZZZAvatarInfo } from '../avatar.js' +import { getMapData } from '../../utils/file.js' +import { elementEnum, anomalyEnum } from './BuffManager.js' +import { charData } from './avatar.js' +import _ from 'lodash' + +/** 技能类型 */ +export interface skill { + /** 技能名,唯一 */ + name: string + /** 技能类型,唯一,参考技能类型命名标准 */ + type: string + /** 属性类型,不指定时,默认取角色属性 */ + element: element + /** 技能倍率数组,于character/角色名/data.json中自动获取,该json中不存在相应数据时可填写该值,以填写值为准 */ + skillMultiplier?: number[] + /** 指定固定技能倍率 */ + fixedMultiplier?: number + /** 角色面板伤害统计中是否隐藏显示 */ + isHide?: boolean + /** + * 重定向技能伤害类型 + * + * 当出现“X"造成的伤害被视为“Y”伤害时,可使用该参数指定Y的类型 + * 参考技能类型命名标准 + */ + redirect?: string + /** 禁用伤害计算cache */ + banCache?: boolean + /** 自定义计算逻辑 */ + dmg?: (calc: Calculator) => damage + /** 额外处理 */ + before?: ({ skill, avatar, usefulBuffs, calc }: { + /** 技能自身 */ + skill: skill + avatar: ZZZAvatarInfo + usefulBuffs: buff[] + calc: Calculator + }) => void + after?: ({ avatar, damage, calc, usefulBuffs }: { + avatar: ZZZAvatarInfo + damage: damage + calc: Calculator + usefulBuffs: buff[] + }) => void +} + +export interface damage { + /** 技能类型 */ + skill: skill + /** 各乘区详细数据 */ + detail: { + /** 伤害倍率 */ + Multiplier: number + /** 攻击力 */ + ATK: number + /** 暴击率 */ + CRITRate: number + /** 暴伤伤害 */ + CRITDMG: number + /** 暴击区期望 */ + CriticalArea: number + /** 增伤区 */ + BoostArea: number + /** 易伤区 */ + VulnerabilityArea: number + /** 抗性区 */ + ResistanceArea: number + /** 防御区 */ + DefenceArea: number + /** 异常暴击率 */ + AnomalyCRITRate: number + /** 异常暴击伤害 */ + AnomalyCRITDMG: number + /** 异常精通区 */ + AnomalyProficiencyArea: number + /** 异常增伤区 */ + AnomalyBoostArea: number + /** 等级区 */ + LevelArea: number + } + /** 伤害结果 */ + result: { + /** 暴击伤害 */ + critDMG: number + /** 期望伤害 */ + expectDMG: number + } + add?: (damage: string | damage) => void + fnc?: (fnc: (n: number) => number) => void + x?: (n: number) => void +} + +const elementType2element = (elementType: number) => elementEnum[[0, 1, 2, 3, -1, 4][elementType - 200]] as element + +const AnomalyData = getMapData('AnomalyData') as { + name: string, + element: element, + element_type: number, + sub_element_type: number, + duration: number, + interval: number, + multiplier: number, + discover?: { + multiplier: number, + fixed_multiplier: number + } +}[] + +interface enemy { + /** 等级 */ + level: number + /** 1级基础防御力 */ + basicDEF: number + /** 抗性 */ + resistance: number +} + +export class Calculator { + readonly buffM: BuffManager + readonly avatar: ZZZAvatarInfo + readonly skills: skill[] = [] + private cache: { [key in buff['type']]?: number } = {} + private damageCache: { [type: string]: damage } = {} + defaultSkill: { [key in keyof skill]?: skill[key] } = {} + enemy: enemy + + constructor(buffM: BuffManager) { + this.buffM = buffM + this.avatar = this.buffM.avatar + this.enemy = { + level: this.avatar.level, + basicDEF: 50, + resistance: 0.2 + } + } + + get initial_properties() { + return this.avatar.initial_properties + } + + /** 定义敌方属性 */ + defEnemy(key: T, value: enemy[T]): void + defEnemy(enemy: enemy): void + defEnemy(param: T | enemy, value?: enemy[T]) { + if (typeof param === 'string' && value !== undefined) { + this.enemy[param] = value + } else if (typeof param === 'object') { + _.merge(this.enemy, param) + } + } + + /** 注册skill */ + new(skill: skill): skill[] + /** 注册skills */ + new(skills: skill[]): skill[] + new(skill: skill | skill[]) { + if (Array.isArray(skill)) { + skill.forEach(s => this.new(s)) + return this.skills + } + skill = _.merge({ + ...this.defaultSkill + }, skill) + if (!skill.element) skill.element = elementType2element(this.avatar.element_type) + if (!skill.name || !skill.type) return logger.warn('无效skill:', skill) + this.skills.push(skill) + return this.skills + } + + /** + * 计算已注册的技能伤害 + * @param type 技能类型名 + */ + calc_skill(type: skill['type']): damage + /** + * 计算技能伤害 + * @param skill 技能 + */ + calc_skill(skill: skill): damage + calc_skill(skill: skill['type'] | skill) { + if (typeof skill === 'string') { + const MySkill = this.skills.find(s => s.type === skill) + if (!MySkill) return + return this.calc_skill(MySkill) + } + if (!skill.banCache && this.damageCache[skill.type]) return this.damageCache[skill.type] + logger.debug(`${logger.green(skill.type)}${skill.name}伤害计算:`) + if (skill.dmg) { + const dmg = skill.dmg(this) + logger.debug('自定义计算最终伤害:', dmg.result) + return dmg + } + this.cache = {} + /** 缩小筛选范围 */ + const usefulBuffs = this.buffM.filter({ + element: skill.element, + range: skill.redirect ? [skill.type, skill.redirect] : [skill.type] + }, this) + if (skill.before) skill.before({ skill, avatar: this.avatar, usefulBuffs, calc: this }) + let Multiplier = 0 + const isAnomaly = typeof anomalyEnum[skill.type as anomaly] === 'number' + if (skill.fixedMultiplier) Multiplier = skill.fixedMultiplier + else if (isAnomaly) { + Multiplier = ( + skill.type === '紊乱' ? + this.get_DiscoverMultiplier(skill) : + this.get_AnomalyMultiplier(skill, usefulBuffs, skill.name.includes('每') ? 1 : 0) + ) || 0 + } else { + if (skill.skillMultiplier) Multiplier = skill.skillMultiplier[this.get_SkillLevel(skill.type[0]) - 1] + else Multiplier = this.get_Multiplier(skill.type) + } + const ExtraMultiplier = this.get_ExtraMultiplier(skill, usefulBuffs) + Multiplier += ExtraMultiplier + if (!Multiplier) return logger.warn('技能倍率缺失:', skill) + if (ExtraMultiplier) logger.debug(`最终倍率:${Multiplier}`) + const ATK = this.get_ATK(skill, usefulBuffs) + let CRITRate = 0, CRITDMG = 0, AnomalyCRITRate = 0, AnomalyCRITDMG = 0 + let AnomalyProficiencyArea = 0, AnomalyBoostArea = 0, LevelArea = 0 + let CriticalArea = 0 + if (isAnomaly) { + AnomalyProficiencyArea = this.get_AnomalyProficiencyArea(skill, usefulBuffs) + AnomalyBoostArea = this.get_AnomalyBoostArea(skill, usefulBuffs) + LevelArea = this.get_LevelArea() + AnomalyCRITRate = this.get_AnomalyCRITRate(skill, usefulBuffs) + AnomalyCRITDMG = this.get_AnomalyCRITDMG(skill, usefulBuffs) + CriticalArea = 1 + AnomalyCRITRate * (AnomalyCRITDMG - 1) + } else { + CRITRate = this.get_CRITRate(skill, usefulBuffs) + CRITDMG = this.get_CRITDMG(skill, usefulBuffs) + CriticalArea = 1 + CRITRate * (CRITDMG - 1) + } + logger.debug(`暴击期望:${CriticalArea}`) + const BoostArea = this.get_BoostArea(skill, usefulBuffs) + const VulnerabilityArea = this.get_VulnerabilityArea(skill, usefulBuffs) + const ResistanceArea = this.get_ResistanceArea(skill, usefulBuffs) + const DefenceArea = this.get_DefenceArea(skill, usefulBuffs) + const result: damage['result'] = isAnomaly ? + { + critDMG: AnomalyCRITRate ? ATK * Multiplier * AnomalyCRITDMG * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea * AnomalyProficiencyArea * LevelArea * AnomalyBoostArea : 0, + expectDMG: ATK * Multiplier * CriticalArea * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea * AnomalyProficiencyArea * LevelArea * AnomalyBoostArea + } : { + critDMG: ATK * Multiplier * CRITDMG * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea, + expectDMG: ATK * Multiplier * CriticalArea * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea + } + const detail: damage['detail'] = { + Multiplier, + ATK, + CRITRate, + CRITDMG, + CriticalArea, + BoostArea, + VulnerabilityArea, + ResistanceArea, + DefenceArea, + AnomalyCRITRate, + AnomalyCRITDMG, + AnomalyProficiencyArea, + AnomalyBoostArea, + LevelArea + } + const damage: damage = { skill, detail, result } + if (skill.after) { + damage.add = (d) => { + if (typeof d === 'string') d = this.calc_skill(d) + logger.debug('追加伤害:' + d.skill.name, d.result) + damage.result.expectDMG += d.result.expectDMG + damage.result.critDMG += d.result.critDMG + } + damage.fnc = (fnc) => { + damage.result.critDMG = fnc(damage.result.critDMG) + damage.result.expectDMG = fnc(damage.result.expectDMG) + } + damage.x = (n) => { + logger.debug('伤害系数:' + n) + damage.fnc!(v => v * n) + } + skill.after({ avatar: this.avatar, damage, calc: this, usefulBuffs }) + } + logger.debug('最终伤害:', result) + if (!skill.banCache) this.damageCache[skill.type] = damage + return damage + } + + calc() { + return this.skills.map(skill => { + try { + return this.calc_skill(skill) + } catch (e) { + logger.error('伤害计算错误:', e) + return + } + }).filter(v => v && !v.skill?.isHide) + } + + /** + * 设置后续新增buff参数的默认值 + * @param obj 直接覆盖默认值 + */ + default(obj: { [key in keyof skill]?: skill[key] }): void + /** + * 设置后续新增技能参数的默认值 + * @param value 为undefined时删除默认值 + */ + default(type: T, value?: skill[T]): void + default(param: T | { [key in keyof skill]?: skill[key] }, value?: skill[T]): void { + if (typeof param === 'object') { + this.defaultSkill = param + } else { + if (value === undefined) delete this.defaultSkill[param] + else this.defaultSkill[param] = value + } + } + + /** + * 获取技能等级 + * @param baseType 技能基类 'A', 'E', 'C', 'R', 'T', 'L' + */ + get_SkillLevel(baseType: string) { + const id = ['A', 'E', 'C', 'R', , 'T', 'L'].indexOf(baseType) + if (id === -1) return 1 + return Number(this.avatar.skills.find(({ skill_type }) => skill_type === id)?.level || 1) + } + + /** + * 获取技能倍率 + * @param type 参见技能命名标准 + */ + get_Multiplier(type: string) { + const skillLevel = this.get_SkillLevel(type[0]) + logger.debug(`等级:${skillLevel}`) + const Multiplier = charData[this.avatar.id].skill[type]?.[skillLevel - 1] + logger.debug(`倍率:${Multiplier}`) + return Multiplier + } + + get_AnomalyData(skill: skill) { + let a = AnomalyData.filter(({ element_type }) => element_type === this.avatar.element_type) + if (skill.type === '紊乱') a = a.filter(({ discover }) => discover) + else a = a.filter(({ name, multiplier }) => name === skill.type && multiplier) + if (a.length === 1) return a[0] + a = a.filter(({ sub_element_type }) => sub_element_type === this.avatar.sub_element_type) + return a[0] + } + + /** 获取属性异常倍率 */ + get_AnomalyMultiplier(skill: skill, usefulBuffs: buff[], times = 0) { + const anomalyData = this.get_AnomalyData(skill) + if (!anomalyData) return + let Multiplier = anomalyData.multiplier + if (anomalyData.duration && anomalyData.interval) { + const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, anomalyData.duration) + times ||= Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)) + Multiplier = anomalyData.multiplier * times + } + logger.debug(`倍率:${Multiplier}`) + return Multiplier + } + + /** 获取紊乱倍率 */ + get_DiscoverMultiplier(skill: skill) { + const anomalyData = this.get_AnomalyData(skill) + if (!anomalyData) return + const AnomalyDuration = this.get_AnomalyDuration({ + ...skill, + name: anomalyData.name, + type: anomalyData.name + }, this.buffM.buffs, anomalyData.duration) + const times = Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)) + const discover = anomalyData.discover! + const Multiplier = discover.fixed_multiplier + times * discover.multiplier + logger.debug(`${anomalyData.name}紊乱 倍率:${Multiplier}`) + return Multiplier + } + + calc_value(value: buff['value'], buff?: buff) { + switch (typeof value) { + case 'number': return value + case 'function': return +value({ avatar: this.avatar, buffM: this.buffM, calc: this }) || 0 + case 'string': return charData[this.avatar.id].buff?.[value]?.[this.get_SkillLevel(value[0]) - 1] || 0 + case 'object': { + if (!Array.isArray(value) || !buff) return 0 + switch (buff.source) { + case 'Weapon': return value[this.avatar.weapon.star - 1] || 0 + case 'Talent': + case 'Addition': return value[this.get_SkillLevel('T') - 1] || 0 + } + } + default: return 0 + } + } + + /** + * 获取局内属性原始值 + * @param isRatio 是否支持buff.value为数值类型且<1时按初始数值百分比提高处理 + */ + get(type: buff['type'], initial: number, skill: skill, usefulBuffs: buff[] = this.buffM.buffs, isRatio = false): number { + return this.cache[type] ??= this.buffM._filter(usefulBuffs, { + element: skill?.element, + range: skill?.redirect ? [skill.type, skill.redirect] : [skill?.type], + type + }, this).reduce((previousValue, buff) => { + const { value } = buff + let add = 0 + if (isRatio && typeof value === 'number' && value < 1) { // 值小于1时,认为是百分比 + add = value * initial + } else { + add = this.calc_value(value, buff) + if (add < 1 && isRatio && Array.isArray(value)) + add *= initial + } + logger.debug(`\tBuff:${buff.name}对${buff.range || '全类型'}增加${add}${buff.element || ''}${type}`) + return previousValue + add + }, initial) + } + + /** 攻击力 */ + get_ATK(skill: skill, usefulBuffs: buff[]) { + let ATK = this.get('攻击力', this.initial_properties.ATK, skill, usefulBuffs, true) + ATK = Math.max(0, Math.min(ATK, 10000)) + logger.debug(`攻击力:${ATK}`) + return ATK + } + + /** 额外倍率 */ + get_ExtraMultiplier(skill: skill, usefulBuffs: buff[]) { + const ExtraMultiplier = this.get('倍率', 0, skill, usefulBuffs) + ExtraMultiplier && logger.debug(`额外倍率:${ExtraMultiplier}`) + return ExtraMultiplier + } + + /** 暴击率 */ + get_CRITRate(skill: skill, usefulBuffs: buff[]) { + let CRITRate = this.get('暴击率', this.initial_properties.CRITRate, skill, usefulBuffs) + CRITRate = Math.max(0, Math.min(CRITRate, 1)) + logger.debug(`暴击率:${CRITRate}`) + return CRITRate + } + + /** 暴击伤害 */ + get_CRITDMG(skill: skill, usefulBuffs: buff[]) { + let CRITDMG = this.get('暴击伤害', this.initial_properties.CRITDMG + 1, skill, usefulBuffs) + CRITDMG = Math.max(0, Math.min(CRITDMG, 5)) + logger.debug(`暴击伤害:${CRITDMG}`) + return CRITDMG + } + + /** 增伤区 */ + get_BoostArea(skill: skill, usefulBuffs: buff[]) { + const BoostArea = this.get('增伤', 1, skill, usefulBuffs) + logger.debug(`增伤区:${BoostArea}`) + return BoostArea + } + + /** 易伤区 */ + get_VulnerabilityArea(skill: skill, usefulBuffs: buff[]) { + const VulnerabilityArea = this.get('易伤', 1, skill, usefulBuffs) + logger.debug(`易伤区:${VulnerabilityArea}`) + return VulnerabilityArea + } + + /** 抗性区 */ + get_ResistanceArea(skill: skill, usefulBuffs: buff[]) { + const ResistanceArea = this.get('无视抗性', 1 + this.enemy.resistance, skill, usefulBuffs) + logger.debug(`抗性区:${ResistanceArea}`) + return ResistanceArea + } + + /** 穿透值 */ + get_Pen(skill: skill, usefulBuffs: buff[]) { + let Pen = this.get('穿透值', this.initial_properties.Pen, skill, usefulBuffs) + Pen = Math.max(0, Math.min(Pen, 1000)) + logger.debug(`穿透值:${Pen}`) + return Pen + } + + /** 穿透率 */ + get_PenRatio(skill: skill, usefulBuffs: buff[]) { + let PenRatio = this.get('穿透率', this.initial_properties.PenRatio, skill, usefulBuffs) + PenRatio = Math.max(0, Math.min(PenRatio, 2)) + logger.debug(`穿透率:${PenRatio}`) + return PenRatio + } + + /** 防御区 */ + get_DefenceArea(skill: skill, usefulBuffs: buff[]) { + const get_base = (level: number) => Math.floor(0.1551 * Math.min(60, level) ** 2 + 3.141 * Math.min(60, level) + 47.2039) + /** 等级基数 */ + const base = get_base(this.avatar.level) + /** 基础防御 */ + const DEF = this.enemy.basicDEF / 50 * get_base(this.enemy.level) + const ignore_defence = this.get('无视防御', 0, skill, usefulBuffs) + const Pen = this.get_Pen(skill, usefulBuffs) + const PenRatio = this.get_PenRatio(skill, usefulBuffs) + /** 防御 */ + const defence = DEF * (1 - ignore_defence) + /** 有效防御 */ + const effective_defence = Math.max(0, defence * (1 - PenRatio) - Pen) + const DefenceArea = base / (effective_defence + base) + logger.debug(`防御区:${DefenceArea}`) + return DefenceArea + } + + /** 等级区 */ + get_LevelArea(level = this.avatar.level) { + const LevelArea = +(1 + 1 / 59 * (level - 1)).toFixed(4) + logger.debug(`等级区:${LevelArea}`) + return LevelArea + } + + /** 异常精通 */ + get_AnomalyProficiency(skill: skill, usefulBuffs: buff[]) { + let AnomalyProficiency = this.get('异常精通', this.initial_properties.AnomalyProficiency, skill, usefulBuffs) + AnomalyProficiency = Math.max(0, Math.min(AnomalyProficiency, 1000)) + logger.debug(`异常精通:${AnomalyProficiency}`) + return AnomalyProficiency + } + + /** 异常精通区 */ + get_AnomalyProficiencyArea(skill: skill, usefulBuffs: buff[]) { + const AnomalyProficiency = this.get_AnomalyProficiency(skill, usefulBuffs) + const AnomalyProficiencyArea = AnomalyProficiency / 100 + logger.debug(`异常精通区:${AnomalyProficiencyArea}`) + return AnomalyProficiencyArea + } + + /** 异常增伤区 */ + get_AnomalyBoostArea(skill: skill, usefulBuffs: buff[]) { + const AnomalyBoostArea = this.get('异常增伤', 1, skill, usefulBuffs) + logger.debug(`异常增伤区:${AnomalyBoostArea}`) + return AnomalyBoostArea + } + + /** 异常暴击率 */ + get_AnomalyCRITRate(skill: skill, usefulBuffs: buff[]) { + let AnomalyCRITRate = this.get('异常暴击率', 0, skill, usefulBuffs) + AnomalyCRITRate = Math.max(0, Math.min(AnomalyCRITRate, 1)) + logger.debug(`异常暴击率:${AnomalyCRITRate}`) + return AnomalyCRITRate + } + + /** 异常暴击伤害 */ + get_AnomalyCRITDMG(skill: skill, usefulBuffs: buff[]) { + let AnomalyCRITDMG = this.get('异常暴击伤害', 1, skill, usefulBuffs) + AnomalyCRITDMG = Math.max(0, Math.min(AnomalyCRITDMG, 5)) + logger.debug(`异常暴击伤害:${AnomalyCRITDMG}`) + return AnomalyCRITDMG + } + + /** 异常持续时间 */ + get_AnomalyDuration(skill: skill, usefulBuffs: buff[], duration: number = 0) { + const AnomalyDuration = +this.get('异常持续时间', duration, skill, usefulBuffs).toFixed(1) + logger.debug(`异常持续时间:${AnomalyDuration}`) + return AnomalyDuration + } + + /** 生命值 */ + get_HP(skill: skill, usefulBuffs: buff[]) { + let HP = this.get('生命值', this.initial_properties.HP, skill, usefulBuffs, true) + HP = Math.max(0, Math.min(HP, 100000)) + logger.debug(`生命值:${HP}`) + return HP + } + + /** 防御力 */ + get_DEF(skill: skill, usefulBuffs: buff[]) { + let DEF = this.get('防御力', this.initial_properties.DEF, skill, usefulBuffs, true) + DEF = Math.max(0, Math.min(DEF, 1000)) + logger.debug(`防御力:${DEF}`) + return DEF + } + + /** 冲击力 */ + get_Impact(skill: skill, usefulBuffs: buff[]) { + let Impact = this.get('冲击力', this.initial_properties.Impact, skill, usefulBuffs, true) + Impact = Math.max(0, Math.min(Impact, 1000)) + logger.debug(`冲击力:${Impact}`) + return Impact + } + + /** 异常掌控 */ + get_AnomalyMastery(skill: skill, usefulBuffs: buff[]) { + let AnomalyMastery = this.get('异常掌控', this.initial_properties.AnomalyMastery, skill, usefulBuffs, true) + AnomalyMastery = Math.max(0, Math.min(AnomalyMastery, 1000)) + logger.debug(`异常掌控:${AnomalyMastery}`) + return AnomalyMastery + } + +} \ No newline at end of file diff --git a/model/damage/Calculator.js b/model/damage/Calculator.js index 3d0a45a..d364ea1 100644 --- a/model/damage/Calculator.js +++ b/model/damage/Calculator.js @@ -54,27 +54,30 @@ export class Calculator { return; return this.calc_skill(MySkill); } - if (this.damageCache[skill.type]) + if (!skill.banCache && this.damageCache[skill.type]) return this.damageCache[skill.type]; - logger.debug(`${logger.green(skill.type)}伤害计算:`); + logger.debug(`${logger.green(skill.type)}${skill.name}伤害计算:`); + if (skill.dmg) { + const dmg = skill.dmg(this); + logger.debug('自定义计算最终伤害:', dmg.result); + return dmg; + } this.cache = {}; - if (skill.dmg) - return skill.dmg(this); /** 缩小筛选范围 */ const usefulBuffs = this.buffM.filter({ element: skill.element, - range: [skill.type] + range: skill.redirect ? [skill.type, skill.redirect] : [skill.type] }, this); if (skill.before) - skill.before({ avatar: this.avatar, usefulBuffs, calc: this }); + skill.before({ skill, avatar: this.avatar, usefulBuffs, calc: this }); let Multiplier = 0; const isAnomaly = typeof anomalyEnum[skill.type] === 'number'; if (skill.fixedMultiplier) Multiplier = skill.fixedMultiplier; else if (isAnomaly) { Multiplier = (skill.type === '紊乱' ? - this.get_DiscoverMultiplier(skill, usefulBuffs) : - this.get_AnomalyMultiplier(skill, usefulBuffs)) || 0; + this.get_DiscoverMultiplier(skill) : + this.get_AnomalyMultiplier(skill, usefulBuffs, skill.name.includes('每') ? 1 : 0)) || 0; } else { if (skill.skillMultiplier) @@ -82,8 +85,12 @@ export class Calculator { else Multiplier = this.get_Multiplier(skill.type); } + const ExtraMultiplier = this.get_ExtraMultiplier(skill, usefulBuffs); + Multiplier += ExtraMultiplier; if (!Multiplier) return logger.warn('技能倍率缺失:', skill); + if (ExtraMultiplier) + logger.debug(`最终倍率:${Multiplier}`); const ATK = this.get_ATK(skill, usefulBuffs); let CRITRate = 0, CRITDMG = 0, AnomalyCRITRate = 0, AnomalyCRITDMG = 0; let AnomalyProficiencyArea = 0, AnomalyBoostArea = 0, LevelArea = 0; @@ -111,9 +118,9 @@ export class Calculator { critDMG: AnomalyCRITRate ? ATK * Multiplier * AnomalyCRITDMG * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea * AnomalyProficiencyArea * LevelArea * AnomalyBoostArea : 0, expectDMG: ATK * Multiplier * CriticalArea * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea * AnomalyProficiencyArea * LevelArea * AnomalyBoostArea } : { - critDMG: ATK * Multiplier * CRITDMG * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea, - expectDMG: ATK * Multiplier * CriticalArea * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea - }; + critDMG: ATK * Multiplier * CRITDMG * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea, + expectDMG: ATK * Multiplier * CriticalArea * BoostArea * VulnerabilityArea * ResistanceArea * DefenceArea + }; const detail = { Multiplier, ATK, @@ -139,14 +146,31 @@ export class Calculator { damage.result.expectDMG += d.result.expectDMG; damage.result.critDMG += d.result.critDMG; }; + damage.fnc = (fnc) => { + damage.result.critDMG = fnc(damage.result.critDMG); + damage.result.expectDMG = fnc(damage.result.expectDMG); + }; + damage.x = (n) => { + logger.debug('伤害系数:' + n); + damage.fnc(v => v * n); + }; skill.after({ avatar: this.avatar, damage, calc: this, usefulBuffs }); } logger.debug('最终伤害:', result); - this.damageCache[skill.type] = damage; + if (!skill.banCache) + this.damageCache[skill.type] = damage; return damage; } calc() { - return this.skills.map(skill => this.calc_skill(skill)).filter(v => v && !v.skill?.isHide); + return this.skills.map(skill => { + try { + return this.calc_skill(skill); + } + catch (e) { + logger.error('伤害计算错误:', e); + return; + } + }).filter(v => v && !v.skill?.isHide); } default(param, value) { if (typeof param === 'object') { @@ -192,25 +216,29 @@ export class Calculator { return a[0]; } /** 获取属性异常倍率 */ - get_AnomalyMultiplier(skill, usefulBuffs, anomalyData) { - anomalyData ||= this.get_AnomalyData(skill); + get_AnomalyMultiplier(skill, usefulBuffs, times = 0) { + const anomalyData = this.get_AnomalyData(skill); if (!anomalyData) return; let Multiplier = anomalyData.multiplier; if (anomalyData.duration && anomalyData.interval) { const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, anomalyData.duration); - const times = Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)); + times ||= Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)); Multiplier = anomalyData.multiplier * times; } logger.debug(`倍率:${Multiplier}`); return Multiplier; } /** 获取紊乱倍率 */ - get_DiscoverMultiplier(skill, usefulBuffs, anomalyData) { - anomalyData ||= this.get_AnomalyData(skill); + get_DiscoverMultiplier(skill) { + const anomalyData = this.get_AnomalyData(skill); if (!anomalyData) return; - const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, anomalyData.duration); + const AnomalyDuration = this.get_AnomalyDuration({ + ...skill, + name: anomalyData.name, + type: anomalyData.name + }, this.buffM.buffs, anomalyData.duration); const times = Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)); const discover = anomalyData.discover; const Multiplier = discover.fixed_multiplier + times * discover.multiplier; @@ -241,7 +269,7 @@ export class Calculator { get(type, initial, skill, usefulBuffs = this.buffM.buffs, isRatio = false) { return this.cache[type] ??= this.buffM._filter(usefulBuffs, { element: skill?.element, - range: [skill?.type], + range: skill?.redirect ? [skill.type, skill.redirect] : [skill?.type], type }, this).reduce((previousValue, buff) => { const { value } = buff; @@ -265,6 +293,12 @@ export class Calculator { logger.debug(`攻击力:${ATK}`); return ATK; } + /** 额外倍率 */ + get_ExtraMultiplier(skill, usefulBuffs) { + const ExtraMultiplier = this.get('倍率', 0, skill, usefulBuffs); + ExtraMultiplier && logger.debug(`额外倍率:${ExtraMultiplier}`); + return ExtraMultiplier; + } /** 暴击率 */ get_CRITRate(skill, usefulBuffs) { let CRITRate = this.get('暴击率', this.initial_properties.CRITRate, skill, usefulBuffs); diff --git a/model/damage/Calculator.ts b/model/damage/Calculator.ts index 3061db4..7c658bf 100644 --- a/model/damage/Calculator.ts +++ b/model/damage/Calculator.ts @@ -19,10 +19,21 @@ export interface skill { fixedMultiplier?: number /** 角色面板伤害统计中是否隐藏显示 */ isHide?: boolean + /** + * 重定向技能伤害类型 + * + * 当出现“X"造成的伤害被视为“Y”伤害时,可使用该参数指定Y的类型 + * 参考技能类型命名标准 + */ + redirect?: string + /** 禁用伤害计算cache */ + banCache?: boolean /** 自定义计算逻辑 */ dmg?: (calc: Calculator) => damage /** 额外处理 */ - before?: ({ avatar, usefulBuffs, calc }: { + before?: ({ skill, avatar, usefulBuffs, calc }: { + /** 技能自身 */ + skill: skill avatar: ZZZAvatarInfo usefulBuffs: buff[] calc: Calculator @@ -77,6 +88,8 @@ export interface damage { expectDMG: number } add?: (damage: string | damage) => void + fnc?: (fnc: (n: number) => number) => void + x?: (n: number) => void } const elementType2element = (elementType: number) => elementEnum[[0, 1, 2, 3, -1, 4][elementType - 200]] as element @@ -172,30 +185,37 @@ export class Calculator { if (!MySkill) return return this.calc_skill(MySkill) } - if (this.damageCache[skill.type]) return this.damageCache[skill.type] - logger.debug(`${logger.green(skill.type)}伤害计算:`) + if (!skill.banCache && this.damageCache[skill.type]) return this.damageCache[skill.type] + logger.debug(`${logger.green(skill.type)}${skill.name}伤害计算:`) + if (skill.dmg) { + const dmg = skill.dmg(this) + logger.debug('自定义计算最终伤害:', dmg.result) + return dmg + } this.cache = {} - if (skill.dmg) return skill.dmg(this) /** 缩小筛选范围 */ const usefulBuffs = this.buffM.filter({ element: skill.element, - range: [skill.type] + range: skill.redirect ? [skill.type, skill.redirect] : [skill.type] }, this) - if (skill.before) skill.before({ avatar: this.avatar, usefulBuffs, calc: this }) + if (skill.before) skill.before({ skill, avatar: this.avatar, usefulBuffs, calc: this }) let Multiplier = 0 const isAnomaly = typeof anomalyEnum[skill.type as anomaly] === 'number' if (skill.fixedMultiplier) Multiplier = skill.fixedMultiplier else if (isAnomaly) { Multiplier = ( skill.type === '紊乱' ? - this.get_DiscoverMultiplier(skill, usefulBuffs) : - this.get_AnomalyMultiplier(skill, usefulBuffs) + this.get_DiscoverMultiplier(skill) : + this.get_AnomalyMultiplier(skill, usefulBuffs, skill.name.includes('每') ? 1 : 0) ) || 0 } else { if (skill.skillMultiplier) Multiplier = skill.skillMultiplier[this.get_SkillLevel(skill.type[0]) - 1] else Multiplier = this.get_Multiplier(skill.type) } + const ExtraMultiplier = this.get_ExtraMultiplier(skill, usefulBuffs) + Multiplier += ExtraMultiplier if (!Multiplier) return logger.warn('技能倍率缺失:', skill) + if (ExtraMultiplier) logger.debug(`最终倍率:${Multiplier}`) const ATK = this.get_ATK(skill, usefulBuffs) let CRITRate = 0, CRITDMG = 0, AnomalyCRITRate = 0, AnomalyCRITDMG = 0 let AnomalyProficiencyArea = 0, AnomalyBoostArea = 0, LevelArea = 0 @@ -249,15 +269,30 @@ export class Calculator { damage.result.expectDMG += d.result.expectDMG damage.result.critDMG += d.result.critDMG } + damage.fnc = (fnc) => { + damage.result.critDMG = fnc(damage.result.critDMG) + damage.result.expectDMG = fnc(damage.result.expectDMG) + } + damage.x = (n) => { + logger.debug('伤害系数:' + n) + damage.fnc!(v => v * n) + } skill.after({ avatar: this.avatar, damage, calc: this, usefulBuffs }) } logger.debug('最终伤害:', result) - this.damageCache[skill.type] = damage + if (!skill.banCache) this.damageCache[skill.type] = damage return damage } calc() { - return this.skills.map(skill => this.calc_skill(skill)).filter(v => v && !v.skill?.isHide) + return this.skills.map(skill => { + try { + return this.calc_skill(skill) + } catch (e) { + logger.error('伤害计算错误:', e) + return + } + }).filter(v => v && !v.skill?.isHide) } /** @@ -311,13 +346,13 @@ export class Calculator { } /** 获取属性异常倍率 */ - get_AnomalyMultiplier(skill: skill, usefulBuffs: buff[], anomalyData?: typeof AnomalyData[number]) { - anomalyData ||= this.get_AnomalyData(skill) + get_AnomalyMultiplier(skill: skill, usefulBuffs: buff[], times = 0) { + const anomalyData = this.get_AnomalyData(skill) if (!anomalyData) return let Multiplier = anomalyData.multiplier if (anomalyData.duration && anomalyData.interval) { const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, anomalyData.duration) - const times = Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)) + times ||= Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)) Multiplier = anomalyData.multiplier * times } logger.debug(`倍率:${Multiplier}`) @@ -325,10 +360,14 @@ export class Calculator { } /** 获取紊乱倍率 */ - get_DiscoverMultiplier(skill: skill, usefulBuffs: buff[], anomalyData?: typeof AnomalyData[number]) { - anomalyData ||= this.get_AnomalyData(skill) + get_DiscoverMultiplier(skill: skill) { + const anomalyData = this.get_AnomalyData(skill) if (!anomalyData) return - const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, anomalyData.duration) + const AnomalyDuration = this.get_AnomalyDuration({ + ...skill, + name: anomalyData.name, + type: anomalyData.name + }, this.buffM.buffs, anomalyData.duration) const times = Math.floor((AnomalyDuration * 10) / (anomalyData.interval * 10)) const discover = anomalyData.discover! const Multiplier = discover.fixed_multiplier + times * discover.multiplier @@ -360,7 +399,7 @@ export class Calculator { get(type: buff['type'], initial: number, skill: skill, usefulBuffs: buff[] = this.buffM.buffs, isRatio = false): number { return this.cache[type] ??= this.buffM._filter(usefulBuffs, { element: skill?.element, - range: [skill?.type], + range: skill?.redirect ? [skill.type, skill.redirect] : [skill?.type], type }, this).reduce((previousValue, buff) => { const { value } = buff @@ -385,6 +424,13 @@ export class Calculator { return ATK } + /** 额外倍率 */ + get_ExtraMultiplier(skill: skill, usefulBuffs: buff[]) { + const ExtraMultiplier = this.get('倍率', 0, skill, usefulBuffs) + ExtraMultiplier && logger.debug(`额外倍率:${ExtraMultiplier}`) + return ExtraMultiplier + } + /** 暴击率 */ get_CRITRate(skill: skill, usefulBuffs: buff[]) { let CRITRate = this.get('暴击率', this.initial_properties.CRITRate, skill, usefulBuffs) diff --git a/model/damage/README.md b/model/damage/README.md index 3f726b8..d3199fb 100644 --- a/model/damage/README.md +++ b/model/damage/README.md @@ -211,6 +211,9 @@ Buff来源可分为三大类:武器、套装、角色(影画、核心被动 > - T 核心技 > - 核心技中的技能各不相同,自行定义即可 +> - Y 影画(如柏妮思6影额外伤害) +> - 影画中的技能各不相同,自行定义即可 + > - 属性异常(特殊) > - 强击 > - 灼烧 @@ -221,11 +224,11 @@ Buff来源可分为三大类:武器、套装、角色(影画、核心被动 #### 技能类型命名解释说明 -1. 首字母为技能所属基类,不可更改、不可单独作为技能名,后跟字母表示技能分支 +1. 首字母为技能所属**基类**,不可更改、不可单独作为技能名,后跟字母表示技能分支 -2. 树状命名,后一位字母代表基于其前一位字母的分支,取技能名发音(倒着读);属性异常较特殊,直接以异常名作为技能类型名 +2. **树状命名**,后一位字母代表基于其前一位字母的分支,取技能名发音(倒着读);属性异常较特殊,直接以异常名作为技能类型名 -3. 后跟数字可表示段数,如AP1表示第一段普攻;为避免混淆,数字仅表示同一技能不同段数,不用于区分不同技能 +3. 后跟数字可表示**段数**,如AP1表示第一段普攻;为避免混淆,数字仅表示同一技能不同段数,不用于区分不同技能 4. 当不需要进一步细分分支时,必须遵守此标准命名,否则可能导致Buff计算错误 @@ -259,7 +262,7 @@ buff作用范围将以技能类型命名为依据向后覆盖。以上述[艾莲 - 如果只包括**CCX**(巡游冲刺攻击),则代表对“冲刺攻击:冰渊潜袭”生效(无论普通或蓄力) - 如果只包括**CCXX**(蓄力巡游冲刺攻击),则代表只对“冲刺攻击:冰渊潜袭”的蓄力巡游冲刺攻击生效 -[点此查看](./character/艾莲/calc.js#L12)艾莲实际伤害计算文件 +[点此查看](./character/艾莲/calc.js#L24)艾莲实际伤害计算文件 ### 技能倍率 @@ -271,6 +274,50 @@ buff作用范围将以技能类型命名为依据向后覆盖。以上述[艾莲 需要自定义data.json时,同样复制一份重命名为**data_user.json**即可 +角色每个技能各等级对应的倍率建议在[米游社官网图鉴](https://baike.mihoyo.com/zzz/wiki/channel/map/2/43)中查询。不建议使用第三方图鉴工具(如B站的绝区零wiki),其技能倍率可能存在错误 + +技能倍率大部分情况下为**等差数列**,少数情况下增量**存在变化**,请注意甄别。对于等差数列的技能倍率,我写了一个简易的生成函数,你可复制粘贴直接使用: + +
+点击展开 + +```js +import { exec } from 'child_process' + +const copyToClipboard = (text) => { + exec('clip').stdin.end(text) +} + +function counter(first, second, num = 16) { + if (first > 100 && second > 100) { + first = Math.round(first * 100) + second = Math.round(second * 100) + } else { + first = Math.round(first * 10000) + second = Math.round(second * 10000) + } + const step = second - first + const arr = [first / 10000] + let txt = first / 10000 + '' + for (let i = 1; i < num; i++) { + const next = (first + step * i) / 10000 + txt += ',' + +next.toFixed(4) + arr.push(next) + } + if (process.platform === 'win32') { + copyToClipboard(txt) + } + console.log(txt) + return arr +} + +// 可按照实际数值填写,当倍率大于100%时可忽略百分号填写 +// 参数依次为:1级倍率 2级倍率 生成长度 +counter(145.7, 159, 16) +``` + +
+ ### 注册技能 伤害计算模块提供了注册各技能的接口[Calculator](./Calculator.ts),所有技能都需要通过此类的实例**calc**进行注册 @@ -285,7 +332,9 @@ buff作用范围将以技能类型命名为依据向后覆盖。以上述[艾莲 - 若某技能所造成伤害的属性与角色属性不符,应指定该技能的属性**element** -- 技能的参数有较多可选的拓展,用于处理更复杂的情况,如有需要请自行查看[Calculator源码](./Calculator.ts)和已有角色的计算案例 +- 技能的参数有较多可选的拓展,用于处理更复杂的情况,如有需要请自行查看[Calculator源码](./Calculator.ts)和已有角色的计算案例(较为复杂的计算案例可参考[柏妮思的伤害计算](./character/柏妮思/calc.js)) + +- 后续会根据需要,新增/调整拓展参数,对于已有的拓展会尽量保持兼容 - 目前只可注册角色的技能,部分武器有独立的造成额外伤害的机制,暂不考虑 @@ -352,6 +401,10 @@ export function calc(buffM, calc, avatar) { 建议开启[在线调试](#管理buff) +## 鸣谢 + +感谢[紫罗兰打烊啦](https://www.miyoushe.com/zzz/accountCenter/postList?id=279259320)对伤害计算细则的评测指正 + --- [伤害计算]:https://www.miyoushe.com/zzz/article/55265618 \ No newline at end of file diff --git a/model/damage/avatar.js b/model/damage/avatar.js index 1dcdfea..0090823 100644 --- a/model/damage/avatar.js +++ b/model/damage/avatar.js @@ -1,5 +1,4 @@ import { aliasToID } from '../../lib/convert/char.js'; -import config from '../../../../lib/config/config.js'; import { BuffManager } from './BuffManager.js'; import { pluginPath } from '../../lib/path.js'; import { elementEnum } from './BuffManager.js'; @@ -15,7 +14,15 @@ const calcFnc = { set: {} }; async function init() { - const isWatch = config.bot.log_level === 'debug'; // debug模式下监听文件变化 + // debug模式下监听文件变化 + const isWatch = await (async () => { + try { + return (await import('../../../../lib/config/config.js')).default.bot.log_level === 'debug'; + } + catch { + return false; + } + })(); await Promise.all(fs.readdirSync(path.join(damagePath, 'character')).filter(v => v !== '模板').map(v => importChar(v, isWatch))); for (const type of ['weapon', 'set']) { await Promise.all(fs.readdirSync(path.join(damagePath, type)).filter(v => v !== '模板.js' && !v.endsWith('_user.js') && v.endsWith('.js')) @@ -43,11 +50,14 @@ async function importChar(charName, isWatch = false) { const calcFile = fs.existsSync(path.join(dir, 'calc_user.js')) ? 'calc_user.js' : 'calc.js'; const dataPath = path.join(dir, (fs.existsSync(path.join(dir, 'data_user.json')) ? 'data_user.json' : 'data.json')); try { + const calcFilePath = path.join(dir, calcFile); if (isWatch) { - watchFile(path.join(dir, calcFile), () => importChar(charName)); + watchFile(calcFilePath, () => importChar(charName)); watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'))); } charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')); + if (!fs.existsSync(calcFilePath)) + return; const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`); if (!m.calc && (!m.buffs || !m.skills)) throw new Error('伤害计算文件格式错误'); diff --git a/model/damage/avatar.ts b/model/damage/avatar.ts index 7691e64..a2bef70 100644 --- a/model/damage/avatar.ts +++ b/model/damage/avatar.ts @@ -1,7 +1,6 @@ import type { skill } from './Calculator.js' import type { ZZZAvatarInfo } from '../avatar.js' import { aliasToID } from '../../lib/convert/char.js' -import config from '../../../../lib/config/config.js' import { buff, BuffManager } from './BuffManager.js' import { pluginPath } from '../../lib/path.js' import { elementEnum } from './BuffManager.js' @@ -46,7 +45,14 @@ const calcFnc: { } async function init() { - const isWatch = config.bot.log_level === 'debug' // debug模式下监听文件变化 + // debug模式下监听文件变化 + const isWatch = await (async () => { + try { + return (await import('../../../../lib/config/config.js')).default.bot.log_level === 'debug' + } catch { + return false + } + })() await Promise.all(fs.readdirSync(path.join(damagePath, 'character')).filter(v => v !== '模板').map(v => importChar(v, isWatch))) for (const type of ['weapon', 'set']) { await Promise.all( @@ -76,11 +82,13 @@ async function importChar(charName: string, isWatch = false) { const calcFile = fs.existsSync(path.join(dir, 'calc_user.js')) ? 'calc_user.js' : 'calc.js' const dataPath = path.join(dir, (fs.existsSync(path.join(dir, 'data_user.json')) ? 'data_user.json' : 'data.json')) try { + const calcFilePath = path.join(dir, calcFile) if (isWatch) { - watchFile(path.join(dir, calcFile), () => importChar(charName)) + watchFile(calcFilePath, () => importChar(charName)) watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'))) } charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')) + if (!fs.existsSync(calcFilePath)) return const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`) if (!m.calc && (!m.buffs || !m.skills)) throw new Error('伤害计算文件格式错误') calcFnc.character[id] = m diff --git a/model/damage/character/11号/calc.js b/model/damage/character/11号/calc.js index 84dbea9..62b0210 100644 --- a/model/damage/character/11号/calc.js +++ b/model/damage/character/11号/calc.js @@ -1,5 +1,11 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ + { + name: '2影', + type: '增伤', + value: 0.03 * 12, + range: ['AP', 'CC', 'CF'] + }, { name: '6影', type: '无视抗性', @@ -7,12 +13,6 @@ export const buffs = [ element: 'Fire', range: ['AQ'] }, - { - name: '2影', - type: '增伤', - value: 0.03 * 12, - range: ['AP', 'CC', 'CF'] - }, { name: '核心被动:热浪', type: '增伤', @@ -29,6 +29,7 @@ export const buffs = [ /** @type {import('../../Calculator.ts').Calculator['skills']} */ export const skills = [ + { name: '灼烧', type: '灼烧' }, { name: '普攻:火力镇压四段', type: 'AQ4' }, { name: '闪避反击:逆火', type: 'CF' }, { name: '强化特殊技:盛燃烈火', type: 'EQ' }, diff --git a/model/damage/character/安东/calc.js b/model/damage/character/安东/calc.js index 0cca921..abcb9e5 100644 --- a/model/damage/character/安东/calc.js +++ b/model/damage/character/安东/calc.js @@ -1,16 +1,16 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ + { + name: '4影', + type: '暴击率', + value: 0.1 + }, { name: '6影', type: '增伤', value: 0.04 * 6, range: ['AQ', 'CFQ'] }, - { - name: '4影', - type: '暴击率', - value: 0.1 - }, { name: '核心被动:兄弟齐心', type: '增伤', @@ -27,6 +27,7 @@ export const buffs = [ /** @type {import('../../Calculator.ts').Calculator['skills']} */ export const skills = [ + { name: '感电每次', type: '感电' }, { name: '普攻二段(爆发)', type: 'AQ2' }, { name: '闪避反击:过载钻击(爆发)', type: 'CFQ' }, { name: '特殊技:爆发钻击(爆发)', type: 'EPQ' }, diff --git a/model/damage/character/悠真/calc.js b/model/damage/character/悠真/calc.js index 94d3165..205ef66 100644 --- a/model/damage/character/悠真/calc.js +++ b/model/damage/character/悠真/calc.js @@ -1,16 +1,16 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ - { - name: '6影', - type: '无视抗性', - value: 0.15 - }, { name: '2影', type: '增伤', value: 0.5, range: ['CCQ'] }, + { + name: '6影', + type: '无视抗性', + value: 0.15 + }, { name: '核心被动:破晓', type: '暴击率', @@ -33,6 +33,7 @@ export const buffs = [ /** @type {import('../../Calculator.ts').Calculator['skills']} */ export const skills = [ + { name: '感电每次', type: '感电' }, { name: '普攻:穿云五段', type: 'AP5' }, { name: '普攻:落羽', type: 'AX' }, { name: '冲刺攻击:飞弦·斩', type: 'CCQ3' }, diff --git a/model/damage/character/星见雅/calc.js b/model/damage/character/星见雅/calc.js index 3f1bece..0bf49e7 100644 --- a/model/damage/character/星见雅/calc.js +++ b/model/damage/character/星见雅/calc.js @@ -9,20 +9,12 @@ // /** 注册buff */ // // 影画加成 // buffM.new({ -// name: '6影', -// type: '增伤', -// isForever: true, -// value: 0.30, +// name: '1影', +// type: '无视防御', +// value: 0.36, // range: ['AX'] // }) // buffM.new({ -// name: '4影', -// type: '增伤', -// isForever: true, -// value: 0.30, -// range: ['TP'] -// }) -// buffM.new({ // name: '2影', // type: '增伤', // isForever: true, @@ -36,9 +28,17 @@ // value: 0.15 // }) // buffM.new({ -// name: '1影', -// type: '无视防御', -// value: 0.36, +// name: '4影', +// type: '增伤', +// isForever: true, +// value: 0.30, +// range: ['TP'] +// }) +// buffM.new({ +// name: '6影', +// type: '增伤', +// isForever: true, +// value: 0.30, // range: ['AX'] // }) // // 额外能力加成 @@ -93,19 +93,11 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ { - name: '6影', - type: '增伤', - isForever: true, - value: 0.30, + name: '1影', + type: '无视防御', + value: 0.36, range: ['AX'] }, - { - name: '4影', - type: '增伤', - isForever: true, - value: 0.30, - range: ['TP'] - }, { name: '2影', type: '暴击率', @@ -120,9 +112,17 @@ export const buffs = [ range: ['AP', 'CF'] }, { - name: '1影', - type: '无视防御', - value: 0.36, + name: '4影', + type: '增伤', + isForever: true, + value: 0.30, + range: ['TP'] + }, + { + name: '6影', + type: '增伤', + isForever: true, + value: 0.30, range: ['AX'] }, { diff --git a/model/damage/character/朱鸢/calc.js b/model/damage/character/朱鸢/calc.js index f0df2f5..65d8fad 100644 --- a/model/damage/character/朱鸢/calc.js +++ b/model/damage/character/朱鸢/calc.js @@ -1,11 +1,5 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ - { - name: '4影', - type: '无视抗性', - value: 0.25, - range: ['AQ', 'CCQ'] - }, { name: '2影', type: '增伤', @@ -13,6 +7,12 @@ export const buffs = [ element: 'Ether', range: ['AQ', 'CCQ'] }, + { + name: '4影', + type: '无视抗性', + value: 0.25, + range: ['AQ', 'CCQ'] + }, { name: '核心被动:特种弹药', type: '增伤', @@ -28,6 +28,7 @@ export const buffs = [ /** @type {import('../../Calculator.ts').Calculator['skills']} */ export const skills = [ + { name: '侵蚀每次', type: '侵蚀' }, { name: '普攻三段(以太)', type: 'AQY3' }, { name: '冲刺攻击:火力压制', type: 'CCQ' }, { diff --git a/model/damage/character/柏妮思/calc.js b/model/damage/character/柏妮思/calc.js new file mode 100644 index 0000000..4682ad1 --- /dev/null +++ b/model/damage/character/柏妮思/calc.js @@ -0,0 +1,102 @@ +/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ +export const buffs = [ + { + name: '1影', + type: '倍率', + value: 1, + range: ['TY', 'YY'] + }, + { + name: '2影', + type: '穿透率', + value: 0.2 + }, + { + name: '4影', + type: '暴击率', + value: 0.3, + range: ['EQ', 'L'] + }, + { + name: '6影', + type: '无视抗性', + value: 0.25, + element: 'Fire', + range: ['TY', 'YY', '灼烧'] + }, + { + name: '核心被动:燃油特调', + type: '增伤', + value: ({ calc }) => Math.min(30, Math.floor(calc.get_AnomalyProficiency() / 10)) * 0.01, + isForever: true, + range: ['TY', 'YY'] + }, + { + name: '额外能力:星火燎原', + type: '异常持续时间', + value: 3, + range: ['灼烧'] + } +] + +/** @type {import('../../Calculator.ts').Calculator['skills']} */ +export const skills = [ + { name: '灼烧', type: '灼烧' }, + { name: '紊乱', type: '紊乱' }, + { + name: '核心被动:余烬', + type: 'TY', + redirect: 'L' + }, + // { name: '普攻:炽焰直调式五段', type: 'AP5' }, + { name: '长按普攻0', type: 'AX0', isHide: true }, + { + name: '长按普攻', + type: 'AX', + after: ({ damage }) => damage.add('AX0') + }, + { name: '闪避反击:摇荡闪', type: 'CF' }, + { name: '强化E:火焰冲击', type: 'EQP0' }, + { + name: '强化E:持续喷射秒伤', + type: 'EQP', + after: ({ damage }) => damage.x(0.5) + }, + { name: '强化E双份:火焰冲击', type: 'EQS0' }, + { + name: '强化E双份:持续喷射秒伤', + type: 'EQS', + after: ({ damage }) => damage.x(0.5) + }, + { name: '连携技:燃油熔焰', type: 'RL' }, + { name: '终结技:纵享盛焰', type: 'RZ' } +] + +/** + * @param {import('../../BuffManager.ts').BuffManager} buffM + * @param {import('../../Calculator.ts').Calculator} calc + * @param {import('../../../avatar.js').ZZZAvatarInfo} avatar + */ +export function calc(buffM, calc, avatar) { + if (avatar.rank >= 6) { + calc.new({ + name: '6影强化E双份额外余烬秒伤', + type: 'YY', + fixedMultiplier: 1.2 + }) + calc.new({ + name: '6影强化E双份额外灼烧', + type: 'Y灼烧', + dmg: (calc) => { + const dmg = calc.calc_skill({ + name: '灼烧每段', + element: 'Fire', + type: '灼烧', + after: ({ damage }) => damage.x(18) + }) + dmg.skill.name = '6影强化E双份额外灼烧' + return dmg + } + }) + } +} \ No newline at end of file diff --git a/model/damage/character/柏妮思/data.json b/model/damage/character/柏妮思/data.json new file mode 100644 index 0000000..ed3e1a7 --- /dev/null +++ b/model/damage/character/柏妮思/data.json @@ -0,0 +1,37 @@ +{ + "skill": { + "AP5": [ + 0.963,1.051,1.139,1.227,1.315,1.403,1.491,1.579,1.667,1.755,1.843,1.931,2.019,2.107,2.195,2.283 + ], + "AX0": [ + 1.254,1.368,1.482,1.596,1.71,1.824,1.938,2.052,2.166,2.28,2.394,2.508,2.622,2.736,2.85,2.964 + ], + "AX": [ + 2.328,2.54,2.752,2.964,3.176,3.388,3.6,3.812,4.024,4.236,4.448,4.66,4.872,5.084,5.296,5.508 + ], + "CF": [ + 2.197,2.397,2.597,2.797,2.997,3.197,3.397,3.597,3.797,3.997,4.197,4.397,4.597,4.797,4.997,5.197 + ], + "EQP0": [ + 0.967,1.055,1.143,1.231,1.319,1.407,1.495,1.583,1.671,1.759,1.847,1.935,2.023,2.111,2.199,2.287 + ], + "EQP": [ + 5.438,5.933,6.428,6.923,7.418,7.913,8.408,8.903,9.398,9.893,10.388,10.883,11.378,11.873,12.368,12.863 + ], + "EQS0": [ + 2.871,3.132,3.393,3.654,3.915,4.176,4.437,4.698,4.959,5.22,5.481,5.742,6.003,6.264,6.525,6.786 + ], + "EQS": [ + 9.581,10.452,11.323,12.194,13.065,13.936,14.807,15.678,16.549,17.42,18.291,19.162,20.033,20.904,21.775,22.646 + ], + "RL": [ + 6.809,7.428,8.047,8.666,9.285,9.904,10.523,11.142,11.761,12.38,12.999,13.618,14.237,14.856,15.475,16.094 + ], + "RZ": [ + 20.122,21.952,23.782,25.612,27.442,29.272,31.102,32.932,34.762,36.592,38.422,40.252,42.082,43.912,45.742,47.572 + ], + "TY": [ + 1.75,2.05,2.35,2.62,2.9,3.2,3.5 + ] + } +} \ No newline at end of file diff --git a/model/damage/character/模板/calc.js b/model/damage/character/模板/calc.js index b4b4ea1..df2be97 100644 --- a/model/damage/character/模板/calc.js +++ b/model/damage/character/模板/calc.js @@ -7,13 +7,7 @@ export function calc(buffM, calc, avatar) { /** 注册buff */ // 影画加成 buffM.new({ - name: '6影', - type: , - value: 0, - range: [''] - }) - buffM.new({ - name: '4影', + name: '1影', type: , value: 0, range: [''] @@ -25,7 +19,13 @@ export function calc(buffM, calc, avatar) { range: [''] }) buffM.new({ - name: '1影', + name: '4影', + type: , + value: 0, + range: [''] + }) + buffM.new({ + name: '6影', type: , value: 0, range: [''] @@ -65,13 +65,7 @@ export function calc(buffM, calc, avatar) { /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ { - name: '6影', - type: , - value: 0, - range: [''] - }, - { - name: '4影', + name: '1影', type: , value: 0, range: [''] @@ -83,7 +77,13 @@ export const buffs = [ range: [''] }, { - name: '1影', + name: '4影', + type: , + value: 0, + range: [''] + }, + { + name: '6影', type: , value: 0, range: [''] diff --git a/model/damage/character/猫又/calc.js b/model/damage/character/猫又/calc.js index 72af1d4..2bfc0d1 100644 --- a/model/damage/character/猫又/calc.js +++ b/model/damage/character/猫又/calc.js @@ -1,9 +1,10 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ { - name: '6影', - type: '暴击伤害', - value: 0.18 * 3 + name: '1影', + type: '无视抗性', + value: 0.16, + element: 'Physical' }, { name: '4影', @@ -11,10 +12,9 @@ export const buffs = [ value: 0.07 * 2 }, { - name: '1影', - type: '无视抗性', - value: 0.16, - element: 'Physical' + name: '6影', + type: '暴击伤害', + value: 0.18 * 3 }, { name: '核心被动:猫步诡影', diff --git a/model/damage/character/艾莲/calc.js b/model/damage/character/艾莲/calc.js index 33883a3..7ab4f85 100644 --- a/model/damage/character/艾莲/calc.js +++ b/model/damage/character/艾莲/calc.js @@ -1,5 +1,17 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ + { + name: '1影', + type: '暴击率', + value: 0.2 * 6 + }, + { + name: '2影', + type: '暴击伤害', + value: 0.6, + range: ['EQ'] + }, + { name: '6影', type: '穿透率', @@ -11,17 +23,6 @@ export const buffs = [ value: 2.5, range: ['CCXX'] }, - { - name: '2影', - type: '暴击伤害', - value: 0.6, - range: ['EQ'] - }, - { - name: '1影', - type: '暴击率', - value: 0.2 * 6 - }, { name: '核心被动:凌牙厉齿', type: '暴击伤害', diff --git a/model/damage/character/青衣/calc.js b/model/damage/character/青衣/calc.js index e993787..0efe706 100644 --- a/model/damage/character/青衣/calc.js +++ b/model/damage/character/青衣/calc.js @@ -1,5 +1,15 @@ /** @type {import('../../BuffManager.ts').BuffManager['buffs']} */ export const buffs = [ + { + name: '1影', + type: '无视防御', + value: 0.15 + }, + { + name: '1影', + type: '暴击率', + value: 0.2 + }, { name: '6影', type: '暴击伤害', @@ -12,16 +22,6 @@ export const buffs = [ type: '无视抗性', value: 0.2 }, - { - name: '1影', - type: '无视防御', - value: 0.15 - }, - { - name: '1影', - type: '暴击率', - value: 0.2 - }, { name: '额外能力:阳关三叠', type: '攻击力', @@ -45,6 +45,7 @@ export const buffs = [ /** @type {import('../../Calculator.ts').Calculator['skills']} */ export const skills = [ + { name: '感电每次', type: '感电' }, { name: '普攻:醉花月云转', type: 'AQ' }, { name: '闪避反击:意不尽', type: 'CF' }, { name: '强化特殊技:月上海棠', type: 'EQ' },