ZZZ-Plugin/model/damage/BuffManager.ts

296 lines
No EOL
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}