refactor(heroConfig): 重构英雄强度评分计算逻辑
1. 重构英雄强度评分权重配置,新增时间窗相关参数并拆分DPS权重 2. 新增多个辅助计算函数,统一普攻/受击/波次/触发技次数计算逻辑 3. 重构buff与技能价值计算,支持自定义评估时间窗 4. 新增详细评分返回接口,拆分强度构成与各技能价值 5. 兼容旧版默认评分接口,保留向下兼容性
This commit is contained in:
@@ -730,10 +730,17 @@ export const HeroList: number[] = [
|
|||||||
|
|
||||||
// ==================== 英雄强度评分公式 ====================
|
// ==================== 英雄强度评分公式 ====================
|
||||||
|
|
||||||
/** 英雄强度公式调参权重(可在编辑器或运行时热调) */
|
/** 英雄强度公式调参权重(可在编辑器或运行时热调)
|
||||||
|
* 说明:
|
||||||
|
* - W_HP: 每 1 点生命值折算 1 分(生命值不随时间变化)
|
||||||
|
* - W_DPS_PER_SEC: 每 1 点 DPS 持续 1 秒折算 0.5 分
|
||||||
|
* (默认 60 秒评估窗下,1 DPS ≈ 30 分,与旧版 W_DPS=30 等效)
|
||||||
|
* - WAVE_DURATION / HITS_PER_WAVE: 波次参数,用于估算 atked/fstart/fend/dead 触发次数
|
||||||
|
* - DEFAULT_EVAL_SECONDS: calcHeroPower 默认使用的评估时间窗
|
||||||
|
*/
|
||||||
export const HERO_POWER_WEIGHTS = {
|
export const HERO_POWER_WEIGHTS = {
|
||||||
W_HP: 1.0,
|
W_HP: 1.0,
|
||||||
W_DPS: 30.0,
|
W_DPS_PER_SEC: 0.5,
|
||||||
W_atking: 1.0,
|
W_atking: 1.0,
|
||||||
W_atked: 0.8,
|
W_atked: 0.8,
|
||||||
W_fstart: 1.0,
|
W_fstart: 1.0,
|
||||||
@@ -745,6 +752,9 @@ export const HERO_POWER_WEIGHTS = {
|
|||||||
W_shield: 1.0,
|
W_shield: 1.0,
|
||||||
W_control: 0.5,
|
W_control: 0.5,
|
||||||
expectedEnemyDmg: 20,
|
expectedEnemyDmg: 20,
|
||||||
|
WAVE_DURATION: 30,
|
||||||
|
HITS_PER_WAVE: 10,
|
||||||
|
DEFAULT_EVAL_SECONDS: 60,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 计算目标等级下 evolve 提供的累计属性加成 */
|
/** 计算目标等级下 evolve 提供的累计属性加成 */
|
||||||
@@ -788,8 +798,45 @@ function calcScopeMul(group: TGroup): number {
|
|||||||
return (group === TGroup.Ally || group === TGroup.Team) ? FightSet.HERO_MAX_NUM : 1;
|
return (group === TGroup.Ally || group === TGroup.Team) ? FightSet.HERO_MAX_NUM : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将计时 buff 折算为永久等效价值 */
|
/** 计算目标时间窗内的普攻次数 */
|
||||||
function calcBuffPermanentEquivalent(buff: BuffConfig, apEff: number, cd: number, buffValueOverride?: number): number {
|
function getAttackCount(hero: heroInfo, seconds: number): number {
|
||||||
|
return seconds / getAttackCd(hero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算目标时间窗内的受击次数 */
|
||||||
|
function getHitCount(seconds: number): number {
|
||||||
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
|
return (seconds / weights.WAVE_DURATION) * weights.HITS_PER_WAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算目标时间窗内的波次数 */
|
||||||
|
function getWaveCount(seconds: number): number {
|
||||||
|
return seconds / HERO_POWER_WEIGHTS.WAVE_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算触发技在目标时间窗内的触发次数 */
|
||||||
|
function getTriggerCount(
|
||||||
|
type: SkillTriggerType,
|
||||||
|
t_num: number,
|
||||||
|
hero: heroInfo,
|
||||||
|
seconds: number
|
||||||
|
): number {
|
||||||
|
switch (type) {
|
||||||
|
case SkillTriggerType.Atking:
|
||||||
|
return getAttackCount(hero, seconds) / t_num;
|
||||||
|
case SkillTriggerType.Atked:
|
||||||
|
return getHitCount(seconds) / t_num;
|
||||||
|
case SkillTriggerType.FStart:
|
||||||
|
case SkillTriggerType.FEnd:
|
||||||
|
return getWaveCount(seconds);
|
||||||
|
case SkillTriggerType.Dead:
|
||||||
|
return getWaveCount(seconds) * (1 + (hero.revive?.r_num || 0));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将计时 buff 折算为“在整个时间窗内持续生效”的等效价值 */
|
||||||
|
function calcBuffPermanentEquivalent(buff: BuffConfig, apEff: number, cd: number, seconds: number, buffValueOverride?: number): number {
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
let value = 0;
|
let value = 0;
|
||||||
if (buff.modifiers) {
|
if (buff.modifiers) {
|
||||||
@@ -799,9 +846,9 @@ function calcBuffPermanentEquivalent(buff: BuffConfig, apEff: number, cd: number
|
|||||||
switch (mod.attr) {
|
switch (mod.attr) {
|
||||||
case Attrs.ap:
|
case Attrs.ap:
|
||||||
if (mod.op === ModOp.Flat) {
|
if (mod.op === ModOp.Flat) {
|
||||||
value += (modValue / cd) * weights.W_DPS;
|
value += (modValue / cd) * weights.W_DPS_PER_SEC * seconds;
|
||||||
} else if (mod.op === ModOp.PercentAdd) {
|
} else if (mod.op === ModOp.PercentAdd) {
|
||||||
value += (modValue / 100) * dps * weights.W_DPS;
|
value += (modValue / 100) * dps * weights.W_DPS_PER_SEC * seconds;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Attrs.hp:
|
case Attrs.hp:
|
||||||
@@ -809,13 +856,13 @@ function calcBuffPermanentEquivalent(buff: BuffConfig, apEff: number, cd: number
|
|||||||
value += modValue * weights.W_HP;
|
value += modValue * weights.W_HP;
|
||||||
break;
|
break;
|
||||||
case Attrs.critical:
|
case Attrs.critical:
|
||||||
value += (modValue / 100) * dps * 0.5 * weights.W_DPS;
|
value += (modValue / 100) * dps * 0.5 * weights.W_DPS_PER_SEC * seconds;
|
||||||
break;
|
break;
|
||||||
case Attrs.critical_damage:
|
case Attrs.critical_damage:
|
||||||
value += (modValue / 100) * dps * 0.05 * weights.W_DPS;
|
value += (modValue / 100) * dps * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||||
break;
|
break;
|
||||||
case Attrs.speed:
|
case Attrs.speed:
|
||||||
value += (modValue / 100) * dps * weights.W_DPS;
|
value += (modValue / 100) * dps * weights.W_DPS_PER_SEC * seconds;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,45 +875,45 @@ function calcBuffPermanentEquivalent(buff: BuffConfig, apEff: number, cd: number
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算 Support 类技能的效果价值 */
|
/** 计算 Support 类技能单次触发的效果价值 */
|
||||||
function calcSupportEffectValue(config: SkillConfig, apEff: number, cd: number): number {
|
function calcSupportEffectValue(config: SkillConfig, apEff: number, cd: number, seconds: number): number {
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
const value = config.ap || 0;
|
const value = config.ap || 0;
|
||||||
|
|
||||||
// 计时 buff:先算永久等效,再按战斗时长覆盖率折算
|
// 计时 buff:先算整个时间窗的永久等效,再按覆盖率折算
|
||||||
if (config.timed_buff_id) {
|
if (config.timed_buff_id) {
|
||||||
const buff = BuffList[config.timed_buff_id];
|
const buff = BuffList[config.timed_buff_id];
|
||||||
if (buff) {
|
if (buff) {
|
||||||
const permanentEquivalent = calcBuffPermanentEquivalent(buff, apEff, cd, config.buff_value);
|
const permanentEquivalent = calcBuffPermanentEquivalent(buff, apEff, cd, seconds, config.buff_value);
|
||||||
const duration = config.buff_duration ?? buff.duration;
|
const duration = config.buff_duration ?? buff.duration;
|
||||||
const uptime = Math.min(duration / FightSet.FiIGHT_TIME, 1);
|
const uptime = seconds > 0 ? Math.min(duration / seconds, 1) : 0;
|
||||||
return permanentEquivalent * uptime;
|
return permanentEquivalent * uptime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (config.buff_type) {
|
switch (config.buff_type) {
|
||||||
case Attrs.ap:
|
case Attrs.ap:
|
||||||
return (value / cd) * weights.W_DPS;
|
return (value / cd) * weights.W_DPS_PER_SEC * seconds;
|
||||||
case Attrs.hp_max:
|
case Attrs.hp_max:
|
||||||
case Attrs.hp:
|
case Attrs.hp:
|
||||||
return value * weights.W_HP;
|
return value * weights.W_HP;
|
||||||
case Attrs.critical:
|
case Attrs.critical:
|
||||||
return (value / 100) * (apEff / cd) * 0.5 * weights.W_DPS;
|
return (value / 100) * (apEff / cd) * 0.5 * weights.W_DPS_PER_SEC * seconds;
|
||||||
case Attrs.critical_damage:
|
case Attrs.critical_damage:
|
||||||
return (value / 100) * (apEff / cd) * 0.05 * weights.W_DPS;
|
return (value / 100) * (apEff / cd) * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||||
case Attrs.stun_chance:
|
case Attrs.stun_chance:
|
||||||
case Attrs.freeze_chance:
|
case Attrs.freeze_chance:
|
||||||
case Attrs.knockback_chance:
|
case Attrs.knockback_chance:
|
||||||
return value * weights.W_control;
|
return value * weights.W_control;
|
||||||
case Attrs.speed:
|
case Attrs.speed:
|
||||||
return (value / 100) * (apEff / cd) * weights.W_DPS;
|
return (value / 100) * (apEff / cd) * weights.W_DPS_PER_SEC * seconds;
|
||||||
default:
|
default:
|
||||||
return value * weights.W_control;
|
return value * weights.W_control;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算单条技能配置的效果价值 */
|
/** 计算单条技能配置单次触发的效果价值 */
|
||||||
function calcSkillEffectValue(skill: SkillConfig, overrides: SkillOverrides | undefined, hero: heroInfo, targetLv: number): number {
|
function calcSkillEffectValue(skill: SkillConfig, overrides: SkillOverrides | undefined, hero: heroInfo, targetLv: number, seconds: number): number {
|
||||||
const { ap: apEff } = getEffectiveStats(hero, targetLv);
|
const { ap: apEff } = getEffectiveStats(hero, targetLv);
|
||||||
const cd = getAttackCd(hero);
|
const cd = getAttackCd(hero);
|
||||||
const config = overrides ? mergeSkillParams(skill, overrides) : skill;
|
const config = overrides ? mergeSkillParams(skill, overrides) : skill;
|
||||||
@@ -874,49 +921,86 @@ function calcSkillEffectValue(skill: SkillConfig, overrides: SkillOverrides | un
|
|||||||
|
|
||||||
switch (config.kind) {
|
switch (config.kind) {
|
||||||
case SkillKind.Damage:
|
case SkillKind.Damage:
|
||||||
return ((config.ap || 0) / 100) * apEff;
|
return ((config.ap || 0) / 100) * apEff * weights.W_DPS_PER_SEC;
|
||||||
case SkillKind.Heal:
|
case SkillKind.Heal:
|
||||||
return ((config.ap || 0) / 100) * apEff * weights.W_heal;
|
return ((config.ap || 0) / 100) * apEff * weights.W_heal;
|
||||||
case SkillKind.Shield:
|
case SkillKind.Shield:
|
||||||
return (config.ap || 0) * weights.expectedEnemyDmg * weights.W_shield;
|
return (config.ap || 0) * weights.expectedEnemyDmg * weights.W_shield;
|
||||||
case SkillKind.Support:
|
case SkillKind.Support:
|
||||||
return calcSupportEffectValue(config, apEff, cd);
|
return calcSupportEffectValue(config, apEff, cd, seconds);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算普通触发类技能(atking/atked/fstart/fend/dead)的价值 */
|
/** 触发技评估项 */
|
||||||
function calcTriggerPower(
|
export interface TriggerSkillEval {
|
||||||
|
type: SkillTriggerType;
|
||||||
|
s_uuid: number;
|
||||||
|
name: string;
|
||||||
|
t_num: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 驻场光环评估项 */
|
||||||
|
export interface FieldSkillEval {
|
||||||
|
uuid: number;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 英雄强度分数构成 */
|
||||||
|
export interface HeroPowerBreakdown {
|
||||||
|
hp: number;
|
||||||
|
dps: number;
|
||||||
|
atking: number;
|
||||||
|
atked: number;
|
||||||
|
fstart: number;
|
||||||
|
fend: number;
|
||||||
|
dead: number;
|
||||||
|
field: number;
|
||||||
|
revive: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 英雄强度详细评估结果 */
|
||||||
|
export interface HeroPowerDetailed {
|
||||||
|
total: number;
|
||||||
|
breakdown: HeroPowerBreakdown;
|
||||||
|
triggerSkills: TriggerSkillEval[];
|
||||||
|
fieldSkills: FieldSkillEval[];
|
||||||
|
normalAttack: { s_uuid: number, name: string, cd: number, dps: number, value: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算普通触发类技能(atking/atked/fstart/fend/dead)的详细价值 */
|
||||||
|
function calcTriggerPowerDetailed(
|
||||||
triggerConfigs: { s_uuid: number, t_num?: number, overrides?: SkillOverrides }[],
|
triggerConfigs: { s_uuid: number, t_num?: number, overrides?: SkillOverrides }[],
|
||||||
hero: heroInfo,
|
hero: heroInfo,
|
||||||
targetLv: number,
|
targetLv: number,
|
||||||
type: SkillTriggerType
|
type: SkillTriggerType,
|
||||||
): number {
|
seconds: number
|
||||||
if (!triggerConfigs || triggerConfigs.length === 0) return 0;
|
): { total: number, items: TriggerSkillEval[] } {
|
||||||
|
if (!triggerConfigs || triggerConfigs.length === 0) return { total: 0, items: [] };
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
const items: TriggerSkillEval[] = [];
|
||||||
|
|
||||||
for (const trig of triggerConfigs) {
|
for (const trig of triggerConfigs) {
|
||||||
const skill = SkillSet[trig.s_uuid];
|
const skill = SkillSet[trig.s_uuid];
|
||||||
if (!skill) continue;
|
if (!skill) continue;
|
||||||
|
|
||||||
const effectValue = calcSkillEffectValue(skill, trig.overrides, hero, targetLv);
|
const effectValue = calcSkillEffectValue(skill, trig.overrides, hero, targetLv, seconds);
|
||||||
const overrides = trig.overrides || {};
|
const overrides = trig.overrides || {};
|
||||||
const group = overrides.TGroup ?? skill.TGroup;
|
const group = overrides.TGroup ?? skill.TGroup;
|
||||||
const scopeMul = calcScopeMul(group);
|
const scopeMul = calcScopeMul(group);
|
||||||
const tNum = trig.t_num || 1;
|
const tNum = trig.t_num || 1;
|
||||||
|
|
||||||
let weight = 1;
|
let weight = 1;
|
||||||
let frequency = 1;
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SkillTriggerType.Atking:
|
case SkillTriggerType.Atking:
|
||||||
weight = weights.W_atking;
|
weight = weights.W_atking;
|
||||||
frequency = 1 / tNum;
|
|
||||||
break;
|
break;
|
||||||
case SkillTriggerType.Atked:
|
case SkillTriggerType.Atked:
|
||||||
weight = weights.W_atked;
|
weight = weights.W_atked;
|
||||||
frequency = 1 / tNum;
|
|
||||||
break;
|
break;
|
||||||
case SkillTriggerType.FStart:
|
case SkillTriggerType.FStart:
|
||||||
weight = weights.W_fstart;
|
weight = weights.W_fstart;
|
||||||
@@ -929,27 +1013,30 @@ function calcTriggerPower(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
total += effectValue * frequency * scopeMul * weight;
|
const triggerCount = getTriggerCount(type, tNum, hero, seconds);
|
||||||
|
const value = effectValue * triggerCount * scopeMul * weight;
|
||||||
|
total += value;
|
||||||
|
items.push({ type, s_uuid: trig.s_uuid, name: skill.name, t_num: tNum, value });
|
||||||
}
|
}
|
||||||
return total;
|
return { total, items };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算驻场光环技能的单位价值 */
|
/** 计算驻场光环技能在时间窗内的总价值 */
|
||||||
function calcFieldSkillValue(field: FieldSkillConfig, hpEff: number, apEff: number, cd: number, scopeMul: number): number {
|
function calcFieldSkillValue(field: FieldSkillConfig, hpEff: number, apEff: number, cd: number, scopeMul: number, seconds: number): number {
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
const dps = apEff / cd;
|
const dps = apEff / cd;
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case FieldSkillType.HeroAtk:
|
case FieldSkillType.HeroAtk:
|
||||||
return field.value * dps * scopeMul * weights.W_DPS;
|
return field.value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||||
case FieldSkillType.HeroHp:
|
case FieldSkillType.HeroHp:
|
||||||
return field.value * hpEff * scopeMul * weights.W_HP;
|
return field.value * hpEff * scopeMul * weights.W_HP;
|
||||||
case FieldSkillType.HeroSpeed:
|
case FieldSkillType.HeroSpeed:
|
||||||
return field.value * dps * scopeMul * weights.W_DPS;
|
return field.value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||||
case FieldSkillType.HeroCrit:
|
case FieldSkillType.HeroCrit:
|
||||||
return field.value * dps * scopeMul * 0.5 * weights.W_DPS;
|
return field.value * dps * scopeMul * 0.5 * weights.W_DPS_PER_SEC * seconds;
|
||||||
case FieldSkillType.HeroCritDamage:
|
case FieldSkillType.HeroCritDamage:
|
||||||
return field.value * dps * scopeMul * 0.05 * weights.W_DPS;
|
return field.value * dps * scopeMul * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||||
case FieldSkillType.HeroStun:
|
case FieldSkillType.HeroStun:
|
||||||
case FieldSkillType.HeroWindFury:
|
case FieldSkillType.HeroWindFury:
|
||||||
case FieldSkillType.HeroPuncture:
|
case FieldSkillType.HeroPuncture:
|
||||||
@@ -959,48 +1046,112 @@ function calcFieldSkillValue(field: FieldSkillConfig, hpEff: number, apEff: numb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算驻场光环总价值 */
|
/** 计算驻场光环的详细价值 */
|
||||||
function calcFieldPower(fieldUuids: number[] | undefined, hero: heroInfo, targetLv: number): number {
|
function calcFieldPowerDetailed(fieldUuids: number[] | undefined, hero: heroInfo, targetLv: number, seconds: number): { total: number, items: FieldSkillEval[] } {
|
||||||
if (!fieldUuids || fieldUuids.length === 0) return 0;
|
if (!fieldUuids || fieldUuids.length === 0) return { total: 0, items: [] };
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
const { hp: hpEff, ap: apEff } = getEffectiveStats(hero, targetLv);
|
const { hp: hpEff, ap: apEff } = getEffectiveStats(hero, targetLv);
|
||||||
const cd = getAttackCd(hero);
|
const cd = getAttackCd(hero);
|
||||||
const scopeMul = FightSet.HERO_MAX_NUM;
|
const scopeMul = FightSet.HERO_MAX_NUM;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
const items: FieldSkillEval[] = [];
|
||||||
|
|
||||||
for (const uuid of fieldUuids) {
|
for (const uuid of fieldUuids) {
|
||||||
const field = FieldSkillSet[uuid];
|
const field = FieldSkillSet[uuid];
|
||||||
if (!field) continue;
|
if (!field) continue;
|
||||||
total += calcFieldSkillValue(field, hpEff, apEff, cd, scopeMul);
|
const value = calcFieldSkillValue(field, hpEff, apEff, cd, scopeMul, seconds) * weights.W_field_alive;
|
||||||
|
total += value;
|
||||||
|
items.push({ uuid, name: field.name, value });
|
||||||
}
|
}
|
||||||
return total * weights.W_field_alive;
|
return { total, items };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取普通攻击信息 */
|
||||||
|
function getNormalAttackInfo(hero: heroInfo, targetLv: number = 1, seconds: number = HERO_POWER_WEIGHTS.DEFAULT_EVAL_SECONDS): { s_uuid: number, name: string, cd: number, dps: number, value: number } {
|
||||||
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
|
const { ap: apEff } = getEffectiveStats(hero, targetLv);
|
||||||
|
const cd = getAttackCd(hero);
|
||||||
|
const skillIds = Object.keys(hero.skills);
|
||||||
|
const firstKey = skillIds.length > 0 ? Number(skillIds[0]) : 0;
|
||||||
|
const skill = firstKey ? SkillSet[firstKey] : undefined;
|
||||||
|
const dps = apEff / cd;
|
||||||
|
return {
|
||||||
|
s_uuid: firstKey,
|
||||||
|
name: skill?.name ?? '普攻',
|
||||||
|
cd,
|
||||||
|
dps,
|
||||||
|
value: dps * weights.W_DPS_PER_SEC * seconds,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算英雄强度评分(power score)。
|
* 计算英雄强度详细评估(含分数构成与每个技能价值)。
|
||||||
|
* @param hero 英雄/怪物配置
|
||||||
|
* @param targetLv 目标等级,默认 1
|
||||||
|
* @param seconds 评估时间窗(秒),默认 HERO_POWER_WEIGHTS.DEFAULT_EVAL_SECONDS
|
||||||
|
* @returns 详细强度评估结果
|
||||||
|
*/
|
||||||
|
export function calcHeroPowerDetailed(hero: heroInfo, targetLv: number = 1, seconds: number = HERO_POWER_WEIGHTS.DEFAULT_EVAL_SECONDS): HeroPowerDetailed {
|
||||||
|
const weights = HERO_POWER_WEIGHTS;
|
||||||
|
const { hp: hpEff, ap: apEff } = getEffectiveStats(hero, targetLv);
|
||||||
|
const cd = getAttackCd(hero);
|
||||||
|
|
||||||
|
const hpValue = hpEff * weights.W_HP;
|
||||||
|
const dpsValue = (apEff / cd) * weights.W_DPS_PER_SEC * seconds;
|
||||||
|
const normalAttack = getNormalAttackInfo(hero, targetLv, seconds);
|
||||||
|
|
||||||
|
const atking = hero.atking ? calcTriggerPowerDetailed(hero.atking, hero, targetLv, SkillTriggerType.Atking, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||||
|
const atked = hero.atked ? calcTriggerPowerDetailed(hero.atked, hero, targetLv, SkillTriggerType.Atked, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||||
|
const fstart = hero.fstart ? calcTriggerPowerDetailed(hero.fstart, hero, targetLv, SkillTriggerType.FStart, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||||
|
const fend = hero.fend ? calcTriggerPowerDetailed(hero.fend, hero, targetLv, SkillTriggerType.FEnd, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||||
|
const dead = hero.dead ? calcTriggerPowerDetailed(hero.dead, hero, targetLv, SkillTriggerType.Dead, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||||
|
const field = hero.field ? calcFieldPowerDetailed(hero.field, hero, targetLv, seconds) : { total: 0, items: [] as FieldSkillEval[] };
|
||||||
|
|
||||||
|
let reviveValue = 0;
|
||||||
|
if (hero.revive) {
|
||||||
|
reviveValue = hpEff * hero.revive.upr * hero.revive.r_num * weights.W_revive * getWaveCount(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = hpValue + dpsValue + atking.total + atked.total + fstart.total + fend.total + dead.total + field.total + reviveValue;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
breakdown: {
|
||||||
|
hp: hpValue,
|
||||||
|
dps: dpsValue,
|
||||||
|
atking: atking.total,
|
||||||
|
atked: atked.total,
|
||||||
|
fstart: fstart.total,
|
||||||
|
fend: fend.total,
|
||||||
|
dead: dead.total,
|
||||||
|
field: field.total,
|
||||||
|
revive: reviveValue,
|
||||||
|
},
|
||||||
|
triggerSkills: [...atking.items, ...atked.items, ...fstart.items, ...fend.items, ...dead.items],
|
||||||
|
fieldSkills: field.items,
|
||||||
|
normalAttack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算指定时间窗内的英雄强度评分。
|
||||||
|
* @param hero 英雄/怪物配置
|
||||||
|
* @param targetLv 目标等级,默认 1
|
||||||
|
* @param seconds 评估时间窗(秒)
|
||||||
|
* @returns 无量纲强度分,数值越大越强
|
||||||
|
*/
|
||||||
|
export function calcHeroPowerOverTime(hero: heroInfo, targetLv: number = 1, seconds: number = HERO_POWER_WEIGHTS.DEFAULT_EVAL_SECONDS): number {
|
||||||
|
return calcHeroPowerDetailed(hero, targetLv, seconds).total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算英雄强度评分(默认时间窗)。
|
||||||
* @param hero 英雄/怪物配置
|
* @param hero 英雄/怪物配置
|
||||||
* @param targetLv 目标等级,默认 1
|
* @param targetLv 目标等级,默认 1
|
||||||
* @returns 无量纲强度分,数值越大越强
|
* @returns 无量纲强度分,数值越大越强
|
||||||
*/
|
*/
|
||||||
export function calcHeroPower(hero: heroInfo, targetLv: number = 1): number {
|
export function calcHeroPower(hero: heroInfo, targetLv: number = 1): number {
|
||||||
const weights = HERO_POWER_WEIGHTS;
|
return calcHeroPowerOverTime(hero, targetLv);
|
||||||
const { hp: hpEff, ap: apEff } = getEffectiveStats(hero, targetLv);
|
|
||||||
const cd = getAttackCd(hero);
|
|
||||||
|
|
||||||
const basePower = hpEff * weights.W_HP + (apEff / cd) * weights.W_DPS;
|
|
||||||
let triggerPower = 0;
|
|
||||||
|
|
||||||
if (hero.atking) triggerPower += calcTriggerPower(hero.atking, hero, targetLv, SkillTriggerType.Atking);
|
|
||||||
if (hero.atked) triggerPower += calcTriggerPower(hero.atked, hero, targetLv, SkillTriggerType.Atked);
|
|
||||||
if (hero.fstart) triggerPower += calcTriggerPower(hero.fstart, hero, targetLv, SkillTriggerType.FStart);
|
|
||||||
if (hero.fend) triggerPower += calcTriggerPower(hero.fend, hero, targetLv, SkillTriggerType.FEnd);
|
|
||||||
if (hero.dead) triggerPower += calcTriggerPower(hero.dead, hero, targetLv, SkillTriggerType.Dead);
|
|
||||||
if (hero.field) triggerPower += calcFieldPower(hero.field, hero, targetLv);
|
|
||||||
if (hero.revive) {
|
|
||||||
triggerPower += hpEff * hero.revive.upr * hero.revive.r_num * weights.W_revive;
|
|
||||||
}
|
|
||||||
|
|
||||||
return basePower + triggerPower;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user