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.
AdLike2048/assets/script/battle/combat/TowerStatResolver.ts

152 lines
5.8 KiB

2 weeks ago
import { heroNormalSkillId, isElementHero } from '../config/BattleHeroTable';
3 weeks ago
import { BattleMapSpec } from '../map/BattleMapSpec';
import { TableNames } from '../../game/ConfigTableData';
import type { TowerCombatStats } from './TowerQueryTypes';
2 weeks ago
import { WeaponComposeConfigProvider } from '../bag/WeaponComposeConfigProvider';
3 weeks ago
/** setupCombat 用的内部结果(range 为像素) */
export interface TowerSetupStats extends TowerCombatStats {
/** 有效等级 */
effectiveLevel: number;
}
/** 战斗信息弹窗数值展示 */
export class TowerStatDisplay {
/**
* 攻击力/经验:最多 1 位小数,整数不带小数点。
* @param value 原始数值
*/
static oneDecimal(value: number): string {
const n = Math.round(Number(value) * 10) / 10;
if (!Number.isFinite(n)) {
console.log('[TowerStatDisplay] 非法数值', value);
return String(value);
}
return n.toFixed(1).replace(/\.0$/, '');
}
/**
* 攻速:保留 2 位小数,整数去掉 .00。
* @param value 原始攻速
*/
static attackSpeed(value: number): string {
const n = Number(value);
if (!Number.isFinite(n)) {
console.log('[TowerStatDisplay] 非法攻速', value);
return String(value);
}
return n.toFixed(2).replace(/\.00$/, '');
}
}
/**
* 塔索敌射程(像素):优先 Skill.range;为 0/未配时用 Hero.atk_range;-1=全屏。
* @param hero Hero 行
* @param skill 普攻 Skill;可空
*/
export function resolveTowerRangePx(hero: ITableHero, skill: ITableSkill | null): number {
if (skill) {
if (skill.range < 0) return 99 * BattleMapSpec.CELL_SIZE;
if (skill.range > 0) return skill.range;
}
if (hero.atk_range > 0) return hero.atk_range;
return 3 * BattleMapSpec.CELL_SIZE;
}
/**
* @deprecated 请用 resolveTowerRangePx
* @param baseAtk 普攻配表;null 默认 3 格像素
*/
export function resolveBaseAtkRangePx(baseAtk: ITableSkill | null): number {
if (!baseAtk) return 3 * BattleMapSpec.CELL_SIZE;
if (baseAtk.range < 0) return 99 * BattleMapSpec.CELL_SIZE;
if (baseAtk.range > 0) return baseAtk.range;
return 3 * BattleMapSpec.CELL_SIZE;
}
/**
* 武器被动 type 11 射程加成(像素)。主动技能的 atk_buff 走命中,不计入。
* @param weapon 当前武器/宝石行;可空
3 weeks ago
*/
function weaponLoadoutRangeAddPx(weapon: ITableWeapon | null): number {
if (!weapon) return 0;
const skillId = Number(weapon.act_skill) || 0;
if (!(skillId > 0)) return 0;
const skill = gg.data.table.getTableData<ITableSkill>(TableNames.Skill, skillId);
if (!skill || Number(skill.type) !== 2) return 0;
return rangeAddFromAtkBuff(skill.atk_buff1) + rangeAddFromAtkBuff(skill.atk_buff2);
}
/**
* atk_buff type 11 射程像素(绝对值 ≤16 按格)。
* @param raw Skill.atk_buff 原文
*/
function rangeAddFromAtkBuff(raw: string | undefined): number {
const s = String(raw ?? '').trim();
if (!s) return 0;
const parts = s.split(',');
if (Number(parts[0]) !== 11) return 0;
const addVal = Math.floor(Number(parts[1]) || 0);
if (addVal === 0) return 0;
return Math.abs(addVal) <= 16 ? addVal * BattleMapSpec.CELL_SIZE : addVal;
}
/**
* 局内战斗基础数值:攻击力 = Hero.atk + 武器/宝石 atk(无等级系数)。
* 宝石 attritubes 由 GemBattleEffect 写入 Attribute,不在此乘。
* @param w1 Hero 行(须已是目标等级那一行)
* @param composeLevel 显示/合成等级
* @param battleWeaponId 战斗武器/技能宝石 id;缺省不回落名将装备
3 weeks ago
*/
export function deriveTowerSetupStats(w1: ITableHero, composeLevel: number, battleWeaponId?: number): TowerSetupStats {
2 weeks ago
const compose = Math.max(1, Math.floor(composeLevel) || 1);
4 days ago
const weaponId = resolveSetupWeaponId(w1, battleWeaponId);
const weaponRow = weaponId > 0
? gg.data.table.getTableData<ITableWeapon>(TableNames.Weapon, weaponId)
: null;
const weaponAtk = weaponRow?.atk ?? 0;
4 days ago
const rowLevel = Math.floor(Number(w1.level)) || 0;
2 weeks ago
let effectiveLevel: number;
if (isElementHero(w1)) {
const cap = WeaponComposeConfigProvider.instance.getElementMaxLevel(Math.floor(Number(w1.herotype)));
4 days ago
effectiveLevel = rowLevel > 0 ? Math.min(rowLevel, cap) : Math.min(compose, cap);
if (rowLevel > 0 && rowLevel !== compose) {
console.warn('[TowerStatResolver] 五行 Hero.level 与传入等级不一致', w1.id, rowLevel, compose);
}
2 weeks ago
} else {
console.warn('[TowerStatResolver] 非五行 Hero,等级按合成截断为 1 上限', w1.id);
effectiveLevel = 1;
3 weeks ago
}
const attack = w1.atk + weaponAtk;
const skillId = heroNormalSkillId(w1, 1);
3 weeks ago
const skillRow = skillId > 0 ? gg.data.table.getTableData<ITableSkill>(TableNames.Skill, skillId) : null;
const attackSpeed = skillRow?.cd > 0 ? 1 / skillRow.cd : 1;
const critRate = Math.min(1, (w1.critical_chance ?? 0) / 10000);
const critDamage = (w1.critical_dam ?? 15000) / 10000;
const range = resolveTowerRangePx(w1, skillRow) + weaponLoadoutRangeAddPx(weaponRow);
return {
effectiveLevel,
attack,
attackSpeed,
attackInterval: attackSpeed > 0 ? 1 / attackSpeed : 0,
range,
critRate,
critDamage,
maxAttackSpeed: 0,
};
}
/**
* 解析 setup 用武器/宝石 id:显式 battleWeaponId 优先;五行禁止回落名将装备。
3 weeks ago
* @param w1 Hero 行
* @param battleWeaponId 调用方已解析的战斗武器(含宝石)
3 weeks ago
*/
function resolveSetupWeaponId(w1: ITableHero, battleWeaponId?: number): number {
if (typeof battleWeaponId === 'number' && battleWeaponId > 0) return battleWeaponId;
if (isElementHero(w1)) return 0;
return 0;
3 weeks ago
}