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.
151 lines
5.8 KiB
151 lines
5.8 KiB
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<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;缺省不回落名将装备
|
|
*/
|
|
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<ITableWeapon>(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<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 优先;五行禁止回落名将装备。
|
|
* @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;
|
|
}
|
|
|