完全重构伤害计算;支持异常伤害计算;支持所有武器、套装计算;新增悠真计算等

This commit is contained in:
UCPr 2025-01-14 02:18:42 +08:00
parent f7e1066773
commit 3f8e64af66
99 changed files with 4243 additions and 1615 deletions

3
.gitignore vendored
View file

@ -9,4 +9,5 @@ data/**/*.*
resources/images/**/*.*
resources/data/**/*.*
resources/map/*.js
model/damage/map/
model/damage/character/**/*_user.*

View file

@ -12,6 +12,8 @@
> 插件依靠社区维护发起者随缘更新但是ZZZure组织成员会对PR进行合并你可以在PR页面@协助者进行合并
>
> 在你使用之前请**务必**完整阅读 `README` 内容,如因无视 `README` 遇到的问题,在提问时难免遭受嘲笑。
>
> 如需自定义伤害计算请查看[此说明](./model/damage/README.md)
# 安装

View file

@ -44,7 +44,7 @@ export function idToClassName(_id) {
/**
* 获取属性标识
* @param {string} _id 属性id
* @param {string | number} _id 属性id
* @returns {string | null}
*/
export const idToSignName = _id => {
@ -56,7 +56,7 @@ export const idToSignName = _id => {
/**
* 获取属性名称
* @param {string} _id 属性id
* @param {string | number} _id 属性id
* @returns {string | null}
*/
export const idToName = _id => {

View file

@ -30,3 +30,26 @@ export const weaponFileNameToID = name => {
export const getAllWeaponID = () => {
return Object.keys(WeaponId2Sprite);
};
const WeaponId2Data = getMapData('WeaponId2Data');
/**
* 武器名称转id
* @param {string} name 武器全称
* @returns {number | null}
*/
export const weaponNameToID = name => {
for (const [id, data] of Object.entries(WeaponId2Data)) {
if (data.name === name) return +id;
}
return null;
}
/**
* 武器ID转职业
* @param {number} id 武器全称
* @returns {number | null}
*/
export const weaponIDToProfession = id => {
return WeaponId2Data[id]?.profession ?? null;
}

View file

@ -1,4 +1,4 @@
import { element, property } from '../lib/convert.js';
import { element } from '../lib/convert.js';
import {
getRoleImage,
getSmallSquareAvatar,
@ -8,9 +8,7 @@ import { imageResourcesPath } from '../lib/path.js';
import { Equip, Weapon } from './equip.js';
import { Property } from './property.js';
import { Skill } from './skill.js';
import { relice_ability } from './damage/relice.js';
import { weapon_ability } from './damage/weapon.js';
import { avatar_ability, has_calculation } from './damage/avatar.js';
import { avatar_ability } from './damage/avatar.js';
import { hasScoreData } from '../lib/score.js';
import _ from 'lodash';
@ -180,6 +178,7 @@ export class ZZZAvatarInfo {
* name_mi18n: string;
* full_name_mi18n: string;
* element_type: number;
* sub_element_type: number;
* camp_name_mi18n: string;
* avatar_profession: number;
* rarity: string;
@ -201,6 +200,7 @@ export class ZZZAvatarInfo {
name_mi18n,
full_name_mi18n,
element_type,
sub_element_type,
camp_name_mi18n,
avatar_profession,
rarity,
@ -224,9 +224,11 @@ export class ZZZAvatarInfo {
this.full_name_mi18n = full_name_mi18n;
/** @type {number} 元素种类 */
this.element_type = element_type;
/** @type {number} 子元素种类 */
this.sub_element_type = sub_element_type;
/** @type {string} */
this.camp_name_mi18n = camp_name_mi18n;
/** @type {number} */
/** @type {number} 职业 */
this.avatar_profession = avatar_profession;
/** @type {string} 稀有度 */
this.rarity = rarity;
@ -298,111 +300,59 @@ export class ZZZAvatarInfo {
return data;
}
/** @type {{
* base_detail: {
* hp: number;
* attack: number;
* defence: number;
* ImpactRatio: number;
* CriticalChanceBase: number;
* CriticalDamageBase: number;
* ElementAbnormalPower: number;
* ElementMystery: number;
* PenRatioBase: number;
* SpGetRatio: number;
* }
* bonus_detail: Record<string, number>;
* set_detail: Record<string, number>;
* }} */
get damage_basic_properties() {
const basic_properties = this.basic_properties;
const base_detail = {
hp: Number(basic_properties.hpmax.final),
attack: Number(basic_properties.attack.final),
defence: Number(basic_properties.def.final),
ImpactRatio: Number(basic_properties.breakstun.final),
CriticalChanceBase:
Number(basic_properties.crit.final.replace('%', '')) / 100,
CriticalDamageBase:
Number(basic_properties.critdam.final.replace('%', '')) / 100,
ElementAbnormalPower: Number(
basic_properties.elementabnormalpower.final
),
ElementMystery: Number(basic_properties.elementmystery.final),
PenRatioBase:
Number(basic_properties.penratio.final.replace('%', '')) / 100,
SpGetRatio: Number(basic_properties.sprecover.final),
};
/** 穿
* 穿透值23203
* 伤害加成31503-31703
/** 基础属性 */
get base_properties() {
if (this._base_properties) return this._base_properties
const basic_properties = this.basic_properties
/**
* @param {keyof ZZZAvatarInfo['basic_properties']} name
*/
const bonus_detail = {};
/** 计算套装数量 */
const set_detail = {};
for (const equip_detail of this.equip) {
bonus_detail['PenDelta'] =
_.get(bonus_detail, 'PenDelta', 0) + equip_detail.get_property(23203);
if (equip_detail.equipment_type == 5) {
const propname = property.idToName(
equip_detail.main_properties[0].property_id
);
if (propname.includes('属性伤害提高')) {
const propenname = property.idToSignName(
equip_detail.main_properties[0].property_id
);
bonus_detail[propenname] =
_.get(bonus_detail, propenname, 0) +
Number(equip_detail.main_properties[0].base.replace('%', '')) / 100;
}
}
const suit_id = String(equip_detail.equip_suit.suit_id);
set_detail[suit_id] = _.get(set_detail, suit_id, 0) + 1;
const get = (name) => {
const data = basic_properties[name]
return Number(data.base || data.final)
}
return this._base_properties = {
HP: get('hpmax'),
ATK: get('attack'),
DEF: get('def'),
Impact: get('breakstun'),
AnomalyMastery: get('elementabnormalpower'),
AnomalyProficiency: get('elementmystery'),
EnergyRegen: get('sprecover')
}
logger.debug('base_detail', base_detail);
logger.debug('bonus_detail', bonus_detail);
logger.debug('set_detail', set_detail);
const retuan_detail = {
base_detail: base_detail,
bonus_detail: bonus_detail,
set_detail: set_detail,
};
return retuan_detail;
}
/** @type {{
* title: string;
* value: {name: string, value: number}[]
* }[]} */
/** 初始属性 */
get initial_properties() {
if (this._initial_properties) return this._initial_properties
const basic_properties = this.basic_properties
/**
* @param {keyof ZZZAvatarInfo['basic_properties']} name
*/
const get = (name) => {
const data = basic_properties[name].final
return Number(data.includes('%') ? data.replace('%', '') / 100 : data)
}
return this._initial_properties = {
HP: get('hpmax'),
ATK: get('attack'),
DEF: get('def'),
Impact: get('breakstun'),
CRITRate: get('crit'),
CRITDMG: get('critdam'),
/** 异常掌控 */
AnomalyMastery: get('elementabnormalpower'),
/** 异常精通 */
AnomalyProficiency: get('elementmystery'),
Pen: this.equip.reduce((prev, curr) => prev + curr.get_property(23203), 0),
PenRatio: get('penratio'),
EnergyRegen: get('sprecover')
}
}
/** @type {import("./damage/Calculator.ts").damage} */
get damages() {
if (!has_calculation(this.id)) {
return [];
}
/** 处理基础数据 */
let { base_detail, bonus_detail, set_detail } = this.damage_basic_properties;
/** 处理驱动盘套装加成 */
let set_detailkeys = Object.keys(set_detail);
for (const set_id of set_detailkeys) {
const set_num = set_detail[set_id];
if (set_num >= 2)
bonus_detail = relice_ability(
set_id,
set_num,
base_detail,
bonus_detail
);
}
logger.debug('+套装bonus_detail', bonus_detail);
/** 处理音频加成 */
if (this.weapon)
bonus_detail = weapon_ability(this.weapon, base_detail, bonus_detail);
logger.debug('+音频bonus_detail', bonus_detail);
/** 处理角色加成 */
const damagelist = avatar_ability(this, base_detail, bonus_detail);
return damagelist;
return avatar_ability(this);
}
/** @type {number|boolean} */

223
model/damage/BuffManager.js Normal file
View file

@ -0,0 +1,223 @@
import { weaponIDToProfession } from '../../lib/convert/weapon.js';
import _ from 'lodash';
export var elementEnum;
(function (elementEnum) {
// 物理、火、冰、电、以太
elementEnum[elementEnum["Physical"] = 0] = "Physical";
elementEnum[elementEnum["Fire"] = 1] = "Fire";
elementEnum[elementEnum["Ice"] = 2] = "Ice";
elementEnum[elementEnum["Electric"] = 3] = "Electric";
elementEnum[elementEnum["Ether"] = 4] = "Ether";
})(elementEnum || (elementEnum = {}));
export var anomalyEnum;
(function (anomalyEnum) {
anomalyEnum[anomalyEnum["\u5F3A\u51FB"] = 0] = "\u5F3A\u51FB";
anomalyEnum[anomalyEnum["\u707C\u70E7"] = 1] = "\u707C\u70E7";
anomalyEnum[anomalyEnum["\u788E\u51B0"] = 2] = "\u788E\u51B0";
anomalyEnum[anomalyEnum["\u611F\u7535"] = 3] = "\u611F\u7535";
anomalyEnum[anomalyEnum["\u4FB5\u8680"] = 4] = "\u4FB5\u8680";
anomalyEnum[anomalyEnum["\u7D0A\u4E71"] = 5] = "\u7D0A\u4E71";
})(anomalyEnum || (anomalyEnum = {}));
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["\u66B4\u51FB\u7387"] = 7] = "\u66B4\u51FB\u7387";
buffTypeEnum[buffTypeEnum["\u66B4\u51FB\u4F24\u5BB3"] = 8] = "\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";
// 其他属性一般不直接影响伤害但可能用于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 = {}));
let depth = 0, weakMapCheck = new WeakMap();
/**
* Buff管理器
* 用于管理角色局内Buff
*/
export class BuffManager {
avatar;
buffs = [];
/** 套装计数 */
setCount = {};
defaultBuff = {};
constructor(avatar) {
this.avatar = avatar;
}
new(buff) {
if (Array.isArray(buff)) {
buff.forEach(b => this.new(b));
return this.buffs;
}
// 简化参数
if (!buff.name && (buff.source || this.defaultBuff.source) === 'Set' && this.defaultBuff.name && typeof buff.check === 'number')
buff.name = this.defaultBuff.name + buff.check;
buff = _.merge({
status: true,
isForever: false,
...this.defaultBuff
}, buff);
if (!buff.source) {
if (buff.name.includes('核心') || buff.name.includes('天赋'))
buff.source = 'Talent';
else if (buff.name.includes('额外能力'))
buff.source = 'Addition';
else if (buff.name.includes('影'))
buff.source = 'Rank';
else if (buff.name.includes('技'))
buff.source = 'Skill';
}
if (!buff.name || !buff.value || !buff.source || !buffTypeEnum[buffTypeEnum[buff.type]])
return logger.warn('无效buff', buff);
// 武器buff职业检查
if (buff.source === 'Weapon') {
if (typeof buff.check === 'function') {
const oriCheck = buff.check;
buff.check = ({ avatar, buffM, calc }) => avatar.avatar_profession === weaponIDToProfession(avatar.weapon.id) && oriCheck({ avatar, buffM, calc });
}
else {
buff.check = ({ avatar }) => avatar.avatar_profession === weaponIDToProfession(avatar.weapon.id);
}
}
else if (buff.source === 'Rank') {
buff.check ??= +buff.name.match(/\d/)?.[0];
}
this.buffs.push(buff);
return this.buffs;
}
_filter(buffs, param, valueOcalc) {
depth++;
try {
if (typeof param === 'string') {
buffs = buffs.filter(buff => buff[param] === valueOcalc);
}
else if (typeof param === 'object') {
buffs = buffs.filter(buff => {
if (buff.status === false)
return false;
for (const key in param) {
if (key === 'range') {
const buffRange = buff.range;
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;
else
continue;
}
else if (key === 'element') {
if (!buff.element || !param.element)
continue; // 对任意属性生效
if (Array.isArray(buff.element)) {
if (buff.element.includes(param.element))
continue;
return false;
}
}
// @ts-ignore
if (buff[key] !== param[key])
return false;
}
if (buff.check) {
if (typeof buff.check === 'number') {
if (buff.source === 'Set' && (this.setCount[buff.name.replace(/\d$/, '')] < buff.check))
return false;
else if (buff.source === 'Rank' && (this.avatar.rank < buff.check))
return false;
}
else if (valueOcalc) {
if (weakMapCheck.has(buff)) {
// console.log(`depth${depth} ${buff.name}${weakMapCheck.get(buff)}`)
if (!weakMapCheck.get(buff))
return false;
}
else {
weakMapCheck.set(buff, false);
if (!buff.check({
avatar: this.avatar,
buffM: this,
calc: valueOcalc
}))
return false;
weakMapCheck.set(buff, true);
}
}
else {
logger.debug('未传入calc' + buff.name);
return false;
}
}
return true;
});
}
else {
buffs = buffs.filter(param);
}
}
catch (e) {
logger.error(e);
}
if (--depth === 0) {
// console.log('重置weakMapCheck')
weakMapCheck = new WeakMap();
}
return buffs;
}
/**
* 遍历buff列表
*/
forEach(fnc) {
return this.buffs.forEach(fnc);
}
filter(param, valueOcalc) {
// @ts-ignore
return this._filter(this.buffs, param, valueOcalc);
}
operator(type, value, fnc) {
this.forEach(buff => {
if (buff[type] === value) {
fnc(buff);
}
});
}
/**
* 关闭符合条件的所有buff
*/
close(type, value) {
this.operator(type, value, buff => buff.status = false);
}
/**
* 开启符合条件的所有buff
*/
open(type, value) {
this.operator(type, value, buff => buff.status = true);
}
default(param, value) {
if (typeof param === 'object') {
this.defaultBuff = param;
}
else {
if (value === undefined)
delete this.defaultBuff[param];
else
this.defaultBuff[param] = value;
}
}
}

296
model/damage/BuffManager.ts Normal file
View file

@ -0,0 +1,296 @@
import type { ZZZAvatarInfo } from '../avatar.js'
import type { Calculator } from './Calculator.ts'
import { weaponIDToProfession } from '../../lib/convert/weapon.js'
import _ from 'lodash'
export enum elementEnum {
// 物理、火、冰、电、以太
Physical, Fire, Ice, Electric, Ether
}
export enum anomalyEnum {
, , , , ,
}
/** 属性类型 */
export type element = keyof typeof elementEnum
/** 异常类型 */
export type anomaly = keyof typeof anomalyEnum
/**
* Buff来源
* @Weapon
* @Set
* @Rank
* @Talent
* @Addition
* @Skill
*/
export type buffSource = 'Weapon' | 'Set' | 'Rank' | 'Talent' | 'Addition' | 'Skill'
export enum buffTypeEnum {
// 通用乘区
, , , , , 穿, 穿,
// 直伤乘区
, ,
// 异常乘区
, , , , ,
// 其他属性一般不直接影响伤害但可能用于buff是否生效判断/转模
, , ,
}
/** Buff增益类型 */
export type buffType = keyof typeof buffTypeEnum
export interface buff {
/** Buff状态true生效false无效 */
status: boolean
/** Buff是否常驻 */
isForever: boolean
/** Buff名称 */
name: string
/** Buff来源 */
source: buffSource
/** Buff增益的类型 */
type: buffType
/**
* Buff增益数值
* @number
* -
* - buff增益类型为**/////** **<1**则将此值理解为**初始属性****百分比提高**
* @array
* buff.source自动选择对应等级/
* - Weapon
* - Talent/Addition
* @function
*
* @string
* buff提高值可能随技能/data.json的"buff"value即为键名
*/
value: number | (({ avatar, buffM, calc }: {
avatar: ZZZAvatarInfo
buffM: BuffManager
calc: Calculator
}) => number) | string | number[]
/** Buff增益技能类型范围无则对全部生效参考技能类型命名标准 */
range?: string[] | anomaly[]
/** Buff增益属性类型无则对全部生效 */
element?: element | element[]
/**
* buff是否生效
* @function
*
* @number
* - buff.source为Set时>=
* - buff.source为Rank时>=
*/
check?: (({ avatar, buffM, calc }: {
avatar: ZZZAvatarInfo
buffM: BuffManager
calc: Calculator
}) => boolean) | number
}
let depth = 0, weakMapCheck = new WeakMap<buff, boolean>()
/**
* Buff管理器
* Buff
*/
export class BuffManager {
readonly avatar: ZZZAvatarInfo
readonly buffs: buff[] = []
/** 套装计数 */
setCount: { [name: string]: number } = {}
defaultBuff: { [key in keyof buff]?: buff[key] } = {}
constructor(avatar: ZZZAvatarInfo) {
this.avatar = avatar
}
/** 注册buff */
new(buff: buff): buff[]
/** 注册buffs */
new(buffs: buff[]): buff[]
new(buff: buff | buff[]) {
if (Array.isArray(buff)) {
buff.forEach(b => this.new(b))
return this.buffs
}
// 简化参数
if (!buff.name && (buff.source || this.defaultBuff.source) === 'Set' && this.defaultBuff.name && typeof buff.check === 'number')
buff.name = this.defaultBuff.name + buff.check
buff = _.merge({
status: true,
isForever: false,
...this.defaultBuff
}, buff)
if (!buff.source) {
if (buff.name.includes('核心') || buff.name.includes('天赋')) buff.source = 'Talent'
else if (buff.name.includes('额外能力')) buff.source = 'Addition'
else if (buff.name.includes('影')) buff.source = 'Rank'
else if (buff.name.includes('技')) buff.source = 'Skill'
}
if (!buff.name || !buff.value || !buff.source || !buffTypeEnum[buffTypeEnum[buff.type]])
return logger.warn('无效buff', buff)
// 武器buff职业检查
if (buff.source === 'Weapon') {
if (typeof buff.check === 'function') {
const oriCheck = buff.check
buff.check = ({ avatar, buffM, calc }) => avatar.avatar_profession === weaponIDToProfession(avatar.weapon.id) && oriCheck({ avatar, buffM, calc })
} else {
buff.check = ({ avatar }) => avatar.avatar_profession === weaponIDToProfession(avatar.weapon.id)
}
} else if (buff.source === 'Rank') {
buff.check ??= +buff.name.match(/\d/)!?.[0]
}
this.buffs.push(buff)
return this.buffs
}
_filter<T extends keyof buff>(buffs: buff[], type: T, value: buff[T]): buff[]
_filter(buffs: buff[], obj: { [key in Exclude<keyof buff, 'status' | 'check' | 'element'>]?: buff[key] } & { element: element }, calc?: Calculator): buff[]
_filter(buffs: buff[], fnc: (buff: buff, index: number) => boolean): buff[]
_filter<T extends keyof buff>(
buffs: buff[],
param:
| T
| ({ [key in Exclude<keyof buff, 'status' | 'check' | 'element'>]?: buff[key] } & { element: element })
| ((buff: buff, index: number) => boolean),
valueOcalc?: buff[T] | Calculator
) {
depth++
try {
if (typeof param === 'string') {
buffs = buffs.filter(buff => buff[param] === valueOcalc)
} else if (typeof param === 'object') {
buffs = buffs.filter(buff => {
if (buff.status === false) return false
for (const key in param) {
if (key === 'range') {
const buffRange = buff.range
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
else continue
} else if (key === 'element') {
if (!buff.element || !param.element) continue // 对任意属性生效
if (Array.isArray(buff.element)) {
if (buff.element.includes(param.element)) continue
return false
}
}
// @ts-ignore
if (buff[key] !== param[key]) return false
}
if (buff.check) {
if (typeof buff.check === 'number') {
if (buff.source === 'Set' && (this.setCount[buff.name.replace(/\d$/, '')] < buff.check)) return false
else if (buff.source === 'Rank' && (this.avatar.rank < buff.check)) return false
} else if (valueOcalc) {
if (weakMapCheck.has(buff)) {
// console.log(`depth${depth} ${buff.name}${weakMapCheck.get(buff)}`)
if (!weakMapCheck.get(buff)) return false
} else {
weakMapCheck.set(buff, false)
if (!buff.check({
avatar: this.avatar,
buffM: this,
calc: valueOcalc as Calculator
})) return false
weakMapCheck.set(buff, true)
}
} else {
logger.debug('未传入calc' + buff.name)
return false
}
}
return true
})
} else {
buffs = buffs.filter(param)
}
} catch (e) {
logger.error(e)
}
if (--depth === 0) {
// console.log('重置weakMapCheck')
weakMapCheck = new WeakMap()
}
return buffs
}
/**
* buff列表
*/
forEach(fnc: (buff: buff, index: number) => void) {
return this.buffs.forEach(fnc)
}
/**
* buff
*/
filter<T extends keyof buff>(type: T, value: buff[T]): buff[]
/**
* **** buff
*/
filter(obj: { [key in Exclude<keyof buff, 'status' | 'check' | 'element'>]?: buff[key] } & { element: element }, calc?: Calculator): buff[]
/**
* buff
*/
filter(fnc: (buff: buff, index: number) => boolean): buff[]
filter<T extends keyof buff>(
param:
| T
| ({ [key in Exclude<keyof buff, 'status' | 'check' | 'element'>]?: buff[key] } & { element: element })
| ((buff: buff, index: number) => boolean),
valueOcalc?: buff[T] | Calculator
) {
// @ts-ignore
return this._filter(this.buffs, param, valueOcalc)
}
operator<T extends keyof buff>(type: T, value: buff[T], fnc: (buff: buff) => void) {
this.forEach(buff => {
if (buff[type] === value) {
fnc(buff)
}
})
}
/**
* buff
*/
close<T extends keyof buff>(type: T, value: buff[T]) {
this.operator(type, value, buff => buff.status = false)
}
/**
* buff
*/
open<T extends keyof buff>(type: T, value: buff[T]) {
this.operator(type, value, buff => buff.status = true)
}
/**
* buff参数的默认值
* @param obj
*/
default(obj: { [key in keyof buff]?: buff[key] }): void
/**
* buff参数的默认值
* @param value undefined时删除默认值
*/
default<T extends keyof buff>(type: T, value?: buff[T]): void
default<T extends keyof buff>(param: T | { [key in keyof buff]?: buff[key] }, value?: buff[T]): void {
if (typeof param === 'object') {
this.defaultBuff = param
} else {
if (value === undefined) delete this.defaultBuff[param]
else this.defaultBuff[param] = value
}
}
}

406
model/damage/Calculator.js Normal file
View file

@ -0,0 +1,406 @@
import { getMapData } from '../../utils/file.js';
import { elementEnum, anomalyEnum } from './BuffManager.js';
import { charData } from './avatar.js';
import _ from 'lodash';
const elementType2element = (elementType) => elementEnum[[0, 1, 2, 3, -1, 4][elementType - 200]];
const AnomalyData = getMapData('AnomalyData');
export class Calculator {
buffM;
avatar;
skills = [];
cache = {};
damageCache = {};
defaultSkill = {};
enemy;
constructor(buffM) {
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(param, value) {
if (typeof param === 'string' && value !== undefined) {
this.enemy[param] = value;
}
else if (typeof param === 'object') {
_.merge(this.enemy, param);
}
}
new(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;
}
calc_skill(skill) {
if (typeof skill === 'string') {
const MySkill = this.skills.find(s => s.type === skill);
if (!MySkill)
return;
return this.calc_skill(MySkill);
}
if (this.damageCache[skill.type])
return this.damageCache[skill.type];
logger.debug(`${logger.green(skill.type)}伤害计算:`);
this.cache = {};
if (skill.dmg)
return skill.dmg(this);
/** 缩小筛选范围 */
const usefulBuffs = this.buffM.filter({
element: skill.element,
range: [skill.type]
}, this);
if (skill.before)
skill.before({ 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;
}
else {
if (skill.skillMultiplier)
Multiplier = skill.skillMultiplier[this.get_SkillLevel(skill.type[0]) - 1];
else
Multiplier = this.get_Multiplier(skill.type);
}
if (!Multiplier)
return logger.warn('技能倍率缺失:', skill);
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 = 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 = {
Multiplier,
ATK,
CRITRate,
CRITDMG,
CriticalArea,
BoostArea,
VulnerabilityArea,
ResistanceArea,
DefenceArea,
AnomalyCRITRate,
AnomalyCRITDMG,
AnomalyProficiencyArea,
AnomalyBoostArea,
LevelArea
};
const 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;
};
skill.after({ avatar: this.avatar, damage, calc: this, usefulBuffs });
}
logger.debug('最终伤害:', result);
this.damageCache[skill.type] = damage;
return damage;
}
calc() {
return this.skills.map(skill => this.calc_skill(skill)).filter(v => v && !v.skill?.isHide);
}
default(param, value) {
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) {
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) {
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) {
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, usefulBuffs, anomalyData) {
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));
Multiplier = anomalyData.multiplier * times;
}
logger.debug(`倍率:${Multiplier}`);
return Multiplier;
}
/** 获取紊乱倍率 */
get_DiscoverMultiplier(skill, usefulBuffs, anomalyData) {
anomalyData ||= this.get_AnomalyData(skill);
if (!anomalyData)
return;
const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, 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) {
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, initial, skill, usefulBuffs = this.buffM.buffs, isRatio = false) {
return this.cache[type] ??= this.buffM._filter(usefulBuffs, {
element: skill?.element,
range: [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, usefulBuffs) {
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_CRITRate(skill, usefulBuffs) {
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, usefulBuffs) {
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, usefulBuffs) {
const BoostArea = this.get('增伤', 1, skill, usefulBuffs);
logger.debug(`增伤区:${BoostArea}`);
return BoostArea;
}
/** 易伤区 */
get_VulnerabilityArea(skill, usefulBuffs) {
const VulnerabilityArea = this.get('易伤', 1, skill, usefulBuffs);
logger.debug(`易伤区:${VulnerabilityArea}`);
return VulnerabilityArea;
}
/** 抗性区 */
get_ResistanceArea(skill, usefulBuffs) {
const ResistanceArea = this.get('无视抗性', 1 + this.enemy.resistance, skill, usefulBuffs);
logger.debug(`抗性区:${ResistanceArea}`);
return ResistanceArea;
}
/** 穿透值 */
get_Pen(skill, usefulBuffs) {
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, usefulBuffs) {
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, usefulBuffs) {
const get_base = (level) => 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, usefulBuffs) {
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, usefulBuffs) {
const AnomalyProficiency = this.get_AnomalyProficiency(skill, usefulBuffs);
const AnomalyProficiencyArea = AnomalyProficiency / 100;
logger.debug(`异常精通区:${AnomalyProficiencyArea}`);
return AnomalyProficiencyArea;
}
/** 异常增伤区 */
get_AnomalyBoostArea(skill, usefulBuffs) {
const AnomalyBoostArea = this.get('异常增伤', 1, skill, usefulBuffs);
logger.debug(`异常增伤区:${AnomalyBoostArea}`);
return AnomalyBoostArea;
}
/** 异常暴击率 */
get_AnomalyCRITRate(skill, usefulBuffs) {
let AnomalyCRITRate = this.get('异常暴击率', 0, skill, usefulBuffs);
AnomalyCRITRate = Math.max(0, Math.min(AnomalyCRITRate, 1));
logger.debug(`异常暴击率:${AnomalyCRITRate}`);
return AnomalyCRITRate;
}
/** 异常暴击伤害 */
get_AnomalyCRITDMG(skill, usefulBuffs) {
let AnomalyCRITDMG = this.get('异常暴击伤害', 1, skill, usefulBuffs);
AnomalyCRITDMG = Math.max(0, Math.min(AnomalyCRITDMG, 5));
logger.debug(`异常暴击伤害:${AnomalyCRITDMG}`);
return AnomalyCRITDMG;
}
/** 异常持续时间 */
get_AnomalyDuration(skill, usefulBuffs, duration = 0) {
const AnomalyDuration = +this.get('异常持续时间', duration, skill, usefulBuffs).toFixed(1);
logger.debug(`异常持续时间:${AnomalyDuration}`);
return AnomalyDuration;
}
/** 生命值 */
get_HP(skill, usefulBuffs) {
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, usefulBuffs) {
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, usefulBuffs) {
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, usefulBuffs) {
let AnomalyMastery = this.get('异常掌控', this.initial_properties.AnomalyMastery, skill, usefulBuffs, true);
AnomalyMastery = Math.max(0, Math.min(AnomalyMastery, 1000));
logger.debug(`异常掌控:${AnomalyMastery}`);
return AnomalyMastery;
}
}

545
model/damage/Calculator.ts Normal file
View file

@ -0,0 +1,545 @@
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
/** 自定义计算逻辑 */
dmg?: (calc: Calculator) => damage
/** 额外处理 */
before?: ({ avatar, usefulBuffs, calc }: {
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
}
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<T extends keyof enemy>(key: T, value: enemy[T]): void
defEnemy(enemy: enemy): void
defEnemy<T extends keyof enemy>(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 (this.damageCache[skill.type]) return this.damageCache[skill.type]
logger.debug(`${logger.green(skill.type)}伤害计算:`)
this.cache = {}
if (skill.dmg) return skill.dmg(this)
/** 缩小筛选范围 */
const usefulBuffs = this.buffM.filter({
element: skill.element,
range: [skill.type]
}, this)
if (skill.before) skill.before({ 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)
) || 0
} else {
if (skill.skillMultiplier) Multiplier = skill.skillMultiplier[this.get_SkillLevel(skill.type[0]) - 1]
else Multiplier = this.get_Multiplier(skill.type)
}
if (!Multiplier) return logger.warn('技能倍率缺失:', skill)
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
}
skill.after({ avatar: this.avatar, damage, calc: this, usefulBuffs })
}
logger.debug('最终伤害:', result)
this.damageCache[skill.type] = damage
return damage
}
calc() {
return this.skills.map(skill => this.calc_skill(skill)).filter(v => v && !v.skill?.isHide)
}
/**
* buff参数的默认值
* @param obj
*/
default(obj: { [key in keyof skill]?: skill[key] }): void
/**
*
* @param value undefined时删除默认值
*/
default<T extends keyof skill>(type: T, value?: skill[T]): void
default<T extends keyof skill>(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[], anomalyData?: typeof AnomalyData[number]) {
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))
Multiplier = anomalyData.multiplier * times
}
logger.debug(`倍率:${Multiplier}`)
return Multiplier
}
/** 获取紊乱倍率 */
get_DiscoverMultiplier(skill: skill, usefulBuffs: buff[], anomalyData?: typeof AnomalyData[number]) {
anomalyData ||= this.get_AnomalyData(skill)
if (!anomalyData) return
const AnomalyDuration = this.get_AnomalyDuration(skill, usefulBuffs, 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?.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_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
}
}

314
model/damage/README.md Normal file
View file

@ -0,0 +1,314 @@
# 伤害计算自定义
> 注意:如需自定义伤害计算,请先确保你拥有对伤害计算的基础知识,可参考[此帖][伤害计算]了解伤害计算的组成部分与计算方法。
角色伤害计算文件为[character/角色名/calc.js](./character/),如需自定义,将**calc.js**复制一份并重命名为**calc_user.js**后自行修改其中逻辑即可。**data.json**为技能倍率、天赋加成数据,如需自定义,同理将**data.json**复制一份并重命名为**data_user.json**后自行修改即可。[套装计算](./set/)和[武器计算](./weapon/)同理。
如需新增角色伤害计算,可复制[模板](./character/模板/)并重命名后修改相应逻辑。
后文将带你入门插件伤害计算逻辑,只要你需要自定义伤害计算,都建议你完整阅读:
> 开发者已对伤害计算进行了规范化、模块化只需要填参数就可以实现常见的伤害计算逻辑即使不懂代码也可以参考模板独立完成。若存在问题可于插件群内提问或联系我的邮箱UCPr251@gmail.com
伤害计算需要明确这几部分:**初始属性**、**局内Buff**、**技能属性**、**敌方属性**,后文将分别说明
## 初始属性
初始属性,即**局外**时代理人的面板属性(参见[上帖][伤害计算]完整描述)
在伤害计算逻辑中,此部分数据可直接获取,不作过多说明
## 局内Buff
局内Buff即仅在局内生效的所有增益一个buff只能对单一属性进行增益但可以同时作用于多个技能。合理管控buff是做好伤害计算最重要的一步
### 认识buff
每个buff由各项[buff参数](./BuffManager.ts#L46)组成,重要参数:
```JS
{
/** Buff名称 */
name: string
/** Buff来源 */
source: buffSource
/** Buff增益的类型 */
type: buffType
/**
* Buff增益数值可为数值、数组、函数、字符串
* @number
* - 一般情况下此值即为提高值
* - 当buff增益类型为攻击力/冲击力/异常精通/异常掌控/防御力/生命值时,若此值<1则将此值理解为初始属性的百分比提高
* @array
* 根据buff.source自动选择对应等级/星级的值,支持:
* - Weapon武器星级进阶
* - Talent/Addition天赋核心技等级
* @function
* 函数返回值则为提高值
* @string
* 角色自身的buff提高值可能随技能/天赋等级提高而提高此时可以于data.json的"buff"中添加对应的倍率信息此时value即为键名其首字母必须为对应技能的基类参考技能类型命名标准
*/
value: number | Function | string
/** Buff增益技能类型范围无则对全部生效参考技能类型命名标准 */
range?: string[]
/** Buff增益属性类型无则对全部属性生效 */
element?: element | element[]
}
```
额外说明:
- **name**Buff名称。可重复
- **source**Buff来源。用于管理buff、简化参数、判断生效条件等。查看[buff来源](./BuffManager.ts#L30)
- **type**Buff增益的类型。查看[增益类型](./BuffManager.ts#L32)
- **value**Buff增益值。具体解释如上述
- **range**Buff增益技能类型范围。该参数用于鉴别不同buff的[生效范围](#技能类型命名对buff作用的影响)(比如只对普攻生效),[填写方法](#技能类型命名标准)会在技能属性中详细说明
- **element**Buff增益属性类型。该参数用于鉴别不同buff的生效属性比如只对冰属性伤害生效。查看[属性类型](./BuffManager.ts#L6)
### 注册buff
伤害计算模块提供了注册、管理各buff的接口[BuffManager](./BuffManager.ts)所有buff都需要通过此类的实例**buffM**进行注册、管理
Buff来源可分为三大类武器、套装、角色影画、核心被动、额外能力、技能buff的注册也分为此三步骤、三部分。具体计算文件分别位于[武器](./weapon/) [套装](./set/) [角色](./character/)
- 自定义方法:复制对应的计算文件,重命名为**原名_user.js**星见雅_user.js修改相应逻辑重启即可
- buff注册方法武器、套装、角色每部分buff的注册我都为你提供了两种方式
- 一:[直接导出](./character/模板/calc.js#L66)在计算文件中导出buffs数组数组中保存各buff即可
- 二:[函数导出](./character/模板/calc.js#L1):调用[BuffManager](./BuffManager.ts)实例**buffM**的**new**方法传入你需要注册的buff即可
- 当上述两方式同时存在时,会**先**注册**直接导出**的buffs**然后**调用**导出的函数**
- 一般情况下,都更推荐**直接导出**的方法。如果你需要更灵活的导出方式,可以选择使用函数导出。
- buff注册原则应尽可能地将能够吃到的buff都注册上不考虑失衡易伤和任何需失衡触发的buff
后文将说明武器、套装、角色三部分各自的buff注册细则
### 武器Buff
[武器buff计算文件模板](./weapon/模板.js)
[武器buff两种导出方式实例](./weapon/霰落星殿.js)
注意事项:
- 武器的**基础属性**和**高级属性**皆已计入[**初始属性**](#初始属性),无需处理
- 武器的**音擎效果**需要自己注册
- 武器buff的**value**值一般为数组类型,具体参考[认识buff](#认识buff)部分
- 武器与角色的职业检查会自动进行
### 套装Buff
[套装buff计算文件模板](./set/模板.js)
[套装buff两种导出方式实例](./set/折枝剑歌.js)
注意事项:
- 主词条的提升会自动注册,无需处理
- 二件套效果只有**属性伤害提升**需要注册,其他已包含于初始属性
- 四件套效果需要自己注册
### 角色Buff
[角色buff计算文件模板](./character/模板/calc.js)
[角色buff两种导出方式实例](./character/星见雅/calc.js)
角色buff分为影画、核心被动、额外能力、技能此四个来源
- **影画·Rank**
**name**建议按照模板填写,此时命座检查会自动进行
- **核心被动·Talent**
核心被动中的**buff增益值**可能随核心技等级提升而提升,此时**value**的类型对应字符串情况并且需要于data.json中添加对应的倍率信息你可参考[安东伤害计算](./character/安东/calc.js#L17)的处理
- **额外能力·Addition**
额外能力的阵营效果直接视为生效,正常注册即可
- **技能·Skill**
部分角色释放技能后会给自己附加增益,正常注册即可
### 管理buff
- [BuffManager](./BuffManager.ts)提供了部分管理buff的函数可自行查看使用
- 较为推荐的管理buff方式为在使用**直接导出**注册相应的buff的基础上通过**导出函数**来管理buff在函数中调整各buff
- **在线调试**:将云崽底层的日志类型(根目录/config/config/bot.yaml中的log_level)修改为**debug**并重启后插件会自动监听当前各计算文件实时更新并会在控制台输出伤害计算的详细过程初始属性、buff情况、技能数据、buff生效情况、各乘区数据可据此调试
- 游戏中的buff生效情况难以确定但通过[自定义敌方属性](#自定义敌方属性)和对buff的精确管控插件的计算结果将与游戏实机十分吻合
<p align="center">
<img width="251" src="https://s2.loli.net/2025/01/14/o6mi3LKdgGtT2RP.jpg" title="她真好看">
<img width="251" src="https://s2.loli.net/2025/01/14/Ue5kLpha7N621Px.jpg" title="她真好看">
</p>
## 技能属性
技能属性,即在代理人技能详情界面对该技能的描述:技能名、伤害倍率、伤害类型等
### 认识技能
技能为泛指,任何可以造成伤害的输出手段,此处都可称为技能,包括属性异常、紊乱等
每个技能由各项[技能参数](./Calculator.ts#L9)组成,必需参数:
```JS
{
/** 技能名,唯一 */
name: string
/** 技能类型,唯一,参考技能类型命名标准 */
type: string
/** 属性类型,不指定时,默认取角色属性 */
element: element
}
```
### 技能类型
由于不同Buff作用的技能类型不同为了统一判断某Buff是否对某技能生效规定**技能类型命名标准**
#### 技能类型命名标准
> - A 攻击
> - AP 普通攻击
> - AX 重击/蓄力攻击
> - AQ 强化普攻
> - C 闪避
> - CP 普通闪避
> - CF 闪避反击
> - CC 冲刺攻击
> - CX 蓄力闪避
> - L 支援技
> - LK 快速支援
> - LZ 招架支援
> - LT 突击支援
> - E 特殊技
> - EP 普通特殊技
> - EQ 强化特殊技
> - R
> - RZ 终结技
> - RL 连携技
> - T 核心技
> - 核心技中的技能各不相同,自行定义即可
> - 属性异常(特殊)
> - 强击
> - 灼烧
> - 碎冰
> - 感电
> - 侵蚀
> - 紊乱
#### 技能类型命名解释说明
1. 首字母为技能所属基类,不可更改、不可单独作为技能名,后跟字母表示技能分支
2. 树状命名,后一位字母代表基于其前一位字母的分支,取技能名发音(倒着读);属性异常较特殊,直接以异常名作为技能类型名
3. 后跟数字可表示段数如AP1表示第一段普攻为避免混淆数字仅表示同一技能不同段数不用于区分不同技能
4. 当不需要进一步细分分支时必须遵守此标准命名否则可能导致Buff计算错误
5. 当需进一步细分多种分支时,应基于此标准已有的命名拓展命名,并确保前后一致
#### 技能类型命名示例
艾莲的冲刺攻击有三种不同的类型,为了区分,需要对其进行拓展
冲刺攻击类型命名标准为**CC**,故:
- “冲刺攻击:寒潮”可表示为**CCP**(普通冲刺攻击);
- “冲刺攻击:骇浪”可表示为**CCQ**(强化冲刺攻击);
- “冲刺攻击:冰渊潜袭”可表示为**CCX**(巡游冲刺攻击),其又分为普通和蓄力两种,又可分别表示为:
- 普通巡游冲刺攻击:**CCXP**
- 蓄力巡游冲刺攻击:**CCXX**
[点此查看](./character/艾莲/calc.js#L40)艾莲实际伤害计算文件
当然,若冲刺攻击不存在变体,则直接使用**CC**命名即可
#### 技能类型命名对Buff作用的影响
技能类型命名将直接决定某buff是否作用于某技能这也是规定标准命名的原因
buff作用范围将以技能类型命名为依据向后覆盖。以上述[艾莲冲刺攻击命名示例](#技能类型命名示例)为例在某buff的作用范围数组即buff的**range**参数)中:
- 如果包括**C**则代表对所有基于C闪避的分支都生效包括CP、CF、CC、CX等
- 如果包括**CC**则代表对所有基于CC冲刺攻击的分支都生效
- 如果只包括**CCQ**(强化冲刺攻击),则代表只对“冲刺攻击:骇浪”生效
- 如果只包括**CCX**(巡游冲刺攻击),则代表对“冲刺攻击:冰渊潜袭”生效(无论普通或蓄力)
- 如果只包括**CCXX**(蓄力巡游冲刺攻击),则代表只对“冲刺攻击:冰渊潜袭”的蓄力巡游冲刺攻击生效
### 技能倍率
[点此查看模板技能倍率](./character/模板/data.json)
不同等级的技能倍率不同,新增某技能的伤害计算时需要你手动添加对应的倍率信息
技能倍率保存在character/角色名/**data.json**中json数据的skill中的各个**键**即为技能类型**type****值**即为每个等级对应的倍率数组长度16
需要自定义data.json时同样复制一份重命名为**data_user.json**即可
### 注册技能
伤害计算模块提供了注册各技能的接口[Calculator](./Calculator.ts),所有技能都需要通过此类的实例**calc**进行注册
技能的注册较为简单:
1. 参考[模板](./character/模板/calc.js#L115)填入各技能相应参数一般只需要填name、type参数
2. 于[data.json](./character/模板/data.json)中为每个技能填写倍率(异常伤害无需填写)
注意事项:
- 若某技能所造成伤害的属性与角色属性不符,应指定该技能的属性**element**
- 技能的参数有较多可选的拓展,用于处理更复杂的情况,请自行查看[源码](./Calculator.ts)和已有角色的计算案例
- 目前只可注册角色的技能,部分武器有独立的造成额外伤害的机制,暂不考虑
## 敌方属性
影响伤害计算的敌方属性有:防御力、抗性、弱点
### 敌方属性的影响
- **防御力**:影响防御乘区。影响直伤和异常伤害计算
- **抗性、弱点**:影响抗性乘区。影响直伤和异常伤害计算
敌方属性可查看[此表](https://img.nga.178.com/attachments/mon_202407/16/axvkQq44x-2xpiZyT3cSwm-1hf.png)
### 自定义敌方属性
可以用 **等级、1级基础防御力、抗性区常量** 三个参数来替代**防御力、抗性、弱点**
- **等级、1级基础防御力**:根据这两个参数可计算出敌方防御力。插件默认敌方等级=角色等级1级基础防御力=50
- **抗性区常量**:敌方存在对应的弱点时,此值为 **0.2**;敌方存在对应的抗性时,此值为 **-0.2**;既不抗也不弱,此值为 **0**。插件默认抗性区常量=0.2
通过在角色伤害计算文件中导出**calc**函数调用Calculator的[defEnemy](./Calculator.ts)方法,你可以对此三个参数进行自定义
目前暂不支持指令自定义,暂不支持直接指定具体敌人
---
[伤害计算]:https://www.miyoushe.com/zzz/article/55265618

View file

@ -1,806 +1,149 @@
import _ from 'lodash';
import { getMapData } from '../../utils/file.js';
import { calculate_damage } from './role.js';
import { ZZZAvatarInfo } from '../avatar.js';
const skilldict = getMapData('SkillData');
/**
* 角色加成
* @param {ZZZAvatarInfo} data 角色信息
* @param {ZZZAvatarInfo['damage_basic_properties']['base_detail']} base_detail 基础属性
* @param {ZZZAvatarInfo['damage_basic_properties']['bonus_detail']} bonus_detail 套装加成
* @returns {{
* title: string,
* value: {name: string, value: number}[]
* }[]} 伤害列表
*/
export const avatar_ability = (data, base_detail, bonus_detail) => {
const damagelist = [];
switch (data.id) {
// 艾莲
case 1191: {
/** 处理命座加成 */
if (data.rank >= 1) {
const CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.12;
}
if (data.rank >= 2) {
const ES_CriticalDamageBase = _.get(
bonus_detail,
'ES_CriticalDamageBase',
0
);
bonus_detail['ES_CriticalDamageBase'] = ES_CriticalDamageBase + 0.6;
const EH_CriticalDamageBase = _.get(
bonus_detail,
'EH_CriticalDamageBase',
0
);
bonus_detail['EH_CriticalDamageBase'] = EH_CriticalDamageBase + 0.6;
}
if (data.rank >= 6) {
const PenRatio = _.get(bonus_detail, 'PenRatioBase', 0);
bonus_detail['PenRatioBase'] = PenRatio + 0.2;
const C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + 2.5;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const CDB = getskilllevelnum(data.id, data.skills, 'T', 'T');
const C_CriticalDamageBase = _.get(
bonus_detail,
'C_CriticalDamageBase',
0
);
bonus_detail['C_CriticalDamageBase'] = C_CriticalDamageBase + CDB;
const A_CriticalDamageBase = _.get(
bonus_detail,
'A_CriticalDamageBase',
0
);
bonus_detail['A_CriticalDamageBase'] = A_CriticalDamageBase + CDB;
const IceDmgAdd = _.get(bonus_detail, 'Ice_DmgAdd', 0);
bonus_detail['Ice_DmgAdd'] = IceDmgAdd + 0.3;
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(
data.id,
data.skills,
'A',
'A'
);
const damagelist1 = calculate_damage(
base_detail,
bonus_detail,
'A',
'A',
'Ice',
skill_multiplier1,
data.level
);
const damage1 = {
title: '普通攻击:急冻修剪法',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(
data.id,
data.skills,
'C',
'C'
);
const damagelist2 = calculate_damage(
base_detail,
bonus_detail,
'C',
'C',
'Ice',
skill_multiplier2,
data.level
);
const damage2 = {
title: '冲刺攻击:冰渊潜袭',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(
data.id,
data.skills,
'E',
'EH'
);
const damagelist3 = calculate_damage(
base_detail,
bonus_detail,
'EUP',
'EH',
'Ice',
skill_multiplier3,
data.level
);
const damage3 = {
title: '强化特殊技:横扫',
value: damagelist3,
};
damagelist.push(damage3);
const skill_multiplier4 = getskilllevelnum(
data.id,
data.skills,
'E',
'ES'
);
const damagelist4 = calculate_damage(
base_detail,
bonus_detail,
'EUP',
'ES',
'Ice',
skill_multiplier4,
data.level
);
const damage4 = {
title: '强化特殊技:鲨卷风',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算连携技伤害 */
const skill_multiplier5 = getskilllevelnum(
data.id,
data.skills,
'R',
'RL'
);
const damagelist5 = calculate_damage(
base_detail,
bonus_detail,
'RL',
'RL',
'Ice',
skill_multiplier5,
data.level
);
const damage5 = {
title: '连携技:雪崩',
value: damagelist5,
};
damagelist.push(damage5);
/** 计算终结技伤害 */
const skill_multiplier6 = getskilllevelnum(
data.id,
data.skills,
'R',
'R'
);
const damagelist6 = calculate_damage(
base_detail,
bonus_detail,
'R',
'R',
'Ice',
skill_multiplier6,
data.level
);
const damage6 = {
title: '终结技:永冬狂宴',
value: damagelist6,
};
damagelist.push(damage6);
break;
}
// 朱鸢
case 1241: {
/** 处理命座加成 */
if (data.rank >= 2) {
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + 0.5;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + 0.5;
}
if (data.rank >= 4) {
let A_ResistancePenetration = _.get(
bonus_detail,
'A_ResistancePenetration',
0
);
bonus_detail['A_ResistancePenetration'] = A_ResistancePenetration + 0.25;
let C_ResistancePenetration = _.get(
bonus_detail,
'C_ResistancePenetration',
0
);
bonus_detail['C_ResistancePenetration'] = C_ResistancePenetration + 0.25;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const DMG_ADD = getskilllevelnum(data.id, data.skills, 'T', 'T');
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + DMG_ADD;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + DMG_ADD;
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.3;
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(
data.id,
data.skills,
'A',
'A'
);
const damagelist1 = calculate_damage(
base_detail,
bonus_detail,
'A',
'A',
'Ether',
skill_multiplier1,
data.level
);
const damage1 = {
title: '普通攻击:请勿抵抗',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(
data.id,
data.skills,
'C',
'C'
);
const damagelist2 = calculate_damage(
base_detail,
bonus_detail,
'C',
'C',
'Ether',
skill_multiplier2,
data.level
);
const damage2 = {
title: '冲刺攻击:火力压制',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算强化特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(
data.id,
data.skills,
'E',
'EUP'
);
let damagelist3 = calculate_damage(
base_detail,
bonus_detail,
'EUP',
'EUP',
'Ether',
skill_multiplier3,
data.level
);
if (data.rank >= 6) {
let damagelist_add = calculate_damage(
base_detail,
bonus_detail,
'EUP',
'EUP',
'Ether',
2.2,
data.level
);
damagelist3['cd'] = damagelist3['cd'] + damagelist_add['cd'] * 4;
damagelist3['qw'] = damagelist3['qw'] + damagelist_add['qw'] * 4;
}
const damage3 = {
title: '强化特殊技:全弹连射',
value: damagelist3,
};
damagelist.push(damage3);
/** 计算连携技伤害 */
const skill_multiplier4 = getskilllevelnum(
data.id,
data.skills,
'R',
'RL'
);
const damagelist4 = calculate_damage(
base_detail,
bonus_detail,
'RL',
'RL',
'Ether',
skill_multiplier4,
data.level
);
const damage4 = {
title: '连携技:歼灭模式',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算终结技伤害 */
const skill_multiplier5 = getskilllevelnum(
data.id,
data.skills,
'R',
'R'
);
const damagelist5 = calculate_damage(
base_detail,
bonus_detail,
'R',
'R',
'Ether',
skill_multiplier5,
data.level
);
const damage5 = {
title: '终结技歼灭模式MAX',
value: damagelist5,
};
damagelist.push(damage5);
break;
}
// 11号
case 1041: {
/** 处理命座加成 */
if (data.rank >= 2) {
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + 0.36;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + 0.36;
}
if (data.rank >= 6) {
let A_ResistancePenetration = _.get(bonus_detail, 'A_ResistancePenetration', 0);
bonus_detail['A_ResistancePenetration'] = A_ResistancePenetration + 0.25;
let C_ResistancePenetration = _.get(bonus_detail, 'C_ResistancePenetration', 0);
bonus_detail['C_ResistancePenetration'] = C_ResistancePenetration + 0.25;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const DMG_ADD = getskilllevelnum(data.id, data.skills, 'T', 'T');
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + DMG_ADD;
let Fire_DmgAdd = _.get(bonus_detail, 'Fire_DmgAdd', 0);
bonus_detail['Fire_DmgAdd'] = Fire_DmgAdd + 0.1;
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(data.id, data.skills, 'A', 'A');
const damagelist1 = calculate_damage(base_detail, bonus_detail, 'A', 'A', 'Fire', skill_multiplier1, data.level);
const damage1 = {
title: '普通攻击:火力镇压',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(data.id, data.skills, 'C', 'C');
const damagelist2 = calculate_damage(base_detail, bonus_detail, 'C', 'C', 'Fire', skill_multiplier2, data.level);
const damage2 = {
title: '闪避反击:逆火',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算强化特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(data.id, data.skills, 'E', 'E');
let damagelist3 = calculate_damage(base_detail, bonus_detail, 'E', 'E', 'Fire', skill_multiplier3, data.level);
const damage3 = {
title: '强化特殊技:盛燃烈火',
value: damagelist3,
};
damagelist.push(damage3);
/** 计算连携技伤害 */
const skill_multiplier4 = getskilllevelnum(data.id, data.skills, 'R', 'RL');
const damagelist4 = calculate_damage(base_detail, bonus_detail, 'RL', 'RL', 'Fire', skill_multiplier4, data.level);
const damage4 = {
title: '连携技:昂扬烈焰',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算终结技伤害 */
const skill_multiplier5 = getskilllevelnum(data.id, data.skills, 'R', 'R');
const damagelist5 = calculate_damage(base_detail, bonus_detail, 'R', 'R', 'Fire', skill_multiplier5, data.level);
const damage5 = {
title: '终结技:轰鸣烈焰',
value: damagelist5,
};
damagelist.push(damage5);
break;
}
// 青衣
case 1251: {
/** 处理命座加成 */
if (data.rank >= 1) {
let ignore_defence = _.get(bonus_detail, 'ignore_defence', 0);
bonus_detail['ignore_defence'] = ignore_defence + 0.15;
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.2;
}
if (data.rank >= 2) {
let T_stundmgmultiplier = _.get(bonus_detail, 'T_stundmgmultiplier', 0);
bonus_detail['T_stundmgmultiplier'] = T_stundmgmultiplier * 1.35;
}
if (data.rank >= 6) {
let All_ResistancePenetration = _.get(bonus_detail, 'All_ResistancePenetration', 0);
bonus_detail['All_ResistancePenetration'] = All_ResistancePenetration + 0.20;
let A_CriticalDamageBase = _.get(bonus_detail, 'A_CriticalDamageBase', 0);
bonus_detail['A_CriticalDamageBase'] = A_CriticalDamageBase + 1;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const TF = getskilllevelnum(data.id, data.skills, 'T', 'T');
let stundmgmultiplier = _.get(bonus_detail, 'stundmgmultiplier', 0);
bonus_detail['stundmgmultiplier'] = stundmgmultiplier + TF * 20;
let ImpactRatio = _.get(bonus_detail, 'ImpactRatio', 0);
if (ImpactRatio > 120) {
if (ImpactRatio >= 220) {
let AttackDelta = _.get(bonus_detail, 'AttackDelta', 0);
bonus_detail['AttackDelta'] = AttackDelta + 600;
} else {
let AttackDelta = _.get(bonus_detail, 'AttackDelta', 0);
bonus_detail['AttackDelta'] = (AttackDelta - 120) * 6;
}
}
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(data.id, data.skills, 'A', 'A');
const damagelist1 = calculate_damage(base_detail, bonus_detail, 'A', 'A', 'Electric', skill_multiplier1, data.level);
const damage1 = {
title: '普通攻击:醉花月云转',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(data.id, data.skills, 'C', 'C');
const damagelist2 = calculate_damage(base_detail, bonus_detail, 'C', 'C', 'Electric', skill_multiplier2, data.level);
const damage2 = {
title: '闪避反击:意不尽',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算强化特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(data.id, data.skills, 'E', 'E');
let damagelist3 = calculate_damage(base_detail, bonus_detail, 'E', 'E', 'Ether', skill_multiplier3, data.level);
const damage3 = {
title: '强化特殊技:月上海棠',
value: damagelist3,
};
damagelist.push(damage3);
/** 计算连携技伤害 */
const skill_multiplier4 = getskilllevelnum(data.id, data.skills, 'R', 'RL');
const damagelist4 = calculate_damage(base_detail, bonus_detail, 'RL', 'RL', 'Electric', skill_multiplier4, data.level);
const damage4 = {
title: '连携技:太平令',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算终结技伤害 */
const skill_multiplier5 = getskilllevelnum(data.id, data.skills, 'R', 'R');
const damagelist5 = calculate_damage(base_detail, bonus_detail, 'R', 'R', 'Electric', skill_multiplier5, data.level);
const damage5 = {
title: '终结技:八声甘州',
value: damagelist5,
};
damagelist.push(damage5);
break;
}
// 猫又
case 1021: {
/** 处理命座加成 */
if (data.rank >= 1) {
let Physical_ResistancePenetration = _.get(bonus_detail, 'Physical_ResistancePenetration', 0);
bonus_detail['Physical_ResistancePenetration'] = Physical_ResistancePenetration + 0.16;
}
if (data.rank >= 4) {
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase * 0.14;
}
if (data.rank >= 6) {
let CriticalDamageBase = _.get(bonus_detail, 'CriticalDamageBase', 0);
bonus_detail['CriticalDamageBase'] = CriticalDamageBase + 0.54;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const TF = getskilllevelnum(data.id, data.skills, 'T', 'T');
let All_DmgAdd = _.get(bonus_detail, 'All_DmgAdd', 0);
bonus_detail['All_DmgAdd'] = All_DmgAdd + TF;
let E_DmgAdd = _.get(bonus_detail, 'E_DmgAdd', 0);
bonus_detail['E_DmgAdd'] = E_DmgAdd + 0.7;
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(data.id, data.skills, 'A', 'A');
const damagelist1 = calculate_damage(base_detail, bonus_detail, 'A', 'A', 'Physical', skill_multiplier1, data.level);
const damage1 = {
title: '普通攻击:猫猫爪刺',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(data.id, data.skills, 'C', 'C');
const damagelist2 = calculate_damage(base_detail, bonus_detail, 'C', 'C', 'Physical', skill_multiplier2, data.level);
const damage2 = {
title: '闪避反击:虚影双刺',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算强化特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(data.id, data.skills, 'E', 'E');
let damagelist3 = calculate_damage(base_detail, bonus_detail, 'E', 'E', 'Physical', skill_multiplier3, data.level);
const damage3 = {
title: '强化特殊技:超~凶奇袭!',
value: damagelist3,
};
damagelist.push(damage3);
/** 计算连携技伤害 */
const skill_multiplier4 = getskilllevelnum(data.id, data.skills, 'R', 'RL');
const damagelist4 = calculate_damage(base_detail, bonus_detail, 'RL', 'RL', 'Physical', skill_multiplier4, data.level);
const damage4 = {
title: '连携技:刃爪挥击',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算终结技伤害 */
const skill_multiplier5 = getskilllevelnum(data.id, data.skills, 'R', 'R');
const damagelist5 = calculate_damage(base_detail, bonus_detail, 'R', 'R', 'Physical', skill_multiplier5, data.level);
const damage5 = {
title: '终结技:刃爪强袭',
value: damagelist5,
};
damagelist.push(damage5);
break;
}
// 安东
case 1111: {
/** 处理命座加成 */
if (data.rank >= 4) {
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.1;
}
if (data.rank >= 6) {
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + 0.24;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + 0.24;
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const TF = getskilllevelnum(data.id, data.skills, 'T', 'T');
const TF2 = getskilllevelnum(data.id, data.skills, 'T', 'T2');
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + TF2;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + TF2;
let E_DmgAdd = _.get(bonus_detail, 'E_DmgAdd', 0);
bonus_detail['E_DmgAdd'] = E_DmgAdd + TF;
let RL_DmgAdd = _.get(bonus_detail, 'RL_DmgAdd', 0);
bonus_detail['RL_DmgAdd'] = RL_DmgAdd + TF;
let R_DmgAdd = _.get(bonus_detail, 'R_DmgAdd', 0);
bonus_detail['R_DmgAdd'] = R_DmgAdd + TF;
/** 计算伤害 */
/** 计算普攻伤害 */
const skill_multiplier1 = getskilllevelnum(data.id, data.skills, 'A', 'A');
const damagelist1 = calculate_damage(base_detail, bonus_detail, 'A', 'A', 'Electric', skill_multiplier1, data.level);
const damage1 = {
title: '普通攻击:热血上工操',
value: damagelist1,
};
damagelist.push(damage1);
/** 计算冲刺伤害 */
const skill_multiplier2 = getskilllevelnum(data.id, data.skills, 'C', 'C');
const damagelist2 = calculate_damage(base_detail, bonus_detail, 'C', 'C', 'Electric', skill_multiplier2, data.level);
const damage2 = {
title: '闪避反击:过载钻击',
value: damagelist2,
};
damagelist.push(damage2);
/** 计算强化特殊技伤害 */
const skill_multiplier3 = getskilllevelnum(data.id, data.skills, 'E', 'E');
let damagelist3 = calculate_damage(base_detail, bonus_detail, 'E', 'E', 'Electric', skill_multiplier3, data.level);
const damage3 = {
title: '特殊技:爆发钻击',
value: damagelist3,
};
damagelist.push(damage3);
/** 计算连携技伤害 */
const skill_multiplier4 = getskilllevelnum(data.id, data.skills, 'R', 'RL');
const damagelist4 = calculate_damage(base_detail, bonus_detail, 'RL', 'RL', 'Electric', skill_multiplier4, data.level);
const damage4 = {
title: '连携技:转转转!',
value: damagelist4,
};
damagelist.push(damage4);
/** 计算终结技伤害 */
const skill_multiplier5 = getskilllevelnum(data.id, data.skills, 'R', 'R');
const damagelist5 = calculate_damage(base_detail, bonus_detail, 'R', 'R', 'Electric', skill_multiplier5, data.level);
const damage5 = {
title: '终结技:转转转转转!',
value: damagelist5,
};
damagelist.push(damage5);
break;
}
// 星见雅
case 1091: {
/** 处理命座加成 */
if (data.rank >= 1) {
const AUP_IgnoreDefence = _.get(bonus_detail, 'AUP_IgnoreDefence', 0)
bonus_detail['AUP_IgnoreDefence'] = AUP_IgnoreDefence + 0.36
}
if (data.rank >= 2) {
const A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0)
bonus_detail['A_DmgAdd'] = A_DmgAdd + 0.30
const C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0)
bonus_detail['C_DmgAdd'] = C_DmgAdd + 0.30
const CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0)
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.15
}
if (data.rank >= 4) {
const TP_DmgAdd = _.get(bonus_detail, 'TP_DmgAdd', 0)
bonus_detail['TP_DmgAdd'] = TP_DmgAdd + 0.3
}
if (data.rank >= 6) {
const AUP_DmgAdd = _.get(bonus_detail, 'AUP_DmgAdd', 0)
bonus_detail['AUP_DmgAdd'] = AUP_DmgAdd + 0.3
}
/** 处理天赋加成 */
/** 获取天赋等级与加成倍率 */
const AUP_DmgAdd = _.get(bonus_detail, 'AUP_DmgAdd', 0)
bonus_detail['AUP_DmgAdd'] = AUP_DmgAdd + 0.6
const AUP_ResistancePenetration = _.get(bonus_detail, 'AUP_ResistancePenetration', 0)
bonus_detail['AUP_ResistancePenetration'] = AUP_ResistancePenetration + 0.3 // 紊乱无视冰抗
const R_DmgAdd = _.get(bonus_detail, 'R_DmgAdd', 0) // 终结技30%冰伤加成
bonus_detail['R_DmgAdd'] = R_DmgAdd + 0.3
logger.debug('最终bonus_detail', bonus_detail)
/** 计算伤害 */
/** 计算普攻伤害 */
const A_multiplier = getskilllevelnum(data.id, data.skills, 'A', 'A')
const A_damage = calculate_damage(base_detail, bonus_detail, 'A', 'A', 'Ice', A_multiplier, data.level)
damagelist.push({
title: '普通攻击:风花五段',
value: A_damage,
})
/** 计算闪避反击伤害 */
const C_multiplier = getskilllevelnum(data.id, data.skills, 'C', 'C')
const C_damage = calculate_damage(base_detail, bonus_detail, 'C', 'C', 'Ice', C_multiplier, data.level)
damagelist.push({
title: '闪避反击:寒雀',
value: C_damage,
})
/** 计算霜灼·破伤害 */
const TP_multiplier = getskilllevelnum(data.id, data.skills, 'T', 'TP')
const TP_damage = calculate_damage(base_detail, bonus_detail, 'TP', 'TP', 'Ice', TP_multiplier, data.level)
damagelist.push({
title: '霜灼·破',
value: TP_damage,
})
/** 计算蓄力攻击伤害 */
const AUP1_multiplier = getskilllevelnum(data.id, data.skills, 'A', 'AUP1')
const AUP1_damage = calculate_damage(base_detail, bonus_detail, 'AUP', 'AUP1', 'Ice', AUP1_multiplier, data.level)
damagelist.push({
title: '蓄力攻击:一段蓄',
value: AUP1_damage,
})
const AUP2_multiplier = getskilllevelnum(data.id, data.skills, 'A', 'AUP2')
const AUP2_damage = calculate_damage(base_detail, bonus_detail, 'AUP', 'AUP2', 'Ice', AUP2_multiplier, data.level)
if (data.rank >= 6) { // 6命累加蓄力伤害
AUP2_damage['cd'] += AUP1_damage['cd']
AUP2_damage['qw'] += AUP1_damage['qw']
}
damagelist.push({
title: '蓄力攻击:二段蓄',
value: AUP2_damage,
})
const AUP3_multiplier = getskilllevelnum(data.id, data.skills, 'A', 'AUP3')
const AUP3_damage = calculate_damage(base_detail, bonus_detail, 'AUP', 'AUP3', 'Ice', AUP3_multiplier, data.level)
if (data.rank >= 6) {
AUP3_damage['cd'] += AUP2_damage['cd']
AUP3_damage['qw'] += AUP2_damage['qw']
}
damagelist.push({
title: '蓄力攻击:三段蓄',
value: AUP3_damage,
})
/** 计算特殊技伤害 */
const E_multiplier = getskilllevelnum(data.id, data.skills, 'E', 'EF')
const E_damage = calculate_damage(base_detail, bonus_detail, 'EUP', 'EF', 'Ice', E_multiplier, data.level)
damagelist.push({
title: '强化特殊技:飞雪',
value: E_damage,
})
const EZ_multiplier = getskilllevelnum(data.id, data.skills, 'E', 'EF2')
const EZ_damage = calculate_damage(base_detail, bonus_detail, 'EUP', 'EF2', 'Ice', EZ_multiplier, data.level)
damagelist.push({
title: '强化特殊技:飞雪(二段)',
value: EZ_damage,
})
/** 计算连携技伤害 */
const RL_multiplier = getskilllevelnum(data.id, data.skills, 'R', 'RL')
const RL_damage = calculate_damage(base_detail, bonus_detail, 'RL', 'RL', 'Ice', RL_multiplier, data.level)
damagelist.push({
title: '连携技:春临',
value: RL_damage,
})
/** 计算终结技伤害 */
const R_multiplier = getskilllevelnum(data.id, data.skills, 'R', 'R')
const R_damage = calculate_damage(base_detail, bonus_detail, 'R', 'R', 'Ice', R_multiplier, data.level)
damagelist.push({
title: '终结技:名残雪',
value: R_damage,
})
break
}
}
logger.debug(logger.green(data.name_mi18n + '伤害:'), damagelist)
return damagelist
};
export const getskilllevelnum = (avatarId, skills, skilltype, skillname) => {
let skill_typeid = 0;
if (skilltype == 'A') skill_typeid = 0;
else if (skilltype == 'C') skill_typeid = 2;
else if (skilltype == 'E') skill_typeid = 1;
else if (skilltype == 'R') skill_typeid = 3;
else if (skilltype == 'L') skill_typeid = 6;
else if (skilltype == 'T') skill_typeid = 5;
let skilllevel =
Number(
skills.find(property => property.skill_type === skill_typeid)?.level || 1
) - 1;
return skilldict[avatarId][skillname][skilllevel];
};
export const has_calculation = avatarId => {
return Object.keys(skilldict).includes(avatarId.toString());
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';
import { Calculator } from './Calculator.js';
import chokidar from 'chokidar';
import path from 'path';
import fs from 'fs';
const damagePath = path.join(pluginPath, 'model', 'damage');
export const charData = {};
const calcFnc = {
character: {},
weapon: {},
set: {}
};
async function init() {
const isWatch = config.bot.log_level === 'debug'; // debug模式下监听文件变化
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'))
.map(v => importFile(type, v.replace('.js', ''), isWatch)));
}
}
function watchFile(path, fnc) {
if (!fs.existsSync(path))
return;
const watcher = chokidar.watch(path, {
awaitWriteFinish: {
stabilityThreshold: 50
}
});
watcher.on('change', (path) => {
logger.debug('重载' + path);
fnc();
});
}
async function importChar(charName, isWatch = false) {
const id = aliasToID(charName);
if (!id)
return logger.warn(`未找到角色${charName}的ID`);
const dir = path.join(damagePath, 'character', charName);
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 {
if (isWatch) {
watchFile(path.join(dir, calcFile), () => importChar(charName));
watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')));
}
charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`);
if (!m.calc && (!m.buffs || !m.skills))
throw new Error('伤害计算文件格式错误');
calcFnc.character[id] = m;
}
catch (e) {
logger.error(`导入角色${charName}伤害计算错误:`, e);
}
}
async function importFile(type, name, isWatch = false) {
const defaultFilePath = path.join(damagePath, type, `${name}.js`);
const userFilePath = path.join(damagePath, type, `${name}_user.js`);
const isUser = fs.existsSync(userFilePath);
const filePath = isUser ? userFilePath : defaultFilePath;
try {
if (isWatch) {
watchFile(filePath, () => importFile(type, name));
}
const m = await import(`./${type}/${name}${isUser ? '_user' : ''}.js?${Date.now()}`);
if (!m.calc && !m.buffs)
throw new Error(type + ' Buff计算文件格式错误');
calcFnc[type][name] = m;
}
catch (e) {
logger.error(`导入${type}/${name}.js错误`, e);
}
}
await init();
/** 角色计算 */
export function avatar_ability(avatar) {
const m = calcFnc.character[avatar.id];
if (!m)
return [];
const buffM = new BuffManager(avatar);
const calc = new Calculator(buffM);
logger.debug('initial_properties', avatar.initial_properties);
weapon_buff(avatar.weapon, buffM);
set_buff(avatar.equip, buffM);
if (m.buffs)
buffM.new(m.buffs);
if (m.skills)
calc.new(m.skills);
if (m.calc)
m.calc(buffM, calc, avatar);
logger.debug(`Buff*${buffM.buffs.length}`, buffM.buffs);
return calc.calc();
}
/** 武器加成 */
export function weapon_buff(equipment, buffM) {
const name = equipment.name;
logger.debug('武器:' + name);
const m = calcFnc.weapon[name];
if (!m)
return;
buffM.default({ name, source: 'Weapon' });
if (m.buffs)
buffM.new(m.buffs);
if (m.calc)
m.calc(buffM, equipment.star);
buffM.default({});
}
/** 套装加成 */
export function set_buff(equip, buffM) {
buffM.default({ name: '', source: 'Set' });
const setCount = {};
for (const equip_detail of equip) {
if (equip_detail.equipment_type == 5) {
// 属伤加成
const index = [31503, 31603, 31703, 31803, 31903].indexOf(equip_detail.main_properties[0].property_id);
if (index > -1 && elementEnum[index]) {
// @ts-ignore
buffM.new({
name: '驱动盘5号位',
type: '增伤',
value: Number(equip_detail.main_properties[0].base.replace('%', '')) / 100,
isForever: true,
element: elementEnum[index]
});
}
}
const suit_name = String(equip_detail.equip_suit.name);
setCount[suit_name] = (setCount[suit_name] || 0) + 1;
}
buffM.setCount = setCount;
for (const [name, count] of Object.entries(setCount)) {
if (count < 2)
continue;
logger.debug(`套装:${name}*${count}`);
const m = calcFnc.set[name];
if (!m)
continue;
buffM.default('name', name);
if (m.buffs)
buffM.new(m.buffs);
if (m.calc)
m.calc(buffM, count);
}
buffM.default({});
}

172
model/damage/avatar.ts Normal file
View file

@ -0,0 +1,172 @@
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'
import { Calculator } from './Calculator.js'
import chokidar from 'chokidar'
import path from 'path'
import fs from 'fs'
const damagePath = path.join(pluginPath, 'model', 'damage')
export const charData: {
[id: number]: {
skill: { [skillName: string]: number[] }
buff: { [buffName: string]: number[] }
}
} = {}
const calcFnc: {
character: {
[id: number]: {
calc?: (buffM: BuffManager, calc: Calculator, avatar: ZZZAvatarInfo) => void
buffs: buff[]
skills: skill[]
}
}
weapon: {
[name: string]: {
calc?: (buffM: BuffManager, star: number) => void
buffs: buff[]
}
}
set: {
[name: string]: {
calc?: (buffM: BuffManager, count: number) => void
buffs: buff[]
}
}
} = {
character: {},
weapon: {},
set: {}
}
async function init() {
const isWatch = config.bot.log_level === 'debug' // debug模式下监听文件变化
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'))
.map(v => importFile(type as 'weapon' | 'set', v.replace('.js', ''), isWatch))
)
}
}
function watchFile(path: string, fnc: () => void) {
if (!fs.existsSync(path)) return
const watcher = chokidar.watch(path, {
awaitWriteFinish: {
stabilityThreshold: 50
}
})
watcher.on('change', (path) => {
logger.debug('重载' + path)
fnc()
})
}
async function importChar(charName: string, isWatch = false) {
const id = aliasToID(charName)
if (!id) return logger.warn(`未找到角色${charName}的ID`)
const dir = path.join(damagePath, 'character', charName)
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 {
if (isWatch) {
watchFile(path.join(dir, calcFile), () => importChar(charName))
watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')))
}
charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'))
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`)
if (!m.calc && (!m.buffs || !m.skills)) throw new Error('伤害计算文件格式错误')
calcFnc.character[id] = m
} catch (e) {
logger.error(`导入角色${charName}伤害计算错误:`, e)
}
}
async function importFile(type: 'weapon' | 'set', name: string, isWatch = false) {
const defaultFilePath = path.join(damagePath, type, `${name}.js`)
const userFilePath = path.join(damagePath, type, `${name}_user.js`)
const isUser = fs.existsSync(userFilePath)
const filePath = isUser ? userFilePath : defaultFilePath
try {
if (isWatch) {
watchFile(filePath, () => importFile(type, name))
}
const m = await import(`./${type}/${name}${isUser ? '_user' : ''}.js?${Date.now()}`)
if (!m.calc && !m.buffs) throw new Error(type + ' Buff计算文件格式错误')
calcFnc[type][name] = m
} catch (e) {
logger.error(`导入${type}/${name}.js错误`, e)
}
}
await init()
/** 角色计算 */
export function avatar_ability(avatar: ZZZAvatarInfo) {
const m = calcFnc.character[avatar.id]
if (!m) return []
const buffM = new BuffManager(avatar)
const calc = new Calculator(buffM)
logger.debug('initial_properties', avatar.initial_properties)
weapon_buff(avatar.weapon, buffM)
set_buff(avatar.equip, buffM)
if (m.buffs) buffM.new(m.buffs)
if (m.skills) calc.new(m.skills)
if (m.calc) m.calc(buffM, calc, avatar)
logger.debug(`Buff*${buffM.buffs.length}`, buffM.buffs)
return calc.calc()
}
/** 武器加成 */
export function weapon_buff(equipment: ZZZAvatarInfo['weapon'], buffM: BuffManager) {
const name = equipment.name
logger.debug('武器:' + name)
const m = calcFnc.weapon[name]
if (!m) return
buffM.default({ name, source: 'Weapon' })
if (m.buffs) buffM.new(m.buffs)
if (m.calc) m.calc(buffM, equipment.star)
buffM.default({})
}
/** 套装加成 */
export function set_buff(equip: ZZZAvatarInfo['equip'], buffM: BuffManager) {
buffM.default({ name: '', source: 'Set' })
const setCount: { [name: string]: number } = {}
for (const equip_detail of equip) {
if (equip_detail.equipment_type == 5) {
// 属伤加成
const index = [31503, 31603, 31703, 31803, 31903].indexOf(equip_detail.main_properties[0].property_id)
if (index > -1 && elementEnum[index]) {
// @ts-ignore
buffM.new({
name: '驱动盘5号位',
type: '增伤',
value: Number(equip_detail.main_properties[0].base.replace('%', '')) / 100,
isForever: true,
element: elementEnum[index]
})
}
}
const suit_name = String(equip_detail.equip_suit.name)
setCount[suit_name] = (setCount[suit_name] || 0) + 1
}
buffM.setCount = setCount
for (const [name, count] of Object.entries(setCount)) {
if (count < 2) continue
logger.debug(`套装:${name}*${count}`)
const m = calcFnc.set[name]
if (!m) continue
buffM.default('name', name)
if (m.buffs) buffM.new(m.buffs)
if (m.calc) m.calc(buffM, count)
}
buffM.default({})
}

View file

@ -0,0 +1,37 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '无视抗性',
value: 0.25,
element: 'Fire',
range: ['AQ']
},
{
name: '2影',
type: '增伤',
value: 0.03 * 12,
range: ['AP', 'CC', 'CF']
},
{
name: '核心被动:热浪',
type: '增伤',
value: 'T',
range: ['AQ', 'CCQ']
},
{
name: '额外能力:燎原',
type: '增伤',
value: 0.1, // 暂不计入失衡0.225
element: 'Fire'
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻:火力镇压四段', type: 'AQ4' },
{ name: '闪避反击:逆火', type: 'CF' },
{ name: '强化特殊技:盛燃烈火', type: 'EQ' },
{ name: '连携技:昂扬烈焰', type: 'RL' },
{ name: '终结技:轰鸣烈焰', type: 'RZ' }
]

View file

@ -0,0 +1,22 @@
{
"skill": {
"AQ4": [
3.407,3.717,4.027,4.337,4.647,4.957,5.267,5.577,5.887,6.197,6.507,6.817,7.127,7.437,7.747,8.057
],
"CF": [
2.62,2.859,3.098,3.337,3.576,3.815,4.054,4.293,4.532,4.771,5.01,5.249,5.488,5.727,5.966,6.205
],
"EQ": [
6.75,7.364,7.978,8.592,9.206,9.82,10.434,11.048,11.662,12.276,12.89,13.504,14.118,14.732,15.346,15.96
],
"RL": [
6.325,6.9,7.475,8.05,8.625,9.2,9.775,10.35,10.925,11.5,12.075,12.65,13.225,13.8,14.375,14.95
],
"RZ": [
21.03,22.942,24.854,26.766,28.678,30.59,32.502,34.414,36.326,38.238,40.15,42.062,43.974,45.886,47.798,49.71
]
},
"buff": {
"T": [0.35,0.408,0.466,0.525,0.583,0.641,0.7]
}
}

View file

@ -0,0 +1,35 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '增伤',
value: 0.04 * 6,
range: ['AQ', 'CFQ']
},
{
name: '4影',
type: '暴击率',
value: 0.1
},
{
name: '核心被动:兄弟齐心',
type: '增伤',
value: 'T1',
range: ['AP4', 'AQ3', 'EPP', 'EPQ', 'EQ', 'RL', 'RZ']
},
{
name: '核心被动:兄弟齐心',
type: '增伤',
value: 'T2',
range: ['AQ2', 'CFQ', 'LKQ', 'LT'] // 为什么支援突击一半电钻攻击一半打桩攻击???还只写了一个倍率……
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻二段(爆发)', type: 'AQ2' },
{ name: '闪避反击:过载钻击(爆发)', type: 'CFQ' },
{ name: '特殊技:爆发钻击(爆发)', type: 'EPQ' },
{ name: '连携技:转转转!', type: 'RL' },
{ name: '终结技:转转转转转!', type: 'RZ' }
]

View file

@ -0,0 +1,27 @@
{
"skill": {
"AQ2": [
4.692,5.119,5.546,5.973,6.4,6.827,7.254,7.681,8.108,8.535,8.962,9.389,9.816,10.243,10.67,11.097
],
"CFQ": [
4.654,5.078,5.502,5.926,6.35,6.774,7.198,7.622,8.046,8.47,8.894,9.318,9.742,10.166,10.59,11.014
],
"EPQ": [
2.314,2.525,2.736,2.947,3.158,3.369,3.58,3.791,4.002,4.213,4.424,4.635,4.846,5.057,5.268,5.479
],
"RL": [
6.407,6.99,7.573,8.156,8.739,9.322,9.905,10.488,11.071,11.654,12.237,12.82,13.403,13.986,14.569,15.152
],
"RZ": [
18.164,19.816,21.468,23.12,24.772,26.424,28.076,29.728,31.38,33.032,34.684,36.336,37.988,39.64,41.292,42.944
]
},
"buff": {
"T1": [
0.12,0.14,0.16,0.18,0.2,0.22,0.24
],
"T2": [
0.2,0.233,0.266,0.3,0.333,0.366,0.4
]
}
}

View file

@ -0,0 +1,42 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '无视抗性',
value: 0.15
},
{
name: '2影',
type: '增伤',
value: 0.5,
range: ['CCQ']
},
{
name: '核心被动:破晓',
type: '暴击率',
value: 'T1',
isForever: true,
range: ['CCQ']
},
{
name: '核心被动:破晓',
type: '暴击伤害',
value: 'T2',
range: ['CCQ']
},
{
name: '额外能力:超频',
type: '增伤',
value: 0.4
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻:穿云五段', type: 'AP5' },
{ name: '普攻:落羽', type: 'AX' },
{ name: '冲刺攻击:飞弦·斩', type: 'CCQ3' },
{ name: '强化特殊技:地网', type: 'EQ' },
{ name: '连携技:会·离', type: 'RL' },
{ name: '终结技:残心', type: 'RZ' }
]

View file

@ -0,0 +1,30 @@
{
"skill": {
"AP5": [
1.329,1.45,1.571,1.692,1.813,1.934,2.055,2.176,2.297,2.418,2.539,2.66,2.781,2.902,3.023,3.144
],
"AX": [
1.054,1.15,1.246,1.342,1.438,1.534,1.63,1.726,1.822,1.918,2.014,2.11,2.206,2.302,2.398,2.494
],
"CCQ3": [
1.896,2.069,2.242,2.415,2.588,2.761,2.934,3.107,3.28,3.453,3.626,3.799,3.972,4.145,4.318,4.491
],
"EQ": [
4.493,4.902,5.311,5.72,6.129,6.538,6.947,7.356,7.765,8.174,8.583,8.992,9.401,9.81,10.219,10.628
],
"RL": [
5.176,5.647,6.118,6.589,7.06,7.531,8.002,8.473,8.944,9.415,9.886,10.357,10.828,11.299,11.77,12.241
],
"RZ": [
19.539,21.316,23.093,24.87,26.647,28.424,30.201,31.978,33.755,35.532,37.309,39.086,40.863,42.64,44.417,46.194
]
},
"buff": {
"T1": [
0.106,0.13,0.154,0.178,0.202,0.226,0.25
],
"T2": [
0.36,0.42,0.48,0.54,0.6,0.66,0.72
]
}
}

View file

@ -0,0 +1,173 @@
// 函数导出:
/**
* @param {import('../../BuffManager.ts').BuffManager} buffM
* @param {import('../../Calculator.ts').Calculator} calc
* @param {import('../../../avatar.js').ZZZAvatarInfo} avatar
*/
// export function calc(buffM, calc, avatar) {
// /** 注册buff */
// // 影画加成
// buffM.new({
// name: '6影',
// type: '增伤',
// isForever: true,
// value: 0.30,
// range: ['AX']
// })
// buffM.new({
// name: '4影',
// type: '增伤',
// isForever: true,
// value: 0.30,
// range: ['TP']
// })
// buffM.new({
// name: '2影',
// type: '增伤',
// isForever: true,
// value: 0.30,
// range: ['AP', 'CF']
// })
// buffM.new({
// name: '2影',
// type: '暴击率',
// isForever: true,
// value: 0.15
// })
// buffM.new({
// name: '1影',
// type: '无视防御',
// value: 0.36,
// range: ['AX']
// })
// // 额外能力加成
// buffM.new({
// name: '额外能力:同沐霜雪',
// type: '增伤',
// isForever: true,
// value: 0.6,
// range: ['AX']
// })
// buffM.new({
// name: '额外能力:同沐霜雪',
// type: '无视抗性',
// isForever: true,
// value: 0.3,
// range: ['AX']
// })
// // 技能加成
// buffM.new({
// name: '终结技',
// type: '增伤',
// element: 'Ice',
// range: ['RZ'],
// value: 0.3
// })
// /** 注册技能 */
// calc.new({ name: '碎冰', type: '碎冰' })
// calc.new({ name: '紊乱', type: '紊乱' })
// calc.new({ name: '普通攻击:风花五段', type: 'AP5' })
// calc.new({ name: '闪避反击:寒雀', type: 'CF' })
// calc.new({ name: '霜灼·破', type: 'TP' })
// calc.new({ name: '蓄力攻击:一段蓄', type: 'AX1' })
// calc.new({
// name: '蓄力攻击:二段蓄',
// type: 'AX2',
// after: ({ avatar, damage }) => avatar.rank >= 6 && damage.add('AX1')
// })
// calc.new({
// name: '蓄力攻击:三段蓄',
// type: 'AX3',
// after: ({ avatar, damage }) => avatar.rank >= 6 && damage.add('AX2')
// })
// calc.new({ name: '强化特殊技:飞雪', type: 'EQ' })
// calc.new({ name: '强化特殊技:飞雪(二段)', type: 'EQ2' })
// calc.new({ name: '连携技:春临', type: 'RL' })
// calc.new({ name: '终结技:名残雪', type: 'RZ' })
// }
// 直接导出:
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '增伤',
isForever: true,
value: 0.30,
range: ['AX']
},
{
name: '4影',
type: '增伤',
isForever: true,
value: 0.30,
range: ['TP']
},
{
name: '2影',
type: '暴击率',
isForever: true,
value: 0.15
},
{
name: '2影',
type: '增伤',
isForever: true,
value: 0.30,
range: ['AP', 'CF']
},
{
name: '1影',
type: '无视防御',
value: 0.36,
range: ['AX']
},
{
name: '额外能力:同沐霜雪',
type: '增伤',
isForever: true,
value: 0.6,
range: ['AX']
},
{
name: '额外能力:同沐霜雪',
type: '无视抗性',
isForever: true,
value: 0.3,
range: ['AX']
},
{
name: '终结技',
type: '增伤',
element: 'Ice',
range: ['RZ'],
value: 0.3
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '碎冰', type: '碎冰' },
{ name: '紊乱', type: '紊乱' },
{ name: '普攻:风花五段', type: 'AP5' },
{ name: '闪避反击:寒雀', type: 'CF' },
{ name: '霜灼·破', type: 'TP' },
{ name: '蓄力攻击:一段蓄', type: 'AX1' },
{
name: '蓄力攻击:二段蓄',
type: 'AX2',
after: ({ avatar, damage }) => avatar.rank >= 6 && damage.add('AX1')
},
{
name: '蓄力攻击:三段蓄',
type: 'AX3',
after: ({ avatar, damage }) => avatar.rank >= 6 && damage.add('AX2')
},
{ name: '强化特殊技:飞雪', type: 'EQ' },
{ name: '强化特殊技:飞雪(二段)', type: 'EQ2' },
{ name: '连携技:春临', type: 'RL' },
{ name: '终结技:名残雪', type: 'RZ' }
]

View file

@ -0,0 +1,34 @@
{
"skill": {
"AP5": [
1.29,1.408,1.526,1.644,1.762,1.88,1.998,2.116,2.234,2.352,2.47,2.588,2.706,2.824,2.942,3.06
],
"CF": [
2.459,2.683,2.907,3.131,3.355,3.579,3.803,4.027,4.251,4.475,4.699,4.923,5.147,5.371,5.595,5.819
],
"AX1": [
4.547,4.961,5.375,5.789,6.203,6.617,7.031,7.445,7.859,8.273,8.687,9.101,9.515,9.929,10.343,10.757
],
"AX2": [
8.581,9.362,10.143,10.924,11.705,12.486,13.267,14.048,14.829,15.61,16.391,17.172,17.953,18.734,19.515,20.296
],
"AX3": [
21.411,23.358,25.305,27.252,29.199,31.146,33.093,35.04,36.987,38.934,40.881,42.828,44.775,46.722,48.669,50.616
],
"EQ": [
3.934,4.293,4.652,5.011,5.37,5.729,6.088,6.447,6.806,7.165,7.524,7.883,8.242,8.601,8.96,9.319
],
"EQ2": [
4.83,5.272,5.712,6.152,6.592,7.032,7.472,7.912,8.352,8.792,9.232,9.672,10.112,10.552,10.992,11.432
],
"RL": [
6.28,6.853,7.426,7.999,8.572,9.145,9.718,10.291,10.864,11.437,12.01,12.583,13.156,13.729,14.302,14.875
],
"RZ": [
23.88,26.051,28.222,30.393,32.564,34.735,36.906,39.077,41.248,43.419,45.59,47.761,49.932,52.103,54.274,56.445
],
"TP": [
7.5,8.75,10,11.25,12.5,13.75,15
]
}
}

View file

@ -0,0 +1,50 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '4影',
type: '无视抗性',
value: 0.25,
range: ['AQ', 'CCQ']
},
{
name: '2影',
type: '增伤',
value: 0.1 * 5,
element: 'Ether',
range: ['AQ', 'CCQ']
},
{
name: '核心被动:特种弹药',
type: '增伤',
value: 'T',
range: ['AQ', 'CCQ']
},
{
name: '额外能力:武装协同',
type: '暴击率',
value: 0.3
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻三段(以太)', type: 'AQY3' },
{ name: '冲刺攻击:火力压制', type: 'CCQ' },
{
name: '强化特殊技:全弹连射',
type: 'EQ',
after: ({ damage, calc }) => {
if (calc.avatar.rank >= 6) {
const EQ2 = calc.calc_skill({
name: '6影以太鹿弹',
type: 'EQ2',
fixedMultiplier: 2.2 * 4,
element: 'Ether'
})
damage.add(EQ2)
}
}
},
{ name: '连携技:歼灭模式', type: 'RL' },
{ name: '终结技歼灭模式MAX', type: 'RZ' }
]

View file

@ -0,0 +1,24 @@
{
"skill": {
"AQY3": [
4.077,4.448,4.819,5.19,5.561,5.932,6.303,6.674,7.045,7.416,7.787,8.158,8.529,8.9,9.271,9.642
],
"CCQ": [
1.359,1.483,1.607,1.731,1.855,1.979,2.103,2.227,2.351,2.475,2.599,2.723,2.847,2.971,3.095,3.219
],
"EQ": [
5.874,6.408,6.942,7.476,8.01,8.544,9.078,9.612,10.146,10.68,11.214,11.748,12.282,12.816,13.35,13.88
],
"RL": [
5.875,6.41,6.945,7.48,8.015,8.55,9.085,9.62,10.155,10.69,11.225,11.76,12.295,12.83,13.35,13.9
],
"RZ": [
19.776,21.574,23.372,25.17,26.968,28.766,30.564,32.362,34.16,35.958,37.756,39.554,41.352,43.15,44.948,46.746
]
},
"buff": {
"T": [
0.4,0.466,0.532,0.6,0.666,0.732,0.8
]
}
}

View file

@ -0,0 +1,123 @@
/**
* @param {import('../../BuffManager.ts').BuffManager} buffM
* @param {import('../../Calculator.ts').Calculator} calc
* @param {import('../../../avatar.js').ZZZAvatarInfo} avatar
*/
export function calc(buffM, calc, avatar) {
/** 注册buff */
// 影画加成
buffM.new({
name: '6影',
type: ,
value: 0,
range: ['']
})
buffM.new({
name: '4影',
type: ,
value: 0,
range: ['']
})
buffM.new({
name: '2影',
type: ,
value: 0,
range: ['']
})
buffM.new({
name: '1影',
type: ,
value: 0,
range: ['']
})
// 核心被动加成
buffM.new({
name: '核心被动:',
type: ,
value: 0,
element: ,
range: ['']
})
// 额外能力加成
buffM.new({
name: '额外能力:',
type: ,
value: 0,
element: ,
range: ['']
})
// 技能加成
buffM.new({
name: '技能:',
type: ,
value: 0,
element: ,
range: ['']
})
/** 注册技能 */
calc.new({ name: '普通攻击:', type: '' })
calc.new({ name: '强化特殊技:', type: '' })
calc.new({ name: '连携技:', type: '' })
calc.new({ name: '终结技:', type: '' })
calc.new({ name: '', type: '' })
}
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: ,
value: 0,
range: ['']
},
{
name: '4影',
type: ,
value: 0,
range: ['']
},
{
name: '2影',
type: ,
value: 0,
range: ['']
},
{
name: '1影',
type: ,
value: 0,
range: ['']
},
{
name: '核心被动:',
type: ,
value: 0,
element: ,
range: ['']
},
{
name: '额外能力:',
type: ,
value: 0,
element: ,
range: ['']
},
{
name: '技能:',
type: ,
value: 0,
element: ,
range: ['']
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻:', type: 'AP' },
{ name: '冲刺攻击:', type: 'CC' },
{ name: '闪避反击:', type: 'CF' },
{ name: '强化特殊技:', type: 'EQ' },
{ name: '连携技:', type: 'RL' },
{ name: '终结技:', type: 'RZ' },
{ name: '', type: '' },
]

View file

@ -0,0 +1,27 @@
{
"skill": {
"AP": [
],
"CC": [
],
"CF": [
],
"EQ": [
],
"RL": [
],
"RZ": [
]
},
"buff": {
"T": [
]
}
}

View file

@ -0,0 +1,40 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '暴击伤害',
value: 0.18 * 3
},
{
name: '4影',
type: '暴击率',
value: 0.07 * 2
},
{
name: '1影',
type: '无视抗性',
value: 0.16,
element: 'Physical'
},
{
name: '核心被动:猫步诡影',
type: '增伤',
value: 'T'
},
{
name: '额外能力:猫步秀',
type: '增伤',
value: 0.35 * 2,
range: ['EQ']
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '强击', type: '强击' },
{ name: '普攻:猫猫爪刺四段', type: 'AP4' },
{ name: '闪避反击:虚影双刺', type: 'CF' },
{ name: '强化特殊技:超~凶奇袭!', type: 'EQ' },
{ name: '连携技:刃爪挥击', type: 'RL' },
{ name: '终结技:刃爪强袭', type: 'RZ' }
]

View file

@ -0,0 +1,24 @@
{
"skill": {
"AP4": [
1.702,1.857,2.012,2.167,2.322,2.477,2.632,2.787,2.942,3.097,3.252,3.407,3.562,3.717,3.872,4.027
],
"CF": [
2.279,2.487,2.695,2.903,3.111,3.319,3.527,3.735,3.943,4.151,4.359,4.567,4.775,4.983,5.191,5.399
],
"EQ": [
5.397,5.888,6.379,6.87,7.361,7.852,8.343,8.834,9.325,9.816,10.307,10.798,11.289,11.78,12.271,12.762
],
"RL": [
5.362,5.85,6.338,6.826,7.314,7.802,8.29,8.778,9.266,9.754,10.242,10.73,11.218,11.706,12.194,12.682
],
"RZ": [
15.711,17.14,18.569,19.998,21.427,22.856,24.285,25.714,27.143,28.572,30.001,31.43,32.859,34.288,35.717,37.146
]
},
"buff": {
"T": [
0.3,0.35,0.4,0.45,0.5,0.55,0.6
]
}
}

View file

@ -0,0 +1,64 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '穿透率',
value: 0.2
},
{
name: '6影',
type: '增伤',
value: 2.5,
range: ['CCXX']
},
{
name: '2影',
type: '暴击伤害',
value: 0.6,
range: ['EQ']
},
{
name: '1影',
type: '暴击率',
value: 0.2 * 6
},
{
name: '核心被动:凌牙厉齿',
type: '暴击伤害',
value: 'T',
range: ['CCXX', 'AQ']
},
{
name: '额外能力:风暴潮',
type: '增伤',
value: 0.03 * 10,
element: 'Ice'
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '碎冰', type: '碎冰' },
{ name: '普攻:急冻修剪法三段', type: 'AQ3' },
{ name: '闪避反击:暗礁', type: 'CF' },
{ name: '冲刺攻击:寒潮', type: 'CCP' },
{
name: '冲刺攻击:冰渊潜袭回旋斩击',
isHide: true,
type: 'CCX0'
},
{
name: '冲刺攻击:冰渊潜袭点按',
type: 'CCXP',
after: ({ damage }) => damage.add('CCX0')
},
{
name: '冲刺攻击:冰渊潜袭蓄力',
type: 'CCXX',
after: ({ damage }) => damage.add('CCX0')
},
{ name: '强化特殊技:横扫', type: 'EQ1' },
{ name: '强化特殊技:鲨卷风', type: 'EQ2' },
{ name: '连携技:雪崩', type: 'RL' },
{ name: '终结技:永冬狂宴', type: 'RZ' }
]

View file

@ -0,0 +1,39 @@
{
"skill": {
"AQ3": [
4.962,5.414,5.866,6.318,6.77,7.22,7.674,8.126,8.578,9.03,9.482,9.934,10.386,10.838,11.29,11.742
],
"CF": [
1.526,1.665,1.804,1.943,2.082,2.221,2.36,2.499,2.638,2.777,2.916,3.055,3.194,3.333,3.472,3.611
],
"CCP": [
1.457,1.59,1.723,1.856,1.989,2.122,2.255,2.388,2.521,2.654,2.787,2.92,3.053,3.186,3.319,3.452
],
"CCX0": [
0.623,0.68,0.737,0.794,0.851,0.908,0.965,1.022,1.079,1.136,1.193,1.25,1.307,1.364,1.421,1.478
],
"CCXP": [
1.276,1.392,1.508,1.624,1.74,1.856,1.972,2.088,2.204,2.32,2.436,2.552,2.668,2.784,2.9,3.016
],
"CCXX": [
1.582,1.726,1.87,2.014,2.158,2.302,2.446,2.59,2.734,2.878,3.022,3.166,3.31,3.454,3.598,3.742
],
"EQ1": [
3.772,4.115,4.458,4.801,5.144,5.487,5.83,6.173,6.516,6.859,7.202,7.545,7.888,8.231,8.574,8.917
],
"EQ2": [
5.533,6.036,6.539,7.042,7.545,8.048,8.551,9.054,9.557,10.06,10.56,11.066,11.569,12.072,12.575,13.078
],
"RL": [
7.946,8.669,9.392,10.115,10.838,11.561,12.284,13.007,13.73,14.453,15.176,15.899,16.622,17.345,18.068,18.791
],
"RZ": [
18.908,20.627,22.346,24.065,25.784,27.503,29.222,30.941,32.66,34.379,36.098,37.817,39.536,41.255,42.974,44.693
]
},
"buff": {
"T": [
0.5,0.583,0.666,0.75,0.833,0.916,1
]
}
}

View file

@ -0,0 +1,53 @@
/** @type {import('../../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '6影',
type: '暴击伤害',
value: 1,
range: ['AQ'],
isForever: true
},
{
name: '6影',
type: '无视抗性',
value: 0.2
},
{
name: '1影',
type: '无视防御',
value: 0.15
},
{
name: '1影',
type: '暴击率',
value: 0.2
},
{
name: '额外能力:阳关三叠',
type: '攻击力',
isForever: true,
value: ({ calc }) => Math.max(0, Math.min((calc.get_Impact() - 120) * 6, 600))
},
{
name: '连携技:太平令',
type: '增伤',
value: 0.03 * 20,
range: ['RL']
},
{
name: '闪络',
source: 'Skill',
type: '增伤',
value: 0.01 * 25,
range: ['AQ']
}
]
/** @type {import('../../Calculator.ts').Calculator['skills']} */
export const skills = [
{ name: '普攻:醉花月云转', type: 'AQ' },
{ name: '闪避反击:意不尽', type: 'CF' },
{ name: '强化特殊技:月上海棠', type: 'EQ' },
{ name: '连携技:太平令', type: 'RL' },
{ name: '终结技:八声甘州', type: 'RZ' }
]

View file

@ -0,0 +1,19 @@
{
"skill": {
"AQ": [
4.487,4.895,5.303,5.711,6.119,6.527,6.935,7.343,7.751,8.159,8.567,8.975,9.383,9.791,10.199,10.607
],
"CF": [
2.84,3.099,3.358,3.617,3.876,4.135,4.394,4.653,4.912,5.171,5.43,5.689,5.948,6.207,6.466,6.725
],
"EQ": [
6.028,6.577,7.126,7.675,8.224,8.773,9.322,9.871,10.42,10.969,11.518,12.067,12.616,13.165,13.714,14.263
],
"RL": [
6.479,7.068,7.657,8.246,8.835,9.424,10.013,10.602,11.191,11.78,12.369,12.958,13.547,14.136,14.725,15.314
],
"RZ": [
16.707,18.226,19.745,21.264,22.783,24.302,25.821,27.34,28.859,30.378,31.897,33.416,34.935,36.454,37.973,39.492
]
}
}

View file

@ -1,119 +0,0 @@
import _ from 'lodash';
import { ZZZAvatarInfo } from '../avatar.js';
/**
*
* @param {string} set_id 套装id
* @param {number} set_num 套装数量
* @param {ZZZAvatarInfo['damage_basic_properties']['base_detail']} base_detail 基础属性
* @param {ZZZAvatarInfo['damage_basic_properties']['bonus_detail']} bonus_detail 套装加成
* @returns {ZZZAvatarInfo['damage_basic_properties']['bonus_detail']} 套装加成
*/
export const relice_ability = (set_id, set_num, base_detail, bonus_detail) => {
switch (set_id) {
case '32700':
if (set_num >= 4) {
let CriticalDamageBase = _.get(bonus_detail, 'CriticalDamageBase', 0);
bonus_detail['CriticalDamageBase'] = CriticalDamageBase + 0.3;
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.12;
logger.debug('32700,4,CriticalDamageBase');
logger.debug('32700,4,CriticalChanceBase');
}
break;
case '31100':
if (set_num >= 4) {
let R_DmgAdd = _.get(bonus_detail, 'R_DmgAdd', 0);
bonus_detail['R_DmgAdd'] = R_DmgAdd + 0.2;
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + 0.15;
logger.debug('relicGetter,4,R_DmgAdd');
}
break;
case '32500':
if (set_num >= 2) {
let IceDmgAdd = _.get(bonus_detail, 'Ice_DmgAdd', 0);
bonus_detail['Ice_DmgAdd'] = IceDmgAdd + 0.1;
logger.debug('32500,2,Ice_DmgAdd');
}
if (set_num >= 4) {
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + 0.4;
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + 0.4;
logger.debug('32500,4,A_DmgAdd');
logger.debug('32500,4,C_DmgAdd');
}
break;
case '32600':
if (set_num >= 2) {
let PhysicalDmgAdd = _.get(bonus_detail, 'Physical_DmgAdd', 0);
bonus_detail['Physical_DmgAdd'] = PhysicalDmgAdd + 0.1;
logger.debug('32600,2,Physical_DmgAdd');
}
if (set_num >= 4) {
let AllDmgAdd = _.get(bonus_detail, 'All_DmgAdd', 0);
bonus_detail['All_DmgAdd'] = AllDmgAdd + 0.35;
logger.debug('32600,4,All_DmgAdd');
}
break;
case '32400':
if (set_num >= 2) {
let Electric_DmgAdd = _.get(bonus_detail, 'Electric_DmgAdd', 0);
bonus_detail['Electric_DmgAdd'] = Electric_DmgAdd + 0.1;
logger.debug('32400,2,Electric_DmgAdd');
}
if (set_num >= 4) {
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + 0.28;
logger.debug('32400,4,AttackAddedRatio');
}
break;
case '32200':
if (set_num >= 2) {
let Fire_DmgAdd = _.get(bonus_detail, 'Fire_DmgAdd', 0);
bonus_detail['Fire_DmgAdd'] = Fire_DmgAdd + 0.1;
logger.debug('32200,4,Fire_DmgAdd');
}
if (set_num >= 4) {
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase + 0.28;
logger.debug('32200,4,CriticalChanceBase');
}
break;
case '32300':
if (set_num >= 2) {
let Ether_DmgAdd = _.get(bonus_detail, 'Ether_DmgAdd', 0);
bonus_detail['Ether_DmgAdd'] = Ether_DmgAdd + 0.1;
logger.debug('32300,4,Ether_DmgAdd');
}
if (set_num >= 4) {
let CriticalDamageBase = _.get(bonus_detail, 'CriticalDamageBase', 0);
bonus_detail['CriticalDamageBase'] = CriticalDamageBase + 0.53;
logger.debug('32300,4,CriticalDamageBase');
}
break;
case '31600':
if (set_num >= 4) {
let All_DmgAdd = _.get(bonus_detail, 'All_DmgAdd', 0);
bonus_detail['All_DmgAdd'] = All_DmgAdd + 0.53;
logger.debug('31600,4,All_DmgAdd');
}
break;
case '31400':
if (set_num >= 4) {
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + 0.25;
logger.debug('31400,4,AttackAddedRatio');
}
break;
case '31000':
if (set_num >= 4) {
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + 0.27;
logger.debug('31400,4,AttackAddedRatio');
}
break;
}
return bonus_detail;
};

View file

@ -1,304 +0,0 @@
import _ from 'lodash';
export const calculate_damage = (
base_detail,
bonus_detail,
skill_type,
add_skill_type,
avatar_element,
skill_multiplier,
level
) => {
const merged_attr = merge_attribute(base_detail, bonus_detail);
logger.debug('merged_attr', merged_attr);
logger.debug(logger.green(skill_type + `${add_skill_type})伤害乘区计算:`));
logger.debug('倍率', skill_multiplier);
const attack = merged_attr.attack;
logger.debug('攻击力', attack);
const critical_chance_base = get_critical_chance_base(
merged_attr,
skill_type,
add_skill_type,
avatar_element
);
logger.debug('暴击率', critical_chance_base);
const critical_damage_base = get_critical_damage_base(
merged_attr,
skill_type,
add_skill_type,
avatar_element
);
logger.debug('暴击伤害', critical_damage_base);
const qiwang_damage = critical_chance_base * (critical_damage_base - 1) + 1;
logger.debug('暴击期望', qiwang_damage);
const injury_area = get_injury_area(
merged_attr,
skill_type,
add_skill_type,
avatar_element
);
logger.debug('增伤区', injury_area);
const damage_ratio = get_damage_ratio(
merged_attr,
skill_type,
add_skill_type,
avatar_element
);
logger.debug('易伤区', damage_ratio);
const resistance_area = get_resistance_area(
merged_attr,
skill_type,
add_skill_type,
avatar_element
);
logger.debug('抗性区', resistance_area);
const defence_multiplier = get_defence_multiplier(
merged_attr,
level,
skill_type,
add_skill_type,
);
logger.debug('防御区', defence_multiplier);
const damage_cd =
attack *
skill_multiplier *
defence_multiplier *
resistance_area *
injury_area *
damage_ratio *
critical_damage_base *
1.0; // 失衡易伤不计
const damage_qw =
attack *
skill_multiplier *
defence_multiplier *
resistance_area *
injury_area *
damage_ratio *
qiwang_damage *
1.0;
const damagelist = {
cd: damage_cd,
qw: damage_qw,
};
logger.debug('最终伤害', damagelist);
return damagelist;
};
export const get_critical_chance_base = (
merged_attr,
skill_type,
add_skill_type,
avatar_element
) => {
let critical_chance_base = _.get(merged_attr, 'CriticalChanceBase', 0);
const merged_attrkey = Object.keys(merged_attr);
for (const attr of merged_attrkey) {
if (attr.search('_CriticalChanceBase') != -1) {
const attr_name = attr.split('_CriticalChanceBase')[0];
if (
[skill_type, add_skill_type, 'All', avatar_element].includes(attr_name)
) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '暴击加成'
);
critical_chance_base = critical_chance_base + merged_attr[attr];
}
}
}
critical_chance_base = Math.min(1, critical_chance_base);
return critical_chance_base;
};
export const get_critical_damage_base = (
merged_attr,
skill_type,
add_skill_type,
avatar_element
) => {
let critical_damage_base = _.get(merged_attr, 'CriticalDamageBase', 0);
const merged_attrkey = Object.keys(merged_attr);
for (const attr of merged_attrkey) {
if (attr.search('_CriticalDamageBase') != -1) {
const attr_name = attr.split('_CriticalDamageBase')[0];
if (
[skill_type, add_skill_type, 'All', avatar_element].includes(attr_name)
) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '爆伤加成'
);
critical_damage_base = critical_damage_base + merged_attr[attr];
}
}
}
return critical_damage_base + 1;
};
export const get_damage_ratio = (
merged_attr,
skill_type,
add_skill_type,
avatar_element
) => {
let damage_ratio = _.get(merged_attr, 'DmgRatio', 0);
const merged_attrkey = Object.keys(merged_attr);
for (const attr of merged_attrkey) {
if (attr.search('_DmgRatio') != -1) {
const attr_name = attr.split('_DmgRatio')[0];
if (
[skill_type, add_skill_type, 'All', avatar_element].includes(attr_name)
) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '易伤加成'
);
damage_ratio = damage_ratio + merged_attr[attr];
}
}
}
return damage_ratio + 1;
};
export const get_resistance_area = (
merged_attr,
skill_type,
add_skill_type,
avatar_element
) => {
let resistance_area = 1.2
for (const attr in merged_attr) {
if (attr.search('_ResistancePenetration') != -1) {
const attr_name = attr.split('_ResistancePenetration')[0]
if (
[skill_type, add_skill_type, 'All', avatar_element].includes(attr_name)
) {
logger.debug(attr + '对' + attr_name + '有' + merged_attr[attr] + '减抗')
resistance_area += merged_attr[attr]
}
}
}
return resistance_area
}
export const get_injury_area = (
merged_attr,
skill_type,
add_skill_type,
avatar_element
) => {
let injury_area = 1.0;
const merged_attrkey = Object.keys(merged_attr);
for (const attr of merged_attrkey) {
if (attr.search('_DmgAdd') != -1) {
const attr_name = attr.split('_DmgAdd')[0];
if (
[skill_type, add_skill_type, 'All', avatar_element].includes(attr_name)
) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '增伤'
);
injury_area = injury_area + merged_attr[attr];
}
}
if (attr.search('AddedRatio') != -1) {
const attr_name = attr.split('AddedRatio')[0];
if ([avatar_element, 'AllDamage'].includes(attr_name)) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '增伤'
);
injury_area = injury_area + merged_attr[attr];
}
}
}
return injury_area;
};
export const get_defence_multiplier = (
merged_attr,
level,
skill_type,
add_skill_type
) => {
/** 计算防御基础值 */
const defadd = 0.155 * (level * level) + 3.12 * level + 46.95;
/** 计算降防 */
let ignore_defence = 1.0;
const merged_attrkey = Object.keys(merged_attr);
for (const attr of merged_attrkey) {
if (attr.search('_IgnoreDefence') != -1) {
const attr_name = attr.split('_IgnoreDefence')[0];
if (
[skill_type, add_skill_type, 'All'].includes(attr_name)
) {
logger.debug(
attr + '对' + attr_name + '有' + merged_attr[attr] + '无视防御'
);
ignore_defence -= merged_attr[attr];
}
}
}
if (merged_attr.ignore_defence) {
ignore_defence -= merged_attr.ignore_defence;
}
/** 计算穿透率 */
let penratio = 1.0;
if (merged_attr.PenRatioBase) {
penratio = 1 - merged_attr.PenRatioBase;
}
/** 计算穿透值 */
const pendelta = _.get(merged_attr, 'PenDelta', 0);
/** 计算防御乘区 */
const defence_multiplier =
defadd / (defadd + (defadd * ignore_defence * penratio - pendelta));
return defence_multiplier;
};
export const merge_attribute = (base_detail, bonus_detail) => {
const merged_attr = {};
const bonus_detailkey = Object.keys(bonus_detail);
for (const merged of bonus_detailkey) {
if (
merged.search('Attack') != -1 ||
(merged.search('Defence') != -1 && merged.search('IgnoreDefence') === -1) ||
merged.search('HP') != -1
) {
continue;
} else if (merged.search('Base') != -1) {
merged_attr[merged] =
_.get(base_detail, merged, 0) + _.get(bonus_detail, merged, 0);
} else {
merged_attr[merged] = _.get(bonus_detail, merged, 0);
}
}
merged_attr['hp'] =
_.get(bonus_detail, 'HPDelta', 0) +
_.get(base_detail, 'hp', 0) * (_.get(bonus_detail, 'HPAddedRatio', 0) + 1);
merged_attr['attack'] =
_.get(bonus_detail, 'AttackDelta', 0) +
_.get(base_detail, 'attack', 0) *
(_.get(bonus_detail, 'AttackAddedRatio', 0) + 1);
merged_attr['defence'] =
_.get(bonus_detail, 'DefenceDelta', 0) +
_.get(base_detail, 'defence', 0) *
(_.get(bonus_detail, 'DefenceAddedRatio', 0) + 1);
merged_attr['CriticalChanceBase'] =
_.get(bonus_detail, 'CriticalChanceBase', 0) +
_.get(base_detail, 'CriticalChanceBase', 0);
merged_attr['CriticalDamageBase'] =
_.get(bonus_detail, 'CriticalDamageBase', 0) +
_.get(base_detail, 'CriticalDamageBase', 0);
merged_attr['PenRatioBase'] =
_.get(bonus_detail, 'PenRatioBase', 0) +
_.get(base_detail, 'PenRatioBase', 0);
return merged_attr;
};

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.15,
check: 4
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: 0.09 * 3,
check: 4
}
]

View file

@ -0,0 +1,42 @@
// 函数导出:
/**
* @param {import('../BuffManager.ts').BuffManager} buffM
* @param {number} count 套装数量
*/
// export function calc(buffM, count) {
// const name = buffM.defaultBuff.name
// switch (true) {
// case (count >= 4):
// buffM.new({
// name: name + '4',
// type: '暴击伤害',
// value: 0.3,
// isForever: true,
// check: ({ buffM, calc }) => calc.get_AnomalyMastery() >= 115
// })
// buffM.new({
// name: name + '4',
// type: '暴击率',
// value: 0.12
// })
// }
// }
// 直接导出:
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
name: '折枝剑歌4',
type: '暴击伤害',
value: 0.3,
isForever: true,
check: ({ buffM, calc }) => buffM.setCount.折枝剑歌 >= 4 && calc.get_AnomalyMastery() >= 115
},
{
type: '暴击率',
value: 0.12,
check: 4
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.15,
check: 4
}
]

View file

@ -0,0 +1,23 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.1,
isForever: true,
element: 'Ice',
check: 2
},
{
type: '增伤',
value: 0.2,
isForever: true,
range: ['A', 'CC'],
check: 4
},
{
type: '增伤',
value: 0.2,
range: ['A', 'CC'],
check: 4
}
]

View file

@ -0,0 +1,43 @@
/**
* @param {import('../BuffManager.ts').BuffManager} buffM
* @param {number} count 套装数量
*/
export function calc(buffM, count) {
const name = buffM.defaultBuff.name
switch (true) {
case (count >= 4):
buffM.new({
name: name + '4',
type: ,
value: 0,
element: ,
range: ['']
})
case (count >= 2):
buffM.new({
name: name + '2',
type: ,
value: 0,
element: ,
range: ['']
})
}
}
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: ,
value: 0,
check: 2,
element: ,
range: ['']
},
{
type: ,
value: 0,
check: 4,
element: ,
range: ['']
}
]

View file

@ -0,0 +1,15 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.2,
isForever: true,
range: ['RZ'],
check: 4
},
{
type: '攻击力',
value: 0.15,
check: 4
}
]

View file

@ -0,0 +1,16 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.15,
isForever: true,
element: ['Electric', 'Fire'],
check: 4
},
{
type: '增伤',
value: 0.2,
check: 4,
range: ['EQ', 'L']
}
]

View file

@ -0,0 +1,21 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.1,
isForever: true,
element: 'Ether',
check: 2
},
{
type: '暴击伤害',
value: 0.2,
isForever: true,
check: 4
},
{
type: '暴击伤害',
value: 0.055 * 6,
check: 4
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: 0.25,
check: 4
}
]

View file

@ -0,0 +1,15 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.1,
element: 'Fire',
isForever: true,
check: 2
},
{
type: '暴击率',
value: 0.28,
check: 4
}
]

View file

@ -0,0 +1,15 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.1,
check: 2,
isForever: true,
element: 'Physical'
},
{
type: '增伤',
value: 0.35,
check: 4
}
]

View file

@ -0,0 +1,15 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.1,
check: 2,
isForever: true,
element: 'Electric'
},
{
type: '攻击力',
value: 0.28,
check: 4
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: 0.08 * 3,
check: 4
}
]

View file

@ -1,84 +0,0 @@
import _ from 'lodash';
import { getMapData } from '../../utils/file.js';
import { ZZZAvatarInfo } from '../avatar.js';
const weapon_effect = getMapData('weapon_effect');
/**
* 武器加成
* @param {ZZZAvatarInfo['weapon']} equipment 武器
* @param {ZZZAvatarInfo['damage_basic_properties']['base_detail']} base_detail 基础属性
* @param {ZZZAvatarInfo['damage_basic_properties']['bonus_detail']} bonus_detail 套装加成
* @returns {ZZZAvatarInfo['damage_basic_properties']['bonus_detail']} 套装加成
*/
export const weapon_ability = (equipment, base_detail, bonus_detail) => {
let equipid = equipment.id
switch (equipid) {
case 14109:{
let CriticalDamageBase = _.get(bonus_detail, 'CriticalDamageBase', 0);
bonus_detail['CriticalDamageBase'] = CriticalDamageBase + weapon_effect[equipment.id]['Param']['CriticalDamageBase'][equipment.star - 1];
let IceDmgAdd = _.get(bonus_detail, 'Ice_DmgAdd', 0);
bonus_detail['Ice_DmgAdd'] = IceDmgAdd + weapon_effect[equipment.id]['Param']['IceDmgAdd'][equipment.star - 1] * 2;
break;
}
case 14119:{
let IceDmgAdd = _.get(bonus_detail, 'Ice_DmgAdd', 0);
bonus_detail['Ice_DmgAdd'] = IceDmgAdd + weapon_effect[equipment.id]['Param']['IceDmgAdd'][equipment.star - 1];
let CriticalChanceBase = _.get(bonus_detail, 'CriticalChanceBase', 0);
bonus_detail['CriticalChanceBase'] = CriticalChanceBase +weapon_effect[equipment.id]['Param']['CriticalChanceBase'][equipment.star - 1];
break;
}
case 14102:{
let Physical_DmgAdd = _.get(bonus_detail, 'Physical_DmgAdd', 0);
bonus_detail['Physical_DmgAdd'] = Physical_DmgAdd + weapon_effect[equipment.id]['Param']['Physical_DmgAdd'][equipment.star - 1];
let All_DmgAdd = _.get(bonus_detail, 'All_DmgAdd', 0);
bonus_detail['All_DmgAdd'] = All_DmgAdd + weapon_effect[equipment.id]['Param']['All_DmgAdd'][equipment.star - 1];
break;
}
case 14104:{
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + weapon_effect[equipment.id]['Param']['AttackAddedRatio'][equipment.star - 1] * 8;
break;
}
case 14124:{
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + weapon_effect[equipment.id]['Param']['A_DmgAdd'][equipment.star - 1] * 8;
break;
}
case 13001:{
let R_DmgAdd = _.get(bonus_detail, 'R_DmgAdd', 0);
bonus_detail['R_DmgAdd'] = R_DmgAdd + weapon_effect[equipment.id]['Param']['R_DmgAdd'][equipment.star - 1] * 3;
break;
}
case 13004:{
let AttackAddedRatio = _.get(bonus_detail, 'AttackAddedRatio', 0);
bonus_detail['AttackAddedRatio'] = AttackAddedRatio + weapon_effect[equipment.id]['Param']['AttackAddedRatio'][equipment.star - 1];
break;
}
case 13013:{
let EUP_DmgAdd = _.get(bonus_detail, 'EUP_DmgAdd', 0);
bonus_detail['EUP_DmgAdd'] = EUP_DmgAdd + weapon_effect[equipment.id]['Param']['EUP_DmgAdd'][equipment.star - 1];
break;
}
case 13106:{
let EUP_DmgAdd = _.get(bonus_detail, 'EUP_DmgAdd', 0);
bonus_detail['EUP_DmgAdd'] = EUP_DmgAdd + weapon_effect[equipment.id]['Param']['EUP_DmgAdd'][equipment.star - 1] * 15;
break;
}
case 13108:{
let Physical_DmgAdd = _.get(bonus_detail, 'Physical_DmgAdd', 0);
bonus_detail['Physical_DmgAdd'] = Physical_DmgAdd + weapon_effect[equipment.id]['Param']['Physical_DmgAdd'][equipment.star - 1];
break;
}
case 13111:{
let A_DmgAdd = _.get(bonus_detail, 'A_DmgAdd', 0);
bonus_detail['A_DmgAdd'] = A_DmgAdd + weapon_effect[equipment.id]['Param']['A_DmgAdd'][equipment.star - 1];
let C_DmgAdd = _.get(bonus_detail, 'C_DmgAdd', 0);
bonus_detail['C_DmgAdd'] = C_DmgAdd + weapon_effect[equipment.id]['Param']['A_DmgAdd'][equipment.star - 1];
break;
}
}
return bonus_detail;
};

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '防御力',
value: [0.2, 0.23, 0.26, 0.29, 0.32]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.15, 0.175, 0.2, 0.225, 0.25]
}
]

View file

@ -0,0 +1,9 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.12, 0.14, 0.16, 0.18, 0.2],
isForever: true,
range: ['A', 'CC', 'CF']
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.08, 0.09, 0.1, 0.11, 0.12]
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '异常掌控',
value: [10, 12, 13, 15, 16]
},
{
type: '异常精通',
value: [10, 12, 13, 15, 16]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.08, 0.09, 0.1, 0.11, 0.12]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.09, 0.1, 0.11, 0.12, 0.13]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '异常掌控',
value: [25, 28, 32, 36, 40]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '异常精通',
value: [25, 28, 32, 36, 40]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.02, 0.023, 0.026, 0.029, 0.032].map(v => v * 8)
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.36, 0.41, 0.465, 0.52, 0.575],
element: 'Physical'
}
]

View file

@ -0,0 +1,12 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '生命值',
value: [0.08, 0.092, 0.104, 0.116, 0.128],
isForever: true
},
{
type: '攻击力',
value: [0.1, 0.115, 0.13, 0.145, 0.16]
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.075, 0.086, 0.097, 0.108, 0.12],
isForever: true
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '异常精通',
value: [30, 34, 38, 42, 48].map(v => v * 4)
}
]

View file

@ -0,0 +1,13 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.15, 0.175, 0.2, 0.22, 0.24],
element: 'Ice',
isForever: true
},
{
type: '攻击力',
value: [0.02, 0.023, 0.026, 0.029, 0.032].map(v => v * 4)
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.1, 0.125, 0.15, 0.175, 0.2]
},
{
type: '增伤',
value: [0.017, 0.02, 0.025, 0.03, 0.033].map(v => v * 6)
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.18, 0.225, 0.27, 0.315, 0.36]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.025, 0.028, 0.032, 0.036, 0.04].map(v => v * 4),
}
]

View file

@ -0,0 +1,9 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.03, 0.035, 0.04, 0.044, 0.048].map(v => v * 15),
element: 'Physical',
range: ['EQ'] // 只持续1s
}
]

View file

@ -0,0 +1,12 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.12, 0.15, 0.18, 0.21, 0.24],
isForever: true
},
{
type: '异常精通',
value: [25, 31, 37, 43, 50].map(v => v * 3)
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.06, 0.069, 0.078, 0.087, 0.096].map(v => v * 2)
}
]

View file

@ -0,0 +1,9 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.15, 0.175, 0.2, 0.22, 0.24],
element: 'Electric',
isForever: true
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.06, 0.075, 0.09, 0.105, 0.12].map(v => v * 5),
range: ['A']
}
]

View file

@ -0,0 +1,9 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.5, 0.575, 0.65, 0.725, 0.8],
element: 'Electric',
range: ['A', 'CC']
}
]

View file

@ -0,0 +1,13 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '异常精通',
value: [75, 85, 95, 105, 115]
},
{
type: '异常增伤',
value: [0.25, 0.275, 0.3, 0.325, 0.35],
check: ({ calc }) => calc.get_AnomalyProficiency() >= 375,
range: ['紊乱']
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.12, 0.138, 0.156, 0.174, 0.192]
}
]

View file

@ -0,0 +1,28 @@
/**
* @param {import('../BuffManager.ts').BuffManager} buffM
* @param {number} star 进阶星数
*/
export function calc(buffM, star) {
buffM.new({
type: ,
value: [][star - 1],
element: ,
range: []
})
}
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: ,
value: [],
element: ,
range: []
},
{
type: ,
value: [],
element: ,
range: []
}
]

View file

@ -0,0 +1,12 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '生命值',
value: [0.08, 0.09, 0.1, 0.11, 0.125],
isForever: true
},
{
type: '冲击力',
value: [0.1, 0.115, 0.13, 0.145, 0.16]
}
]

View file

@ -0,0 +1,19 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '暴击率',
value: [0.1, 0.115, 0.13, 0.145, 0.16],
isForever: true
},
{
type: '增伤',
value: [0.4, 0.46, 0.52, 0.58, 0.64],
element: 'Electric',
isForever: true,
range: ['CC']
},
{
type: '暴击率',
value: [0.1, 0.115, 0.13, 0.145, 0.16]
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.12, 0.15, 0.18, 0.21, 0.24].map(v => v * 3),
element: 'Physical'
}
]

View file

@ -0,0 +1,13 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.25, 0.315, 0.38, 0.445, 0.5],
element: 'Ice',
isForever: true
},
{
type: '暴击率',
value: [0.1, 0.125, 0.15, 0.175, 0.2].map(v => v * 2)
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.035, 0.044, 0.052, 0.061, 0.07].map(v => v * 10)
},
{
type: '异常精通',
value: [50, 62, 75, 87, 100]
}
]

View file

@ -0,0 +1,12 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.25, 0.2875, 0.325, 0.3625, 0.4]
},
{
type: '暴击伤害',
value: [0.015, 0.0172, 0.0195, 0.0217, 0.024].map(v => v * 20),
element: ['Ice', 'Fire']
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.1, 0.125, 0.15, 0.175, 0.2].map(v => v * 2)
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '冲击力',
value: [0.007, 0.0088, 0.0105, 0.0122, 0.014].map(v => v * 30)
},
{
type: '增伤',
value: [0.2, 0.23, 0.26, 0.29, 0.32]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.035, 0.044, 0.052, 0.06, 0.07].map(v => v * 8)
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.15, 0.175, 0.2, 0.22, 0.24],
check: ({ avatar }) => avatar.element_type === 205
}
]

View file

@ -0,0 +1,8 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.15, 0.172, 0.195, 0.217, 0.24].map(v => v * 3),
range: ['RZ']
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.1, 0.115, 0.13, 0.145, 0.16]
},
{
type: '增伤',
value: [0.15, 0.175, 0.2, 0.225, 0.25]
}
]

View file

@ -0,0 +1,11 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.08, 0.092, 0.104, 0.116, 0.128]
},
{
type: '异常精通',
value: [40, 46, 52, 58, 64]
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '暴击率',
value: [0.12, 0.135, 0.155, 0.175, 0.2]
}
]

View file

@ -0,0 +1,13 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.06, 0.069, 0.078, 0.087, 0.096],
isForever: true
},
{
type: '增伤',
value: [0.15, 0.172, 0.195, 0.218, 0.24],
range: ['EQ']
}
]

View file

@ -0,0 +1,13 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '增伤',
value: [0.2, 0.25, 0.3, 0.35, 0.4],
element: 'Physical',
isForever: true
},
{
type: '增伤',
value: [0.25, 0.315, 0.38, 0.44, 0.5]
}
]

View file

@ -0,0 +1,14 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '暴击率',
value: [0.15, 0.188, 0.226, 0.264, 0.3],
isForever: true
},
{
type: '增伤',
value: [0.35, 0.435, 0.52, 0.605, 0.7],
element: 'Ether',
range: ['A']
}
]

View file

@ -0,0 +1,7 @@
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '攻击力',
value: [0.025, 0.028, 0.032, 0.036, 0.04].map(v => v * 10)
}
]

View file

@ -0,0 +1,34 @@
// 函数导出:
/**
* @param {import('../BuffManager.ts').BuffManager} buffM
* @param {number} star 进阶星数
*/
// export function calc(buffM, star) {
// buffM.new({
// type: '暴击伤害',
// value: [0.5, 0.57, 0.65, 0.72, 0.8][star - 1],
// isForever: true
// })
// buffM.new({
// type: '增伤',
// value: [0.2, 0.23, 0.26, 0.29, 0.32][star - 1] * 2,
// element: 'Ice'
// })
// }
// 直接导出:
/** @type {import('../BuffManager.ts').BuffManager['buffs']} */
export const buffs = [
{
type: '暴击伤害',
value: [0.5, 0.57, 0.65, 0.72, 0.8],
isForever: true
},
{
type: '增伤',
value: [0.2, 0.23, 0.26, 0.29, 0.32].map(v => v * 2),
element: 'Ice'
}
]

View file

@ -206,7 +206,7 @@ export class Equip {
/**
* @param {number} id
* @returns {EquipProperty}
* @returns {number}
*/
get_property(id) {
const result =

View file

@ -0,0 +1,98 @@
[
{
"name": "强击",
"element": "Physical",
"element_type": 200,
"sub_element_type": 0,
"duration": 0,
"interval": 0,
"multiplier": 7.13
},
{
"name": "畏缩",
"element": "Physical",
"element_type": 200,
"sub_element_type": 0,
"duration": 10,
"interval": 1,
"multiplier": 0,
"discover": {
"multiplier": 0.075,
"fixed_multiplier": 4.5
}
},
{
"name": "灼烧",
"element": "Fire",
"element_type": 201,
"sub_element_type": 0,
"duration": 10,
"interval": 0.5,
"multiplier": 0.5,
"discover": {
"multiplier": 0.5,
"fixed_multiplier": 4.5
}
},
{
"name": "碎冰",
"element": "Ice",
"element_type": 202,
"sub_element_type": 0,
"duration": 0,
"interval": 0,
"multiplier": 5
},
{
"name": "霜寒",
"element": "Ice",
"element_type": 202,
"sub_element_type": 0,
"duration": 10,
"interval": 1,
"multiplier": 0,
"discover": {
"multiplier": 0.075,
"fixed_multiplier": 4.5
}
},
{
"name": "霜寒",
"element": "Ice",
"element_type": 202,
"sub_element_type": 1,
"duration": 20,
"interval": 1,
"multiplier": 0,
"discover": {
"multiplier": 0.75,
"fixed_multiplier": 6
}
},
{
"name": "感电",
"element": "Electric",
"element_type": 203,
"sub_element_type": 0,
"duration": 10,
"interval": 1,
"multiplier": 1.25,
"discover": {
"multiplier": 1.25,
"fixed_multiplier": 4.5
}
},
{
"name": "侵蚀",
"element": "Ether",
"element_type": 205,
"sub_element_type": 0,
"duration": 10,
"interval": 0.5,
"multiplier": 0.625,
"discover": {
"multiplier": 0.625,
"fixed_multiplier": 4.5
}
}
]

View file

@ -1,21 +1,21 @@
{
"12102": ["AttackAddedRatio", "攻击力百分比"],
"12103": ["AttackDelta", "攻击力"],
"11102": ["HPAddedRatio", "生命值百分比"],
"11103": ["HPDelta", "生命值"],
"13102": ["DefenceAddedRatio", "防御力百分比"],
"13103": ["DefenceDelta", "防御力"],
"20103": ["CriticalChanceBase", "暴击率"],
"21103": ["CriticalDamageBase", "暴击伤害"],
"12202": ["ImpactRatio", "冲击力"],
"31203": ["ElementMystery", "异常精通"],
"31403": ["ElementAbnormalPower", "异常掌控"],
"23203": ["PenDelta", "穿透值"],
"30502": ["SpGetRatio", "能量恢复"],
"23103": ["PenRatioBase", "穿透率"],
"31703": ["IceAddedRatio", "冰属性伤害提高"],
"31603": ["FireAddedRatio", "火属性伤害提高"],
"31503": ["PhysicalAddedRatio", "物理属性伤害提高"],
"31803": ["ElectricAddedRatio", "电属性伤害提高"],
"31903": ["EtherAddedRatio", "以太属性伤害提高"]
"11102": ["HPRatio", "生命值百分比"],
"11103": ["HP", "生命值"],
"12102": ["ATKRatio", "攻击力百分比"],
"12103": ["ATK", "攻击力"],
"12202": ["Impact", "冲击力"],
"13102": ["DEFRatio", "防御力百分比"],
"13103": ["DEF", "防御力"],
"20103": ["CRITRate", "暴击率"],
"21103": ["CRITDMG", "暴击伤害"],
"23203": ["Pen", "穿透值"],
"23103": ["PenRatio", "穿透率"],
"30502": ["EnergyRegen", "能量恢复"],
"31203": ["AnomalyProficiency", "异常精通"],
"31403": ["AnomalyMastery", "异常掌控"],
"31503": ["PhysicalDMGBonus", "物理属性伤害提高"],
"31603": ["FireDMGBonus", "火属性伤害提高"],
"31703": ["IceDMGBonus", "冰属性伤害提高"],
"31803": ["ElectricDMGBonus", "电属性伤害提高"],
"31903": ["EtherDMGBonus", "以太属性伤害提高"]
}

View file

@ -0,0 +1,338 @@
{
"12001": {
"id": 12001,
"name": "「月相」-望",
"rarity": "B",
"profession": 1
},
"12002": {
"id": 12002,
"name": "「月相」-晦",
"rarity": "B",
"profession": 1
},
"12003": {
"id": 12003,
"name": "「月相」-朔",
"rarity": "B",
"profession": 1
},
"12004": {
"id": 12004,
"name": "「残响」-Ⅰ型",
"rarity": "B",
"profession": 4
},
"12005": {
"id": 12005,
"name": "「残响」-Ⅱ型",
"rarity": "B",
"profession": 4
},
"12006": {
"id": 12006,
"name": "「残响」-Ⅲ型",
"rarity": "B",
"profession": 4
},
"12007": {
"id": 12007,
"name": "「湍流」-铳型",
"rarity": "B",
"profession": 2
},
"12008": {
"id": 12008,
"name": "「湍流」-矢型",
"rarity": "B",
"profession": 2
},
"12009": {
"id": 12009,
"name": "「湍流」-斧型",
"rarity": "B",
"profession": 2
},
"12010": {
"id": 12010,
"name": "「电磁暴」-壹式",
"rarity": "B",
"profession": 3
},
"12011": {
"id": 12011,
"name": "「电磁暴」-贰式",
"rarity": "B",
"profession": 3
},
"12012": {
"id": 12012,
"name": "「电磁暴」-叁式",
"rarity": "B",
"profession": 3
},
"12013": {
"id": 12013,
"name": "「恒等式」-本格",
"rarity": "B",
"profession": 5
},
"12014": {
"id": 12014,
"name": "「恒等式」-变格",
"rarity": "B",
"profession": 5
},
"13001": {
"id": 13001,
"name": "街头巨星",
"rarity": "A",
"profession": 1
},
"13002": {
"id": 13002,
"name": "时光切片",
"rarity": "A",
"profession": 4
},
"13003": {
"id": 13003,
"name": "雨林饕客",
"rarity": "A",
"profession": 3
},
"13004": {
"id": 13004,
"name": "星徽引擎",
"rarity": "A",
"profession": 1
},
"13005": {
"id": 13005,
"name": "人为刀俎",
"rarity": "A",
"profession": 2
},
"13006": {
"id": 13006,
"name": "贵重骨核",
"rarity": "A",
"profession": 2
},
"13007": {
"id": 13007,
"name": "正版变身器",
"rarity": "A",
"profession": 5
},
"13008": {
"id": 13008,
"name": "双生泣星",
"rarity": "A",
"profession": 3
},
"13009": {
"id": 13009,
"name": "触电唇彩",
"rarity": "A",
"profession": 3
},
"13010": {
"id": 13010,
"name": "兔能环",
"rarity": "A",
"profession": 5
},
"13011": {
"id": 13011,
"name": "春日融融",
"rarity": "A",
"profession": 5
},
"13013": {
"id": 13013,
"name": "鎏金花信",
"rarity": "A",
"profession": 1
},
"13015": {
"id": 13015,
"name": "强音热望",
"rarity": "A",
"profession": 1
},
"13101": {
"id": 13101,
"name": "德玛拉电池Ⅱ型",
"rarity": "A",
"profession": 2
},
"13103": {
"id": 13103,
"name": "聚宝箱",
"rarity": "A",
"profession": 4
},
"13106": {
"id": 13106,
"name": "家政员",
"rarity": "A",
"profession": 1
},
"13108": {
"id": 13108,
"name": "仿制星徽引擎",
"rarity": "A",
"profession": 1
},
"13111": {
"id": 13111,
"name": "旋钻机-赤轴",
"rarity": "A",
"profession": 1
},
"13112": {
"id": 13112,
"name": "比格气缸",
"rarity": "A",
"profession": 5
},
"13113": {
"id": 13113,
"name": "含羞恶面",
"rarity": "A",
"profession": 4
},
"13115": {
"id": 13115,
"name": "好斗的阿炮",
"rarity": "A",
"profession": 4
},
"13127": {
"id": 13127,
"name": "维序者-特化型",
"rarity": "A",
"profession": 5
},
"13128": {
"id": 13128,
"name": "轰鸣座驾",
"rarity": "A",
"profession": 3
},
"14001": {
"id": 14001,
"name": "加农转子",
"rarity": "A",
"profession": 1
},
"14002": {
"id": 14002,
"name": "逍遥游球",
"rarity": "A",
"profession": 4
},
"14003": {
"id": 14003,
"name": "左轮转子",
"rarity": "A",
"profession": 2
},
"14102": {
"id": 14102,
"name": "钢铁肉垫",
"rarity": "S",
"profession": 1
},
"14104": {
"id": 14104,
"name": "硫磺石",
"rarity": "S",
"profession": 1
},
"14107": {
"id": 14107,
"name": "奔袭獠牙",
"rarity": "S",
"profession": 5
},
"14109": {
"id": 14109,
"name": "霰落星殿",
"rarity": "S",
"profession": 3
},
"14110": {
"id": 14110,
"name": "燃狱齿轮",
"rarity": "S",
"profession": 2
},
"14114": {
"id": 14114,
"name": "拘缚者",
"rarity": "S",
"profession": 2
},
"14116": {
"id": 14116,
"name": "焰心桂冠",
"rarity": "S",
"profession": 2
},
"14117": {
"id": 14117,
"name": "灼心摇壶",
"rarity": "S",
"profession": 3
},
"14118": {
"id": 14118,
"name": "嵌合编译器",
"rarity": "S",
"profession": 3
},
"14119": {
"id": 14119,
"name": "深海访客",
"rarity": "S",
"profession": 1
},
"14120": {
"id": 14120,
"name": "残心青囊",
"rarity": "S",
"profession": 1
},
"14121": {
"id": 14121,
"name": "啜泣摇篮",
"rarity": "S",
"profession": 4
},
"14122": {
"id": 14122,
"name": "时流贤者",
"rarity": "S",
"profession": 3
},
"14124": {
"id": 14124,
"name": "防暴者Ⅵ型",
"rarity": "S",
"profession": 1
},
"14125": {
"id": 14125,
"name": "玉壶青冰",
"rarity": "S",
"profession": 2
},
"14126": {
"id": 14126,
"name": "淬锋钳刺",
"rarity": "S",
"profession": 3
}
}

View file

@ -1,162 +0,0 @@
{
"14109": {
"Param": {
"CriticalDamageBase": [
0.5,
0.57,
0.65,
0.72,
0.8
],
"IceDmgAdd": [
0.2,
0.23,
0.26,
0.29,
0.32
]
}
},
"14119": {
"Param": {
"IceDmgAdd": [
0.25,
0.315,
0.38,
0.445,
0.5
],
"CriticalChanceBase": [
0.2,
0.25,
0.3,
0.35,
0.4
]
}
},
"14102": {
"Param": {
"All_DmgAdd": [
0.25,
0.315,
0.38,
0.445,
0.5
],
"Physical_DmgAdd": [
0.2,
0.25,
0.3,
0.35,
0.4
]
}
},
"14104": {
"Param": {
"AttackAddedRatio": [
0.035,
0.044,
0.052,
0.06,
0.07
]
}
},
"14124": {
"Param": {
"A_DmgAdd": [
0.35,
0.435,
0.52,
0.605,
0.7
]
}
},
"13001": {
"Param": {
"R_DmgAdd": [
0.15,
0.172,
0.195,
0.217,
0.24
]
}
},
"13004": {
"Param": {
"AttackAddedRatio": [
0.12,
0.138,
0.156,
0.174,
0.192
]
}
},
"13013": {
"Param": {
"EUP_DmgAdd": [
0.15,
0.172,
0.195,
0.217,
0.24
]
}
},
"13106": {
"Param": {
"EUP_DmgAdd": [
0.03,
0.035,
0.04,
0.044,
0.048
]
}
},
"13108": {
"Param": {
"Physical_DmgAdd": [
0.36,
0.41,
0.465,
0.52,
0.575
]
}
},
"13111": {
"Param": {
"A_DmgAdd": [
0.50,
0.575,
0.65,
0.725,
0.80
]
}
},
"14121": {
"Param": {
"All_DmgAdd": [
0.1,
0.125,
0.15,
0.175,
0.20
],
"All_DmgAdd_Max": [
0.102,
0.12,
0.15,
0.18,
0.198
]
}
}
}

View file

@ -39,56 +39,57 @@
<div class="title">
<% include(sys.specialTitle, {en: 'PROPERTY' , cn: '属性' , count: 6 }) %>
</div>
{{set basic_properties = charData.basic_properties}}
<div class="list">
<div class="properties">
<div class="prop-icon hpmax"></div>
<div class="label yellow">生命值</div>
<div class="value">{{charData.basic_properties.hpmax.final}}</div>
<div class="value">{{basic_properties.hpmax.final}}</div>
</div>
<div class="properties">
<div class="prop-icon attack"></div>
<div class="label yellow">攻击力</div>
<div class="value">{{charData.basic_properties.attack.final}}</div>
<div class="value">{{basic_properties.attack.final}}</div>
</div>
<div class="properties">
<div class="prop-icon def"></div>
<div class="label yellow">防御力</div>
<div class="value">{{charData.basic_properties.def.final}}</div>
<div class="value">{{basic_properties.def.final}}</div>
</div>
<div class="properties">
<div class="prop-icon breakstun"></div>
<div class="label">冲击力</div>
<div class="value">{{charData.basic_properties.breakstun.final}}</div>
<div class="value">{{basic_properties.breakstun.final}}</div>
</div>
<div class="properties">
<div class="prop-icon crit"></div>
<div class="label blue">暴击率</div>
<div class="value">{{charData.basic_properties.crit.final}}</div>
<div class="value">{{basic_properties.crit.final}}</div>
</div>
<div class="properties">
<div class="prop-icon critdam"></div>
<div class="label blue">暴击伤害</div>
<div class="value">{{charData.basic_properties.critdam.final}}</div>
<div class="value">{{basic_properties.critdam.final}}</div>
</div>
<div class="properties">
<div class="prop-icon elementabnormalpower"></div>
<div class="label">异常掌控</div>
<div class="value">{{charData.basic_properties.elementabnormalpower.final}}</div>
<div class="value">{{basic_properties.elementabnormalpower.final}}</div>
</div>
<div class="properties">
<div class="prop-icon elementmystery"></div>
<div class="label">异常精通</div>
<div class="value">{{charData.basic_properties.elementmystery.final}}</div>
<div class="value">{{basic_properties.elementmystery.final}}</div>
</div>
<div class="properties">
<div class="prop-icon penratio"></div>
<div class="label">穿透率</div>
<div class="value">{{charData.basic_properties.penratio.final}}</div>
<div class="value">{{basic_properties.penratio.final}}</div>
</div>
<div class="properties">
<div class="prop-icon sprecover"></div>
<div class="label">能量恢复</div>
<div class="value">{{charData.basic_properties.sprecover.final}}</div>
<div class="value">{{basic_properties.sprecover.final}}</div>
</div>
</div>
</div>
@ -223,9 +224,9 @@
</div>
{{each damages damage}}
<div class="dmg-tr">
<div class="dmg-td no-zzz-font">{{damage.title}}</div>
<div class="dmg-td">{{damage.value.cd.toFixed(0)}}</div>
<div class="dmg-td">{{damage.value.qw.toFixed(0)}}</div>
<div class="dmg-td no-zzz-font">{{damage.skill.name}}</div>
<div class="dmg-td">{{damage.result.critDMG.toFixed(0)}}</div>
<div class="dmg-td">{{damage.result.expectDMG.toFixed(0)}}</div>
</div>
{{/each}}
</div>