Files
bettergi-scripts-list/repo/js/RoleExperienceCalculation/lib/calculator.js
sk skr~~~ 373da85817 JS脚本:RoleExperienceCalculation (#2605)
* Add files via upload

* Add files via upload

* Add files via upload

* Delete repo/js/RoleExperienceCalculation/desktop.ini
2026-01-03 10:25:44 +08:00

271 lines
11 KiB
JavaScript
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.
// 角色经验计算 - 计算模块
// 经验
var expCalculator= {
// 核心数据1-90级每级升级所需经验值 (来源: 原神WIKI)[citation:5]
LEVEL_EXP_REQUIREMENTS: [
0, // Lv1 之前为0
1000, 1325, 1700, 2150, 2625, // Lv1-5
3150, 3725, 4350, 5000, 5700, // Lv6-10
6450, 7225, 8050, 8925, 9825, // Lv11-15
10750, 11725, 12725, 13775, 14875, // Lv16-20
16800, 18000, 19250, 20550, 21875, // Lv21-25
23250, 24650, 26100, 27575, 29100, // Lv26-30
30650, 32250, 33875, 35550, 37250, // Lv31-35
38975, 40750, 42575, 44425, 46300, // Lv36-40
50625, 52700, 54775, 56900, 59075, // Lv41-45
61275, 63525, 65800, 68125, 70475, // Lv46-50
76500, 79050, 81650, 84275, 86950, // Lv51-55
89650, 92400, 95175, 98000, 100875, // Lv56-60
108950, 112050, 115175, 118325, 121525, // Lv61-65
124775, 128075, 131400, 134775, 138175, // Lv66-70
148700, 152375, 156075, 159825, 163600, // Lv71-75
167425, 171300, 175225, 179175, 183175, // Lv76-80
216225, 243025, 273100, 306800, 344600, // Lv81-85
386950, 434425, 487625, 547200, 0 // Lv86-90 (90级为满级)
],
// 经验书定义[citation:2]
EXP_BOOKS: {
PURPLE: {name: '大英雄的经验', experience: 20000},
BLUE: {name: '冒险家的经验', experience: 5000},
GREEN: {name: '流浪者的经验', experience: 1000}
},
/**
* 计算从当前等级/经验升级到目标等级所需的总经验值。
* @param {number} currentLevel - 当前等级 (1-89)
* @param {number} currentExp - 当前等级下已累积的经验值
* @param {number} targetLevel - 目标等级 (2-90)
* @returns {number} 所需总经验值,如果输入无效返回 -1
*/
calculateExpRequired: function (currentLevel, currentExp, targetLevel) {
// 参数校验
if (currentLevel < 1 || currentLevel >= 90 ||
targetLevel <= currentLevel || targetLevel > 90 ||
currentExp < 0) {
console.error('Invalid input parameters.');
return -1;
}
let totalExpNeeded = 0;
// 1. 减去当前等级已积累的经验
totalExpNeeded -= currentExp;
// 2. 累加从当前等级到目标等级-1所需的每一级经验
for (let lvl = currentLevel; lvl < targetLevel; lvl++) {
totalExpNeeded += expCalculator.LEVEL_EXP_REQUIREMENTS[lvl];
}
return totalExpNeeded;
},
/**
* 将经验值转换为所需的各种经验书数量(优先使用高等级书籍)。
* @param {number} expRequired - 所需总经验值
* @returns {Object} 包含三种经验书所需数量的对象
*/
convertExpToBooks: function (expRequired) {
let remainingExp = expRequired;
const purpleBooks = Math.floor(remainingExp / expCalculator.EXP_BOOKS.PURPLE.experience);
remainingExp %= expCalculator.EXP_BOOKS.PURPLE.experience;
const blueBooks = Math.floor(remainingExp / expCalculator.EXP_BOOKS.BLUE.experience);
remainingExp %= expCalculator.EXP_BOOKS.BLUE.experience;
const greenBooks = Math.ceil(remainingExp / expCalculator.EXP_BOOKS.GREEN.experience); // 向上取整因为无法提供不足1000的经验
return {
purple: purpleBooks,
blue: blueBooks,
green: greenBooks,
// 返回一个详细的文本摘要,方便直接使用
summary: `共需: ${purpleBooks}本[${expCalculator.EXP_BOOKS.PURPLE.name}] + ${blueBooks}本[${expCalculator.EXP_BOOKS.BLUE.name}] + ${greenBooks}本[${expCalculator.EXP_BOOKS.GREEN.name}]`
};
}
}
// 树脂
var resinCalculation = {
// 世界等级掉落配置:每种经验书的 [最小数量, 最大数量]
WORLD_LEVEL_DROP_CONFIG : {
0: { '流浪者的经验': [7, 8], '冒险家的经验': [3, 4] },
1: { '流浪者的经验': [10, 12], '冒险家的经验': [5, 6] },
2: { '冒险家的经验': [10, 11] },
3: { '冒险家的经验': [13, 14] },
4: { '大英雄的经验': [2, 3], '冒险家的经验': [6, 7] },
5: { '大英雄的经验': [3, 4], '冒险家的经验': [6, 7] },
6: { '大英雄的经验': [4, 5], '冒险家的经验': [6, 7] },
7: { '大英雄的经验': [4, 5], '冒险家的经验': [6, 7] },
8: { '大英雄的经验': [4, 5], '冒险家的经验': [6, 7] },
9: { '大英雄的经验': [4, 5], '冒险家的经验': [6, 7] }
},
/**
* 计算单次刷取的期望经验值和概率分布
* @param {string|boolean} worldLevel - 世界等级
* @returns {Object} {expectedExp, minExp, distribution}
*/
calculateSingleRunExp: function(worldLevel) {
// 添加中文到英文的映射
const BOOK_NAME_MAPPING = {
'流浪者的经验': 'GREEN',
'冒险家的经验': 'BLUE',
'大英雄的经验': 'PURPLE'
};
const config = this.WORLD_LEVEL_DROP_CONFIG[worldLevel];
if (!config || Object.keys(config).length === 0) {
return { expectedExp: 0, minExp: 0, distribution: [] };
}
let outcomeDetails = [];
for (const [chineseBookName, range] of Object.entries(config)) {
const englishKey = BOOK_NAME_MAPPING[chineseBookName];
// 检查映射是否成功
if (!englishKey) {
console.warn(`未找到对应的英文键名: ${chineseBookName}`);
continue;
}
const [min, max] = range;
const possibilities = [];
// 使用英文键名访问 EXP_BOOKS
const bookConfig = expCalculator.EXP_BOOKS[englishKey];
if (!bookConfig) {
console.warn(`在 EXP_BOOKS 中未找到: ${englishKey}`);
continue;
}
for (let qty = min; qty <= max; qty++) {
possibilities.push({
bookKey: englishKey,
chineseName: chineseBookName, // 可选:保留中文名
qty,
experience: bookConfig.experience * qty
});
}
outcomeDetails.push(possibilities);
}
// 如果 outcomeDetails 为空,返回默认值
if (outcomeDetails.length === 0) {
console.error('没有有效的掉落配置');
return { expectedExp: 0, minExp: 0, distribution: [] };
}
// 计算所有组合的概率分布(假设每种数量概率相等)
const distributionMap = new Map();
function dfs(index, currentExp, probability) {
if (index === outcomeDetails.length) {
distributionMap.set(currentExp, (distributionMap.get(currentExp) || 0) + probability);
return;
}
for (const outcome of outcomeDetails[index]) {
dfs(index + 1, currentExp + outcome.experience, probability / outcomeDetails[index].length);
}
}
dfs(0, 0, 1.0);
// 转换为排序后的数组并计算期望值
let distribution = [];
let expectedExp = 0;
let minExp = Infinity;
for (const [experience, prob] of distributionMap.entries()) {
distribution.push({ experience, probability: prob });
expectedExp += experience * prob;
if (experience < minExp) minExp = experience;
}
distribution.sort((a, b) => a.experience - b.experience);
return {
expectedExp: Math.round(expectedExp),
minExp: minExp,
distribution
};
},
/**
* 主计算函数:分别计算使用浓缩树脂和原粹树脂的刷取次数
* @param {number} requiredExp - 所需经验值
* @param {string|boolean} worldLevel - 世界等级 (0-9)
* @param {{}} options - 计算选项
* @param {boolean} options.useExpected - true:使用期望值计算(平均情况), false:使用保底值计算(最非情况)
* @param {number} options.resinPerCondensed - 合成一个浓缩树脂所需的原粹树脂默认为60
* @returns {Object} 计算结果
*/
calculateExpBookRequirements: function(requiredExp, worldLevel, options = {}) {
const {
useExpected = true,
resinPerCondensed = 60
} = options;
// 获取单次刷取的经验数据
const { expectedExp, minExp, distribution } = this.calculateSingleRunExp(worldLevel);
if (expectedExp === 0) {
return {
error: `世界等级 ${worldLevel} 的掉落配置不完整,无法计算。请补充 WORLD_LEVEL_DROP_CONFIG 中的数据。`
};
}
// 选择计算基准:期望值(平均)或保底值(最小)
const expPerRun = useExpected ? expectedExp : minExp;
const calcMode = useExpected ? '期望值(平均)' : '保底值(最小)';
// 计算总刷取次数
const totalRuns = Math.ceil(requiredExp / expPerRun);
// 计算浓缩树脂相关次数
const condensedRuns = Math.ceil(totalRuns / (resinPerCondensed / 20)); // 全部使用浓缩树脂的次数
// 原粹树脂需要刷取的次数(如果没有浓缩树脂)
const originalResinRuns = totalRuns; // 全部使用原粹树脂的次数
// 计算原粹树脂总消耗量
const totalOriginalResin = totalRuns * 20; // 每次副本固定消耗20原粹树脂
// 计算需要合成的浓缩树脂数量
const condensedResinNeeded = Math.ceil(totalOriginalResin / resinPerCondensed);
return {
// 计算基准信息
calcMode,
expPerRunUsed: expPerRun,
singleRunStats: {
expectedExp,
minExp,
possibleExpRange: distribution.length > 0 ?
`${distribution[0].experience} ~ ${distribution[distribution.length - 1].experience}` : 'N/A'
},
// 核心结果:分别输出两种树脂的刷取次数
runs: {
// 使用浓缩树脂需要刷取的次数
usingCondensedResin: condensedRuns,
// 使用原粹树脂需要刷取的次数
usingOriginalResin: originalResinRuns,
// 总副本挑战次数(两种方式相同)
totalChallenges: totalRuns
},
// 资源消耗
resinCost: {
// 原粹树脂总消耗量
totalOriginalResin,
// 需要合成的浓缩树脂数量
condensedResinNeeded,
// 合成浓缩树脂所需原粹树脂总量
resinForCondensed: condensedResinNeeded * resinPerCondensed
},
};
},
}