import { heroNormalSkillId, isElementHero } from '../config/BattleHeroTable'; import { BattleMapSpec } from '../map/BattleMapSpec'; import { TableNames } from '../../game/ConfigTableData'; import type { TowerCombatStats } from './TowerQueryTypes'; import { WeaponComposeConfigProvider } from '../bag/WeaponComposeConfigProvider'; /** 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 当前武器/宝石行;可空 */ 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(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;缺省不回落名将装备 */ export function deriveTowerSetupStats(w1: ITableHero, composeLevel: number, battleWeaponId?: number): TowerSetupStats { const compose = Math.max(1, Math.floor(composeLevel) || 1); const weaponId = resolveSetupWeaponId(w1, battleWeaponId); const weaponRow = weaponId > 0 ? gg.data.table.getTableData(TableNames.Weapon, weaponId) : null; const weaponAtk = weaponRow?.atk ?? 0; const rowLevel = Math.floor(Number(w1.level)) || 0; let effectiveLevel: number; if (isElementHero(w1)) { const cap = WeaponComposeConfigProvider.instance.getElementMaxLevel(Math.floor(Number(w1.herotype))); 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); } } else { console.warn('[TowerStatResolver] 非五行 Hero,等级按合成截断为 1 上限', w1.id); effectiveLevel = 1; } const attack = w1.atk + weaponAtk; const skillId = heroNormalSkillId(w1, 1); const skillRow = skillId > 0 ? gg.data.table.getTableData(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 优先;五行禁止回落名将装备。 * @param w1 Hero 行 * @param battleWeaponId 调用方已解析的战斗武器(含宝石) */ function resolveSetupWeaponId(w1: ITableHero, battleWeaponId?: number): number { if (typeof battleWeaponId === 'number' && battleWeaponId > 0) return battleWeaponId; if (isElementHero(w1)) return 0; return 0; }