mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-16 13:17:32 +00:00
支持自定义评分规则、支持动态选用、更新相关文档
This commit is contained in:
parent
06ec5152a0
commit
504d2792a8
11 changed files with 217 additions and 114 deletions
|
|
@ -13,7 +13,7 @@
|
|||
>
|
||||
> 在你使用之前请**务必**完整阅读 `README` 内容,如因无视 `README` 遇到的问题,在提问时难免遭受嘲笑。
|
||||
>
|
||||
> 如需自定义伤害计算请查看[此说明](./model/damage/README.md)
|
||||
> **自定义词条权重**和**自定义伤害计算**请查看[此教程](./model/damage/README.md)
|
||||
|
||||
# 安装
|
||||
|
||||
|
|
|
|||
54
lib/score.js
54
lib/score.js
|
|
@ -6,41 +6,39 @@ import { nameToId } from './convert/property.js';
|
|||
export const baseValueData = getMapData('EquipBaseValue');
|
||||
const equipScore = getMapData('EquipScore');
|
||||
/** @type {{ [charID: string]: { [propID: string]: number } }} */
|
||||
export const scoreData = Object.create(null);
|
||||
for (const charName in equipScore) {
|
||||
// 兼容原ID格式
|
||||
if (+charName) {
|
||||
scoreData[charName] = equipScore[charName];
|
||||
continue;
|
||||
};
|
||||
const charID = charNameToID(charName);
|
||||
if (!charID)
|
||||
continue;
|
||||
scoreData[charID] = Object.create(null);
|
||||
for (const propName in equipScore[charName]) {
|
||||
const propID = nameToId(propName);
|
||||
if (!propID || !equipScore[charName][propName])
|
||||
export const scoreWeight = Object.create(null);
|
||||
|
||||
/**
|
||||
* 将权重数据格式化为ID格式权重数据并处理小词条
|
||||
* @returns {{ [propID: string]: number }}
|
||||
*/
|
||||
export function formatScoreWeight(oriScoreWeight) {
|
||||
if (!oriScoreWeight) return false;
|
||||
if (typeof oriScoreWeight !== 'object') return false;
|
||||
const weight = Object.create(null);
|
||||
for (const propName in oriScoreWeight) {
|
||||
if (!oriScoreWeight[propName])
|
||||
continue;
|
||||
scoreData[charID][propID] = equipScore[charName][propName];
|
||||
const propID = +propName || nameToId(propName);
|
||||
if (!propID)
|
||||
continue;
|
||||
weight[propID] = oriScoreWeight[propName];
|
||||
};
|
||||
/** 小生命、小攻击、小防御映射为大生命、大攻击、大防御的1/3 */
|
||||
for (const [small, big] of [[11103, 11102], [12103, 12102], [13103, 13102]]) {
|
||||
if (scoreData[charID][big]) {
|
||||
scoreData[charID][small] ??= scoreData[charID][big] / 3;
|
||||
if (weight[big]) {
|
||||
weight[small] ??= weight[big] / 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有分数数据
|
||||
* @param {string} charID 角色id
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const hasScoreData = charID => {
|
||||
return (
|
||||
scoreData[charID] &&
|
||||
Object.keys(scoreData[charID]).length > 0
|
||||
);
|
||||
for (const charName in equipScore) {
|
||||
// 兼容原ID格式
|
||||
const charID = +charName || charNameToID(charName);
|
||||
if (!charID)
|
||||
continue;
|
||||
scoreWeight[charID] = formatScoreWeight(equipScore[charName]);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import {
|
|||
getSmallSquareAvatar,
|
||||
getSquareAvatar,
|
||||
} from '../lib/download.js';
|
||||
import { formatScoreWeight, scoreWeight } from '../lib/score.js';
|
||||
import { avatar_ability, scoreFnc } from './damage/avatar.js';
|
||||
import { imageResourcesPath } from '../lib/path.js';
|
||||
import { Equip, Weapon } from './equip.js';
|
||||
import { Property } from './property.js';
|
||||
import { Skill } from './skill.js';
|
||||
import { avatar_ability } from './damage/avatar.js';
|
||||
import { hasScoreData, scoreData } from '../lib/score.js';
|
||||
|
||||
import _ from 'lodash';
|
||||
import fs from 'fs';
|
||||
|
|
@ -262,8 +262,9 @@ export class ZZZAvatarInfo {
|
|||
this.isNew = isNew;
|
||||
/** @type {number} 等级级别(取十位数字)*/
|
||||
this.level_rank = Math.floor(this.level / 10);
|
||||
this.scoreWeight = formatScoreWeight(scoreFnc[this.id] && scoreFnc[this.id](this)?.[1]) || scoreWeight[this.id];
|
||||
for (const equip of this.equip) {
|
||||
equip.get_score(this.id);
|
||||
equip.get_score(this.scoreWeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -356,7 +357,7 @@ export class ZZZAvatarInfo {
|
|||
|
||||
/** @type {number|boolean} */
|
||||
get equip_score() {
|
||||
if (hasScoreData(this.id)) {
|
||||
if (this.scoreWeight) {
|
||||
let score = 0;
|
||||
for (const equip of this.equip) {
|
||||
score += equip.score;
|
||||
|
|
@ -435,7 +436,7 @@ export class ZZZAvatarInfo {
|
|||
|
||||
/** 面板属性label效果 */
|
||||
get_label(propID) {
|
||||
const base = scoreData[this.id][propID];
|
||||
const base = this.scoreWeight[propID];
|
||||
if (!base) return '';
|
||||
return base === 1 ? 'yellow' :
|
||||
base >= 0.75 ? 'blue' :
|
||||
|
|
|
|||
|
|
@ -1,3 +1,51 @@
|
|||
# 词条权重自定义
|
||||
|
||||
## 方法一:预设方法(推荐)
|
||||
|
||||
### 基础步骤
|
||||
|
||||
1. 复制模板文件:[ZZZ-plugin/model/damage/character/模板/score.js](./character/模板/score.js)
|
||||
|
||||
2. 进入角色数据文件夹:[ZZZ-plugin/model/damage/character/角色名](./character/)
|
||||
|
||||
3. 粘贴文件,并将粘贴的模板文件重命名为**score_user.js**
|
||||
|
||||
4. 打开**score_user.js**,根据需要调整权重数值
|
||||
|
||||
5. 保存并重启
|
||||
|
||||
示例图:
|
||||
|
||||
<p align="center">
|
||||
<img width=800 src="https://s2.loli.net/2025/03/27/OcuIPDyE5sHSJZw.jpg" title="词条权重自定义基础步骤">
|
||||
</p>
|
||||
|
||||
### 进阶操作
|
||||
|
||||
> 将崽底层日志模式切换为**debug**模式,可在控制台查看评分计算详细过程;且会自动监听现有评分计算文件实时热更新。可按需开启
|
||||
|
||||
在函数体中,可根据玩家角色数据**动态选用**不同的权重方案
|
||||
|
||||
- 函数参数:[ZZZAvatarInfo](../avatar.js#L173)(角色数据)
|
||||
|
||||
- 函数返回值:
|
||||
- 元组:[评分规则名, 权重数据]
|
||||
- 类型:**[string, { [词条名: string]: number }]**
|
||||
- 若返回其他类型,会自动选择默认评分规则
|
||||
|
||||
## 方法二:直接修改默认权重
|
||||
|
||||
> 注意:直接修改插件所属文件,将会导致后续该文件更新冲突。若你不清楚如何解决冲突,请使用[方法一](#方法一预设方法推荐)
|
||||
|
||||
打开插件默认词条权重文件直接修改相应权重保存即可,重启生效
|
||||
|
||||
文件路径:[ZZZ-plugin/resources/map/EquipScore.json](../../resources/map/EquipScore.json)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
感谢**银狐**对评分计算规则的指导建议
|
||||
|
||||
[点此查看](./Score.ts)评分计算规则源码,如果有任何对评分计算的想法建议,欢迎与我联系:ucpr251@gmail.com
|
||||
|
||||
# 伤害计算自定义
|
||||
|
||||
|
|
@ -7,7 +55,7 @@
|
|||
|
||||
后文将带你入门插件伤害计算逻辑,只要你需要自定义伤害计算,都建议你完整阅读:
|
||||
|
||||
> 底层已对伤害计算进行了规范化、模块化,只需要填参数就可以实现常见的伤害计算逻辑,即使不懂代码也可以参考模板独立完成。若存在问题、建议,可于插件群内询问,或联系我的邮箱:UCPr251@gmail.com
|
||||
> 底层已对伤害计算进行了规范化、模块化,只需要填参数就可以实现常见的伤害计算逻辑,即使不懂代码也可以参考模板独立完成。若存在问题、建议,可于插件群内询问,或与我沟通:ucpr251@gmail.com
|
||||
|
||||
伤害计算需要明确这几部分:**初始属性**、**局内Buff**、**技能属性**、**敌方属性**,后文将分别说明
|
||||
|
||||
|
|
@ -231,6 +279,7 @@ Buff来源可分为三大类:武器、套装、角色(影画、核心被动
|
|||
|
||||
> - 追加攻击
|
||||
> - 直接以“追加攻击”命名
|
||||
> - 追加攻击不被认为是一种新的输出手段(技能),它更类似于原直伤输出的一个特殊属性,使该伤害能同时享受对其**原所属技能类型**的增益和**仅对追加攻击生效**的增益,故一般不将其单独作为技能类型而是在技能属性的重定向参数中添加**追加攻击**参数,请参考[技能重定向说明注意事项](#技能类型命名对Buff作用的影响)
|
||||
|
||||
#### 技能类型命名解释说明
|
||||
|
||||
|
|
@ -281,6 +330,7 @@ buff作用范围将以技能类型命名为依据向后覆盖。以上述[艾莲
|
|||
- 对于`“X"(造成的伤害)被视为“Y”(伤害)`此类特殊技能,需要指定技能**重定向参数**,同时上述buff覆盖规则会发生变化,具体请参考[源码内描述](./Calculator.ts#L22)
|
||||
|
||||
> 需要注意的是:即使出现`“X"(造成的伤害)被视为“Y”(伤害)`,对**Y**类型的增益**X**不一定能吃到,视具体情况变化
|
||||
> 对于被视为**追加攻击**伤害的技能,其重定向参数一般为包含其**原技能类型**和**追加攻击**的数组,因为它可以享受到分别对两者生效的不同增益(以游戏内具体情况为准),参考[**零号·安比**伤害计算文件](./character/零号·安比/calc.js#L51)
|
||||
|
||||
### 技能倍率
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { baseValueData, scoreData } from '../../lib/score.js';
|
||||
import { idToName } from '../../lib/convert/property.js';
|
||||
import { baseValueData } from '../../lib/score.js';
|
||||
import { getMapData } from '../../utils/file.js';
|
||||
var rarity;
|
||||
(function (rarity) {
|
||||
|
|
@ -10,13 +10,13 @@ var rarity;
|
|||
const mainStats = getMapData('EquipMainStats');
|
||||
const subStats = Object.keys(baseValueData).map(Number);
|
||||
export default class Score {
|
||||
scoreData;
|
||||
equip;
|
||||
weight;
|
||||
partition;
|
||||
userMainStat;
|
||||
constructor(charID, equip) {
|
||||
this.scoreData = scoreData[charID];
|
||||
constructor(equip, weight) {
|
||||
this.equip = equip;
|
||||
this.weight = weight;
|
||||
this.partition = this.equip.equipment_type;
|
||||
this.userMainStat = this.equip.main_properties[0].property_id;
|
||||
}
|
||||
|
|
@ -37,13 +37,13 @@ export default class Score {
|
|||
}
|
||||
get_max_count() {
|
||||
const subMaxStats = subStats
|
||||
.filter(p => p !== this.userMainStat && this.scoreData[p])
|
||||
.sort((a, b) => this.scoreData[b] - this.scoreData[a]).slice(0, 4);
|
||||
.filter(p => p !== this.userMainStat && this.weight[p])
|
||||
.sort((a, b) => this.weight[b] - this.weight[a]).slice(0, 4);
|
||||
if (!subMaxStats.length)
|
||||
return 0;
|
||||
logger.debug(`[${this.partition}号位]理论副词条:` + subMaxStats.map(idToName).reduce((a, p, i) => a + `${p}*${this.scoreData[subMaxStats[i]].toFixed(2)} `, ''));
|
||||
let count = this.scoreData[subMaxStats[0]] * 6;
|
||||
subMaxStats.slice(1).forEach(p => count += this.scoreData[p] || 0);
|
||||
logger.debug(`[${this.partition}号位]理论副词条:` + subMaxStats.map(idToName).reduce((a, p, i) => a + `${p}*${this.weight[subMaxStats[i]].toFixed(2)} `, ''));
|
||||
let count = this.weight[subMaxStats[0]] * 6;
|
||||
subMaxStats.slice(1).forEach(p => count += this.weight[p] || 0);
|
||||
logger.debug(`[${this.partition}号位]理论词条数:${logger.blue(count)}`);
|
||||
return count;
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ export default class Score {
|
|||
let count = 0;
|
||||
for (const prop of this.equip.properties) {
|
||||
const propID = prop.property_id;
|
||||
const weight = this.scoreData[propID];
|
||||
const weight = this.weight[propID];
|
||||
if (weight) {
|
||||
logger.debug(`[${this.partition}号位]实际副词条:${idToName(propID)} ${logger.green(prop.count + 1)}*${weight}`);
|
||||
count += weight * (prop.count + 1);
|
||||
|
|
@ -75,17 +75,17 @@ export default class Score {
|
|||
return score;
|
||||
}
|
||||
const mainMaxStat = mainStats[this.partition]
|
||||
.filter(p => this.scoreData[p])
|
||||
.sort((a, b) => this.scoreData[b] - this.scoreData[a])[0];
|
||||
const mainScore = (mainMaxStat ? 12 * (this.scoreData[this.userMainStat] || 0) / this.scoreData[mainMaxStat] : 12) * this.get_level_multiplier();
|
||||
.filter(p => this.weight[p])
|
||||
.sort((a, b) => this.weight[b] - this.weight[a])[0];
|
||||
const mainScore = (mainMaxStat ? 12 * (this.weight[this.userMainStat] || 0) / this.weight[mainMaxStat] : 12) * this.get_level_multiplier();
|
||||
const subScore = actual_count / max_count * 43;
|
||||
const score = (mainScore + subScore) * rarity_multiplier;
|
||||
logger.debug(`[${this.partition}号位] ${logger.magenta(`(${mainScore} + ${subScore}) * ${rarity_multiplier} = ${score}`)}`);
|
||||
return score;
|
||||
}
|
||||
static main(charID, equip) {
|
||||
static main(equip, weight) {
|
||||
try {
|
||||
return new Score(charID, equip).get_score();
|
||||
return new Score(equip, weight).get_score();
|
||||
}
|
||||
catch (err) {
|
||||
logger.error('角色驱动盘评分计算错误:', err);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { Equip } from '../equip.js'
|
||||
import { baseValueData, scoreData } from '../../lib/score.js'
|
||||
import type { scoreWeight } from '../../lib/score.js'
|
||||
import { idToName } from '../../lib/convert/property.js'
|
||||
import { baseValueData } from '../../lib/score.js'
|
||||
import { getMapData } from '../../utils/file.js'
|
||||
|
||||
enum rarity { S, A, B }
|
||||
|
|
@ -11,15 +12,16 @@ const mainStats = getMapData('EquipMainStats') as { [partition: string]: number[
|
|||
const subStats = Object.keys(baseValueData).map(Number)
|
||||
|
||||
export default class Score {
|
||||
protected scoreData: typeof scoreData[string]
|
||||
protected equip: Equip
|
||||
/** 词条权重 */
|
||||
protected weight: typeof scoreWeight[string]
|
||||
/** 驱动盘n号位 */
|
||||
protected partition: number
|
||||
/** 用户主词条 */
|
||||
protected userMainStat: number
|
||||
constructor(charID: string, equip: Equip) {
|
||||
this.scoreData = scoreData[charID]
|
||||
constructor(equip: Equip, weight: Score['weight']) {
|
||||
this.equip = equip
|
||||
this.weight = weight
|
||||
this.partition = this.equip.equipment_type
|
||||
this.userMainStat = this.equip.main_properties[0].property_id
|
||||
}
|
||||
|
|
@ -47,12 +49,12 @@ export default class Score {
|
|||
get_max_count() {
|
||||
/** 权重最大的4个副词条 */
|
||||
const subMaxStats = subStats
|
||||
.filter(p => p !== this.userMainStat && this.scoreData[p])
|
||||
.sort((a, b) => this.scoreData[b] - this.scoreData[a]).slice(0, 4)
|
||||
.filter(p => p !== this.userMainStat && this.weight[p])
|
||||
.sort((a, b) => this.weight[b] - this.weight[a]).slice(0, 4)
|
||||
if (!subMaxStats.length) return 0
|
||||
logger.debug(`[${this.partition}号位]理论副词条:` + subMaxStats.map(idToName).reduce((a, p, i) => a + `${p}*${this.scoreData[subMaxStats[i]].toFixed(2)} `, ''))
|
||||
let count = this.scoreData[subMaxStats[0]] * 6 // 权重最大副词条强化五次
|
||||
subMaxStats.slice(1).forEach(p => count += this.scoreData[p] || 0) // 其他词条各计入一次
|
||||
logger.debug(`[${this.partition}号位]理论副词条:` + subMaxStats.map(idToName).reduce((a, p, i) => a + `${p}*${this.weight[subMaxStats[i]].toFixed(2)} `, ''))
|
||||
let count = this.weight[subMaxStats[0]] * 6 // 权重最大副词条强化五次
|
||||
subMaxStats.slice(1).forEach(p => count += this.weight[p] || 0) // 其他词条各计入一次
|
||||
logger.debug(`[${this.partition}号位]理论词条数:${logger.blue(count)}`)
|
||||
return count
|
||||
}
|
||||
|
|
@ -62,7 +64,7 @@ export default class Score {
|
|||
let count = 0
|
||||
for (const prop of this.equip.properties) {
|
||||
const propID = prop.property_id
|
||||
const weight = this.scoreData[propID]
|
||||
const weight = this.weight[propID]
|
||||
if (weight) {
|
||||
logger.debug(`[${this.partition}号位]实际副词条:${idToName(propID)} ${logger.green(prop.count + 1)}*${weight}`)
|
||||
count += weight * (prop.count + 1)
|
||||
|
|
@ -90,18 +92,18 @@ export default class Score {
|
|||
}
|
||||
// 456号位
|
||||
const mainMaxStat = mainStats[this.partition]
|
||||
.filter(p => this.scoreData[p])
|
||||
.sort((a, b) => this.scoreData[b] - this.scoreData[a])[0]
|
||||
const mainScore = (mainMaxStat ? 12 * (this.scoreData[this.userMainStat] || 0) / this.scoreData[mainMaxStat] : 12) * this.get_level_multiplier()
|
||||
.filter(p => this.weight[p])
|
||||
.sort((a, b) => this.weight[b] - this.weight[a])[0]
|
||||
const mainScore = (mainMaxStat ? 12 * (this.weight[this.userMainStat] || 0) / this.weight[mainMaxStat] : 12) * this.get_level_multiplier()
|
||||
const subScore = actual_count / max_count * 43
|
||||
const score = (mainScore + subScore) * rarity_multiplier
|
||||
logger.debug(`[${this.partition}号位] ${logger.magenta(`(${mainScore} + ${subScore}) * ${rarity_multiplier} = ${score}`)}`)
|
||||
return score
|
||||
}
|
||||
|
||||
static main(charID: string, equip: Equip) {
|
||||
static main(equip: Equip, weight: Score['weight']) {
|
||||
try {
|
||||
return new Score(charID, equip).get_score()
|
||||
return new Score(equip, weight).get_score()
|
||||
} catch (err) {
|
||||
logger.error('角色驱动盘评分计算错误:', err)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import chokidar from 'chokidar';
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
const damagePath = path.join(pluginPath, 'model', 'damage');
|
||||
export const charData = {};
|
||||
export const charData = Object.create(null);
|
||||
export const scoreFnc = Object.create(null);
|
||||
const calcFnc = {
|
||||
character: {},
|
||||
weapon: {},
|
||||
set: {}
|
||||
character: Object.create(null),
|
||||
weapon: Object.create(null),
|
||||
set: Object.create(null)
|
||||
};
|
||||
async function init() {
|
||||
const isWatch = await (async () => {
|
||||
|
|
@ -33,7 +34,7 @@ function watchFile(path, fnc) {
|
|||
return;
|
||||
const watcher = chokidar.watch(path, {
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 50
|
||||
stabilityThreshold: 251
|
||||
}
|
||||
});
|
||||
watcher.on('change', (path) => {
|
||||
|
|
@ -46,24 +47,42 @@ async function importChar(charName, isWatch = false) {
|
|||
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'));
|
||||
const getFileName = (name, ext) => fs.existsSync(path.join(dir, `${name}_user${ext}`)) ? `${name}_user${ext}` : `${name}${ext}`;
|
||||
const dataPath = path.join(dir, getFileName('data', '.json'));
|
||||
const calcFile = getFileName('calc', '.js');
|
||||
const scoreFile = getFileName('score', '.js');
|
||||
try {
|
||||
const loadCharData = () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
|
||||
const calcFilePath = path.join(dir, calcFile);
|
||||
const loadCalcJS = async () => {
|
||||
if (!fs.existsSync(calcFilePath))
|
||||
return;
|
||||
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`);
|
||||
if (!m.calc && (!m.buffs || !m.skills))
|
||||
throw new Error('伤害计算文件格式错误:' + charName);
|
||||
calcFnc.character[id] = m;
|
||||
};
|
||||
const scoreFilePath = path.join(dir, scoreFile);
|
||||
const loadScoreJS = async () => {
|
||||
if (!fs.existsSync(scoreFilePath))
|
||||
return;
|
||||
const m = await import(`./character/${charName}/${scoreFile}?${Date.now()}`);
|
||||
const fnc = m.default;
|
||||
if (!fnc || typeof fnc !== 'function')
|
||||
throw new Error('评分权重文件格式错误:' + charName);
|
||||
scoreFnc[id] = fnc;
|
||||
};
|
||||
if (isWatch) {
|
||||
watchFile(calcFilePath, () => importChar(charName));
|
||||
watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')));
|
||||
watchFile(dataPath, loadCharData);
|
||||
watchFile(calcFilePath, loadCalcJS);
|
||||
watchFile(scoreFilePath, loadScoreJS);
|
||||
}
|
||||
charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
|
||||
if (!fs.existsSync(calcFilePath))
|
||||
return;
|
||||
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`);
|
||||
if (!m.calc && (!m.buffs || !m.skills))
|
||||
throw new Error('伤害计算文件格式错误');
|
||||
calcFnc.character[id] = m;
|
||||
loadCharData();
|
||||
await loadCalcJS();
|
||||
await loadScoreJS();
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(`导入角色${charName}伤害计算错误:`, e);
|
||||
logger.error(`导入角色${charName}计算文件错误:`, e);
|
||||
}
|
||||
}
|
||||
async function importFile(type, name, isWatch = false) {
|
||||
|
|
|
|||
|
|
@ -12,15 +12,19 @@ import fs from 'fs'
|
|||
const damagePath = path.join(pluginPath, 'model', 'damage')
|
||||
|
||||
export const charData: {
|
||||
[id: number]: {
|
||||
[id: string]: {
|
||||
skill: { [skillName: string]: number[] }
|
||||
buff: { [buffName: string]: number[] }
|
||||
}
|
||||
} = {}
|
||||
} = Object.create(null)
|
||||
|
||||
export const scoreFnc: {
|
||||
[charID: string]: (charData: ZZZAvatarInfo) => [string, { [propID: string]: number }] | undefined
|
||||
} = Object.create(null)
|
||||
|
||||
const calcFnc: {
|
||||
character: {
|
||||
[id: number]: {
|
||||
[charID: string]: {
|
||||
calc?: (buffM: BuffManager, calc: Calculator, avatar: ZZZAvatarInfo) => void
|
||||
buffs: buff[]
|
||||
skills: skill[]
|
||||
|
|
@ -39,9 +43,9 @@ const calcFnc: {
|
|||
}
|
||||
}
|
||||
} = {
|
||||
character: {},
|
||||
weapon: {},
|
||||
set: {}
|
||||
character: Object.create(null),
|
||||
weapon: Object.create(null),
|
||||
set: Object.create(null)
|
||||
}
|
||||
|
||||
async function init() {
|
||||
|
|
@ -54,10 +58,10 @@ async function init() {
|
|||
}
|
||||
})()
|
||||
await Promise.all(fs.readdirSync(path.join(damagePath, 'character')).filter(v => v !== '模板').map(v => importChar(v, isWatch)))
|
||||
for (const type of ['weapon', 'set']) {
|
||||
for (const type of ['weapon', 'set'] as const) {
|
||||
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))
|
||||
.map(v => importFile(type, v.replace('.js', ''), isWatch))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +70,7 @@ function watchFile(path: string, fnc: () => void) {
|
|||
if (!fs.existsSync(path)) return
|
||||
const watcher = chokidar.watch(path, {
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 50
|
||||
stabilityThreshold: 251
|
||||
}
|
||||
})
|
||||
watcher.on('change', (path) => {
|
||||
|
|
@ -79,21 +83,38 @@ 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'))
|
||||
const getFileName = (name: string, ext: '.js' | '.json') =>
|
||||
fs.existsSync(path.join(dir, `${name}_user${ext}`)) ? `${name}_user${ext}` : `${name}${ext}`
|
||||
const dataPath = path.join(dir, getFileName('data', '.json'))
|
||||
const calcFile = getFileName('calc', '.js')
|
||||
const scoreFile = getFileName('score', '.js')
|
||||
try {
|
||||
const loadCharData = () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'))
|
||||
const calcFilePath = path.join(dir, calcFile)
|
||||
if (isWatch) {
|
||||
watchFile(calcFilePath, () => importChar(charName))
|
||||
watchFile(dataPath, () => charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8')))
|
||||
const loadCalcJS = async () => {
|
||||
if (!fs.existsSync(calcFilePath)) return
|
||||
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`)
|
||||
if (!m.calc && (!m.buffs || !m.skills)) throw new Error('伤害计算文件格式错误:' + charName)
|
||||
calcFnc.character[id] = m
|
||||
}
|
||||
charData[id] = JSON.parse(fs.readFileSync(dataPath, 'utf8'))
|
||||
if (!fs.existsSync(calcFilePath)) return
|
||||
const m = await import(`./character/${charName}/${calcFile}?${Date.now()}`)
|
||||
if (!m.calc && (!m.buffs || !m.skills)) throw new Error('伤害计算文件格式错误')
|
||||
calcFnc.character[id] = m
|
||||
const scoreFilePath = path.join(dir, scoreFile)
|
||||
const loadScoreJS = async () => {
|
||||
if (!fs.existsSync(scoreFilePath)) return
|
||||
const m = await import(`./character/${charName}/${scoreFile}?${Date.now()}`)
|
||||
const fnc = m.default
|
||||
if (!fnc || typeof fnc !== 'function') throw new Error('评分权重文件格式错误:' + charName)
|
||||
scoreFnc[id] = fnc
|
||||
}
|
||||
if (isWatch) {
|
||||
watchFile(dataPath, loadCharData)
|
||||
watchFile(calcFilePath, loadCalcJS)
|
||||
watchFile(scoreFilePath, loadScoreJS)
|
||||
}
|
||||
loadCharData()
|
||||
await loadCalcJS()
|
||||
await loadScoreJS()
|
||||
} catch (e) {
|
||||
logger.error(`导入角色${charName}伤害计算错误:`, e)
|
||||
logger.error(`导入角色${charName}计算文件错误:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
17
model/damage/character/模板/score.js
Normal file
17
model/damage/character/模板/score.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/** @type {import('../../avatar.ts')['scoreFnc'][string]} */
|
||||
export default function (avatar) {
|
||||
return ['评分规则名', {
|
||||
"生命值百分比": 0,
|
||||
"攻击力百分比": 0.75,
|
||||
"防御力百分比": 0,
|
||||
"冲击力": 0,
|
||||
"暴击率": 1,
|
||||
"暴击伤害": 1,
|
||||
"穿透率": 0.75,
|
||||
"穿透值": 0.25,
|
||||
"能量回复": 0,
|
||||
"异常精通": 0,
|
||||
"异常掌控": 0,
|
||||
"冰属性伤害提高": 1
|
||||
}]
|
||||
}
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
import { property } from '../lib/convert.js';
|
||||
import { getSuitImage, getWeaponImage } from '../lib/download.js';
|
||||
import {
|
||||
scoreData,
|
||||
hasScoreData,
|
||||
getEquipPropertyEnhanceCount
|
||||
} from '../lib/score.js';
|
||||
import { getEquipPropertyEnhanceCount } from '../lib/score.js';
|
||||
import Score from './damage/Score.js';
|
||||
|
||||
/**
|
||||
|
|
@ -186,14 +182,13 @@ export class Equip {
|
|||
|
||||
/**
|
||||
* 获取装备属性分数
|
||||
* @param {string} charID
|
||||
* @param {{[propID: string]: number}} weight 权重
|
||||
* @returns {number}
|
||||
*/
|
||||
get_score(charID) {
|
||||
if (hasScoreData(charID)) {
|
||||
this.properties.forEach(item => item.base_score = scoreData[charID][item.property_id] || 0);
|
||||
this.score = Score.main(charID, this);
|
||||
}
|
||||
get_score(weight) {
|
||||
if (!weight) return this.score;
|
||||
this.properties.forEach(item => item.base_score = weight[item.property_id] || 0);
|
||||
this.score = Score.main(this, weight);
|
||||
return this.score;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@
|
|||
"穿透率": 0.75,
|
||||
"穿透值": 0.25,
|
||||
"能量回复": 0,
|
||||
"异常精通": 0.25,
|
||||
"异常精通": 0.5,
|
||||
"异常掌控": 0.5,
|
||||
"冰属性伤害提高": 1
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue