You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
6.1 KiB
173 lines
6.1 KiB
import { TableNames } from '../../game/ConfigTableData';
|
|
import type { UnitDefId } from 'db://assets/mx/module/battle/config/BattleConfigTypes';
|
|
import { logChapterMonsterHpCalc } from '../debug/MonsterHpDebugLog';
|
|
import type { ProjectLevelMonsterConfig } from './LevelMonsterConfig';
|
|
|
|
/**
|
|
* 最终 HP 额外倍率:max(1, 局外紫色及以上武器最高等级 / 5)
|
|
*/
|
|
export function calcSpecialReplicaFinalHpScale(): number {
|
|
const purpleMaxLv = Math.max(1, gg.data.project.getMaxLevelPurpleWeapon());
|
|
return Math.max(1, purpleMaxLv / 5);
|
|
}
|
|
|
|
/** 按关卡配表解析 hp_multiple */
|
|
export function resolveHpMultiple(chapterHpMultiple: number): number {
|
|
return chapterHpMultiple > 0 ? chapterHpMultiple : 1;
|
|
}
|
|
|
|
/** 读取 Monster1 系数:monster_group 定列,波次序号 waveIndex 定行 */
|
|
export function getMonster2Factor(monsterGroup: number, difficultyRow: number): number {
|
|
const row = gg.data.table.getTableData<ITableMonster1>(TableNames.Monster1, difficultyRow);
|
|
if (!row) {
|
|
console.log('[MonsterHpCalculator] Monster2 row missing', difficultyRow);
|
|
return 1;
|
|
}
|
|
const key = `diffcult${monsterGroup}` as keyof ITableMonster1;
|
|
const value = row[key];
|
|
return typeof value === 'number' && value > 0 ? value : 1;
|
|
}
|
|
|
|
/** 旧逻辑:按战斗时间查 Monster2 行(time <= row.id 的首行) */
|
|
export function getMonster2FactorByBattleTime(monsterGroup: number, battleTime: number): number {
|
|
const list = gg.data.table.getTableList<ITableMonster1>(TableNames.Monster1) ?? [];
|
|
for (const row of list) {
|
|
if (battleTime <= row.id) {
|
|
const key = `diffcult${monsterGroup}` as keyof ITableMonster1;
|
|
const value = row[key];
|
|
return typeof value === 'number' && value > 0 ? value : 1;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 战力修正系数:
|
|
* - 推荐/玩家 >= 1:max(1, ratio ^ power_para)
|
|
* - 推荐/玩家 < 1:max(0.5, ratio ^ power_para2)
|
|
*/
|
|
export function calcPowerHpCoeff(
|
|
recommendPower: number,
|
|
userPower: number,
|
|
powerPara: number,
|
|
powerPara2: number = 0,
|
|
): number {
|
|
const user = Math.max(1, userPower);
|
|
const recommend = Math.max(0, recommendPower);
|
|
const ratio = recommend / user;
|
|
|
|
if (ratio >= 1) {
|
|
if (powerPara <= 0) return 1;
|
|
return Math.max(1, Math.pow(ratio, powerPara));
|
|
}
|
|
if (powerPara2 <= 0) return 1;
|
|
return Math.max(0.5, Math.pow(ratio, powerPara2));
|
|
}
|
|
|
|
/** 段位对应 weapon_para(Level 表暂无该字段时返回 1) */
|
|
export function getWeaponParaFromMaxChapter(rankId: number): number {
|
|
if (!(rankId > 0)) {
|
|
console.log('[MonsterHpCalculator] rankId 无效', rankId);
|
|
return 1;
|
|
}
|
|
const levelRow = gg.data.table.getTableData<ITableLevel>(TableNames.Level, rankId);
|
|
const v = (levelRow as { weapon_para?: number } | null)?.weapon_para;
|
|
return typeof v === 'number' && v > 0 ? v : 1;
|
|
}
|
|
|
|
export function calcPowerHpCoeffForLevel(
|
|
_levelId: number,
|
|
powerPara: number,
|
|
powerPara2: number = 0,
|
|
): number {
|
|
const power = gg.data.getCurPower();
|
|
return calcPowerHpCoeff(power, power, powerPara, powerPara2);
|
|
}
|
|
|
|
import { finitePositiveHp } from '../../../mx/tools/FiniteHp';
|
|
|
|
/**
|
|
* 策划 / 旧项目血量公式:
|
|
* floor(monster_hp × hp_multiple × port_hp × basehp × 战力修正 × weapon_para)
|
|
*/
|
|
export function calcChapterMonsterHp(
|
|
monsterId: number,
|
|
port: number,
|
|
difficultyRow: number,
|
|
config: ProjectLevelMonsterConfig,
|
|
): number {
|
|
const monster = gg.data.table.getTableData<ITableMonster>(TableNames.Monster, monsterId);
|
|
const monsterHp = finitePositiveHp(
|
|
(monster as ITableMonster & { HP?: number })?.HP ?? monster?.hp,
|
|
1,
|
|
);
|
|
const portHp = finitePositiveHp(config.portHpMult[port - 1], 1);
|
|
const baseHp = getMonster2Factor(config.monsterGroup, difficultyRow);
|
|
const powerCoeff = calcPowerHpCoeffForLevel(
|
|
config.levelId,
|
|
config.powerPara,
|
|
config.powerPara2,
|
|
);
|
|
const hpMultiple = finitePositiveHp(config.hpMultiple, 1);
|
|
const weaponPara = finitePositiveHp(config.weaponPara, 1);
|
|
const raw = monsterHp * hpMultiple * portHp * baseHp * powerCoeff * weaponPara;
|
|
if (!Number.isFinite(raw)) {
|
|
console.error(`[MonsterHpCalculator] invalid hp raw monsterId=${monsterId} port=${port} difficultyRow=${difficultyRow} monsterHp=${monsterHp} hpMultiple=${hpMultiple} portHp=${portHp} baseHp=${baseHp} powerCoeff=${powerCoeff} weaponPara=${weaponPara}`);
|
|
}
|
|
let finalHp = finitePositiveHp(Math.floor(raw), 1);
|
|
const scale = finitePositiveHp(config.specialReplicaHpScale, 1);
|
|
if (scale !== 1) {
|
|
finalHp = finitePositiveHp(Math.floor(finalHp * scale), finalHp);
|
|
}
|
|
logChapterMonsterHpCalc({
|
|
monsterId,
|
|
port,
|
|
difficultyRow,
|
|
config,
|
|
monsterHp,
|
|
portHp,
|
|
baseHp,
|
|
powerCoeff,
|
|
hpMultiple,
|
|
weaponPara,
|
|
raw,
|
|
finalHp,
|
|
});
|
|
return finalHp;
|
|
}
|
|
|
|
/** 旧 MonsterDataManager 兼容:返回 basehp × 战力修正(不含 monster_hp / hp_multiple / weapon_para) */
|
|
export function calcMonsterHpBaseByBattleTime(
|
|
monsterGroup: number,
|
|
battleTime: number,
|
|
recommendPower: number,
|
|
powerPara: number,
|
|
powerPara2: number = 0,
|
|
): number {
|
|
const m2 = getMonster2FactorByBattleTime(monsterGroup, battleTime);
|
|
const powerCoeff = calcPowerHpCoeff(recommendPower, gg.data.getCurPower(), powerPara, powerPara2);
|
|
return m2 * powerCoeff;
|
|
}
|
|
|
|
export function makeMonsterUnitDefId(monsterId: number, port: number, difficultyRow: number): UnitDefId {
|
|
return `monster_${monsterId}_p${port}_r${difficultyRow}`;
|
|
}
|
|
|
|
export function parseMonsterUnitDefId(unitDefId: UnitDefId): {
|
|
monsterId: number;
|
|
port: number;
|
|
difficultyRow: number;
|
|
} | null {
|
|
const m = /^monster_(\d+)_p(\d+)_r(\d+)$/.exec(unitDefId);
|
|
if (!m) return null;
|
|
return {
|
|
monsterId: parseInt(m[1], 10),
|
|
port: parseInt(m[2], 10),
|
|
difficultyRow: parseInt(m[3], 10),
|
|
};
|
|
}
|
|
|
|
export function parseLegacyMonsterId(unitDefId: UnitDefId): number {
|
|
const m = /^monster_(\d+)$/.exec(unitDefId);
|
|
return m ? parseInt(m[1], 10) : 0;
|
|
}
|
|
|