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.
593 lines
24 KiB
593 lines
24 KiB
import type { BattleSession } from 'db://assets/mx/module/battle';
|
|
import { AttrId, AttributeComp, CombatComp, TeamComp } from 'db://assets/mx/module/battle/ecs/Components';
|
|
import type { EntityId } from 'db://assets/mx/module/ecs';
|
|
import { GEvent } from 'db://assets/mx/module/event/GEvent';
|
|
import { TableNames } from '../../game/ConfigTableData';
|
|
import { BattleBuffCarryStore } from '../../manager/battleBuff/BattleBuffCarryStore';
|
|
import { BattleBuffFunctionType } from '../../manager/battleBuff/BattleBuffTypes';
|
|
import {
|
|
BattleBuffTargetParse,
|
|
parseBattleBuffSkillListMul,
|
|
parseFunctionParNums,
|
|
} from '../../manager/battleBuff/BattleBuffParamParse';
|
|
import { AI_PLAYER_ID } from '../ai/runtime/AiCampArea';
|
|
import { isElementHero } from '../config/BattleHeroTable';
|
|
import { WeaponComposeConfigProvider } from '../bag/WeaponComposeConfigProvider';
|
|
import { BattleMapSpec } from '../map/BattleMapSpec';
|
|
import { getTowerLoadoutService } from './TowerLoadoutService';
|
|
import type { TowerCombatStats } from './TowerQueryTypes';
|
|
import { getPassiveSkillHost } from './PassiveSkillRuntime';
|
|
|
|
/** modifier source 前缀 */
|
|
const DIGIT_STAT_SOURCE = 'battle_buff_digit_';
|
|
|
|
/** 单塔战斗侧额外效果(105+) */
|
|
export interface DigitCombatFlags {
|
|
/** 107/113:技能伤害倍数(113 为 Hero.skill_id) */
|
|
skillMuls: { skillIds: number[]; mul: number }[];
|
|
/** 110:技能必暴(convert=暴击率转爆伤) */
|
|
forceCritSkills: { skillIds: number[]; convert: boolean }[];
|
|
/** 111:概率多倍伤 */
|
|
chanceMul: { wan: number; mul: number } | null;
|
|
/** 112:条件必暴 */
|
|
condCrit: 'poison' | 'slow' | 'always_convert' | null;
|
|
/** 108:穿透加值 */
|
|
pierceAdd: number;
|
|
/** 109:额外箭 */
|
|
extraArrow: number;
|
|
}
|
|
|
|
/** 绑定所需最小战斗句柄 */
|
|
interface DigitStatBattleRef {
|
|
/** 本机 playerId */
|
|
localPlayerId: number;
|
|
/** 取玩家 */
|
|
getPlayer(playerId: number): { deployedUnits: readonly EntityId[] } | null | undefined;
|
|
/** 会话 */
|
|
session: BattleSession;
|
|
}
|
|
|
|
/** 场上单位匹配快照 */
|
|
interface DigitUnitSnap {
|
|
/** 实体 */
|
|
entityId: EntityId;
|
|
/** 五行 */
|
|
heroType: number;
|
|
/** 匹配等级 */
|
|
level: number;
|
|
/** 同五行排名(1=最高) */
|
|
rank: number;
|
|
}
|
|
|
|
/**
|
|
* 局内 functiontype 101~113:按 target/targetpar 给数字塔属性/技能/战斗标记。
|
|
*/
|
|
export class BattleBuffDigitStatHost {
|
|
private _battle: DigitStatBattleRef | null = null;
|
|
private readonly _flags: { entityId: EntityId; flags: DigitCombatFlags }[] = [];
|
|
|
|
/** 绑定本局并刷新已部署单位 */
|
|
bind(battle: DigitStatBattleRef): void {
|
|
this._battle = battle;
|
|
this.reapplyPlayer(battle.localPlayerId);
|
|
this.reapplyPlayer(AI_PLAYER_ID);
|
|
}
|
|
|
|
/** 解除绑定 */
|
|
unbind(): void {
|
|
this._battle = null;
|
|
this._flags.length = 0;
|
|
}
|
|
|
|
/**
|
|
* 取塔战斗标记(伤害管线 / 弓箭用)。
|
|
* @param entityId 塔
|
|
*/
|
|
getFlags(entityId: EntityId): DigitCombatFlags | null {
|
|
for (let i = 0; i < this._flags.length; i++) {
|
|
if (this._flags[i].entityId === entityId) return this._flags[i].flags;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 某玩家携带变更后重刷其场上单位。
|
|
* @param playerId 玩家
|
|
*/
|
|
reapplyPlayer(playerId: number): void {
|
|
const battle = this._battle;
|
|
if (!battle) {
|
|
console.warn('[BattleBuffDigitStat] reapply 未 bind', playerId);
|
|
return;
|
|
}
|
|
const pid = Math.max(0, Math.floor(Number(playerId) || 0));
|
|
const session = battle.session;
|
|
const seen: EntityId[] = [];
|
|
const player = battle.getPlayer(pid);
|
|
const deployed = player?.deployedUnits ?? [];
|
|
for (let i = 0; i < deployed.length; i++) {
|
|
const eid = deployed[i];
|
|
if (this._hasSeen(seen, eid)) continue;
|
|
seen.push(eid);
|
|
this.refreshEntity(session, eid, pid);
|
|
}
|
|
session.world.view2(CombatComp, TeamComp).forEach((entityId, _combat, team) => {
|
|
if (team.ownerId !== pid) return;
|
|
if (this._hasSeen(seen, entityId)) return;
|
|
seen.push(entityId);
|
|
this.refreshEntity(session, entityId, pid);
|
|
});
|
|
if (this._hasDigitPassives(pid) && seen.length <= 0) {
|
|
console.warn('[BattleBuffDigitStat] 有数字 buff 但场上无塔可刷', pid);
|
|
}
|
|
GEvent.Ins.emit(GEvent.UpdateBestPlace);
|
|
}
|
|
|
|
/**
|
|
* 未部署/配表预览:把 101~104 叠到快照数值上(与 ECS 通道一致)。
|
|
* @param stats 基础战斗数值
|
|
* @param weapon1Id Hero.id
|
|
* @param level 合成等级
|
|
* @param playerId 携带方
|
|
*/
|
|
previewOntoStats(stats: TowerCombatStats, weapon1Id: number, level: number, playerId: number): TowerCombatStats {
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, weapon1Id);
|
|
if (!hero) return stats;
|
|
const heroType = this._resolveElementType(weapon1Id, hero);
|
|
const lv = this._resolveMatchLevel(hero, level);
|
|
const pid = Math.max(0, Math.floor(Number(playerId) || 0));
|
|
let attackMul = 1;
|
|
let aspdAdd = 0;
|
|
let critRateAdd = 0;
|
|
let critDmgAdd = 0;
|
|
let rangeAdd = 0;
|
|
const passives = BattleBuffCarryStore.getPassives(pid);
|
|
for (let i = 0; i < passives.length; i++) {
|
|
const row = passives[i].row as ITableBattlebuff | undefined;
|
|
if (!row) continue;
|
|
const ft = Math.floor(Number(row.functiontype) || 0);
|
|
if (!this._isStatFt(ft) && ft !== BattleBuffFunctionType.DigitRange) continue;
|
|
if (!BattleBuffTargetParse.matches(row.target, row.targetpar, heroType, lv, 1)) continue;
|
|
const nums = parseFunctionParNums(row.functionpar);
|
|
const wan = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (ft === BattleBuffFunctionType.DigitDamage) {
|
|
if (wan === 0) continue;
|
|
attackMul *= 1 + wan / 10000;
|
|
} else if (ft === BattleBuffFunctionType.DigitCritRate) {
|
|
if (wan === 0) continue;
|
|
critRateAdd += wan / 10000;
|
|
} else if (ft === BattleBuffFunctionType.DigitCritDamage) {
|
|
if (wan === 0) continue;
|
|
critDmgAdd += wan / 10000;
|
|
} else if (ft === BattleBuffFunctionType.DigitAttackSpeed) {
|
|
if (wan === 0) continue;
|
|
aspdAdd += wan;
|
|
} else if (ft === BattleBuffFunctionType.DigitRange) {
|
|
if (wan === 0) continue;
|
|
rangeAdd += Math.abs(wan) <= 16 ? wan * BattleMapSpec.CELL_SIZE : wan;
|
|
}
|
|
}
|
|
if (attackMul === 1 && aspdAdd === 0 && critRateAdd === 0 && critDmgAdd === 0 && rangeAdd === 0) return stats;
|
|
const attackSpeed = stats.attackSpeed * (1 + aspdAdd / 10000);
|
|
return {
|
|
...stats,
|
|
attack: Math.floor(stats.attack * attackMul),
|
|
attackSpeed,
|
|
attackInterval: attackSpeed > 0 ? 1 / attackSpeed : 0,
|
|
critRate: Math.min(1, stats.critRate + critRateAdd),
|
|
critDamage: stats.critDamage + critDmgAdd,
|
|
range: stats.range + rangeAdd,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 单塔刷新 101~112。
|
|
* @param session 会话
|
|
* @param entityId 实体
|
|
* @param ownerPlayerId 归属
|
|
*/
|
|
refreshEntity(session: BattleSession, entityId: EntityId, ownerPlayerId: number): void {
|
|
this._clearDigitMods(session, entityId);
|
|
this._clearFlags(entityId);
|
|
const pid = Math.max(0, Math.floor(Number(ownerPlayerId) || 0));
|
|
const loadout = getTowerLoadoutService().getLoadout(entityId);
|
|
if (!loadout) {
|
|
getPassiveSkillHost().syncBuffSkills(session, entityId, []);
|
|
return;
|
|
}
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, loadout.weapon1Id);
|
|
if (!hero) {
|
|
console.warn('[BattleBuffDigitStat] Hero 缺失', loadout.weapon1Id, entityId);
|
|
getPassiveSkillHost().syncBuffSkills(session, entityId, []);
|
|
return;
|
|
}
|
|
const heroType = this._resolveElementType(loadout.weapon1Id, hero);
|
|
const level = this._resolveMatchLevel(hero, loadout.level);
|
|
const world = session.world;
|
|
if (!world.isAlive(entityId)) return;
|
|
if (!world.getComponent(entityId, CombatComp)) {
|
|
console.warn('[BattleBuffDigitStat] 无 CombatComp', entityId, loadout.weapon1Id);
|
|
getPassiveSkillHost().syncBuffSkills(session, entityId, []);
|
|
return;
|
|
}
|
|
const snaps = this._buildSnaps(session, pid);
|
|
let rank = 0;
|
|
for (let i = 0; i < snaps.length; i++) {
|
|
if (snaps[i].entityId === entityId) {
|
|
rank = snaps[i].rank;
|
|
break;
|
|
}
|
|
}
|
|
const grantSkills: number[] = [];
|
|
const flags = this._emptyFlags();
|
|
const defaultSkillId = Math.floor(Number(hero.skill_id) || 0);
|
|
const passives = BattleBuffCarryStore.getPassives(pid);
|
|
for (let i = 0; i < passives.length; i++) {
|
|
const row = passives[i].row as ITableBattlebuff | undefined;
|
|
if (!row) continue;
|
|
if (!BattleBuffTargetParse.matches(row.target, row.targetpar, heroType, level, rank)) continue;
|
|
const ft = Math.floor(Number(row.functiontype) || 0);
|
|
const ft1 = Math.floor(Number(row.functiontype1) || 0);
|
|
this._applyPrimary(session, entityId, row, ft, i, grantSkills, flags, defaultSkillId);
|
|
this._applySecondary(row, ft1, flags, defaultSkillId);
|
|
}
|
|
this._setFlags(entityId, flags);
|
|
getPassiveSkillHost().syncBuffSkills(session, entityId, grantSkills);
|
|
}
|
|
|
|
private _applyPrimary(
|
|
session: BattleSession,
|
|
entityId: EntityId,
|
|
row: ITableBattlebuff,
|
|
ft: number,
|
|
index: number,
|
|
grantSkills: number[],
|
|
flags: DigitCombatFlags,
|
|
defaultSkillId: number,
|
|
): void {
|
|
const source = `${DIGIT_STAT_SOURCE}${row.id}_${index}`;
|
|
const nums = parseFunctionParNums(row.functionpar);
|
|
if (ft === BattleBuffFunctionType.DigitDamage
|
|
|| ft === BattleBuffFunctionType.DigitCritRate
|
|
|| ft === BattleBuffFunctionType.DigitCritDamage
|
|
|| ft === BattleBuffFunctionType.DigitAttackSpeed) {
|
|
const wan = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (wan === 0) {
|
|
console.warn('[BattleBuffDigitStat] functionpar 无效', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
this._applyStat(session, entityId, ft, wan, source);
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitGrantSkill) {
|
|
const skillId = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (!(skillId > 0)) {
|
|
console.warn('[BattleBuffDigitStat] ft105 缺 Skill.id', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
grantSkills.push(skillId);
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitRange) {
|
|
const raw = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (raw === 0) {
|
|
console.warn('[BattleBuffDigitStat] ft106 缺距离', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
const addPx = Math.abs(raw) <= 16 ? raw * BattleMapSpec.CELL_SIZE : raw;
|
|
this._ensureAttr(session, entityId);
|
|
session.attributes.addModifier(entityId, {
|
|
attr: AttrId.Range, op: 'Add', value: addPx, source,
|
|
});
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitPierce) {
|
|
const n = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (!(n > 0)) {
|
|
console.warn('[BattleBuffDigitStat] ft108 缺穿透', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
flags.pierceAdd += n;
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitExtraArrow) {
|
|
const n = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (!(n > 0)) {
|
|
console.warn('[BattleBuffDigitStat] ft109 缺箭数', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
flags.extraArrow += n;
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitChanceMulDmg) {
|
|
if (nums.length < 2) {
|
|
console.warn('[BattleBuffDigitStat] ft111 需 概率,倍数', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
flags.chanceMul = { wan: Math.floor(nums[0]), mul: Math.floor(nums[1]) };
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitCondForceCrit) {
|
|
const t = Math.floor(Number(row.target) || 0);
|
|
if (t === 2) flags.condCrit = 'poison';
|
|
else if (t === 3) flags.condCrit = 'slow';
|
|
else if (t === 4) flags.condCrit = 'always_convert';
|
|
else console.warn('[BattleBuffDigitStat] ft112 无法从 target 推断模式', row.id, t);
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitSkillDmgMul) {
|
|
const parsed = parseBattleBuffSkillListMul(row.functionpar);
|
|
if (!parsed) {
|
|
console.warn('[BattleBuffDigitStat] ft107 functionpar 无效', row.id, row.functionpar);
|
|
return;
|
|
}
|
|
flags.skillMuls.push({ skillIds: this._expandWeaponSkillIds(parsed.skillIds), mul: parsed.mul });
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitDefaultSkillDmgMul) {
|
|
this._pushDefaultSkillMul(row, row.functionpar, flags, defaultSkillId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param row Battlebuff 行
|
|
* @param ft1 functiontype1
|
|
* @param flags 塔战斗标记
|
|
* @param defaultSkillId 该数字 Hero.skill_id
|
|
*/
|
|
private _applySecondary(row: ITableBattlebuff, ft1: number, flags: DigitCombatFlags, defaultSkillId: number): void {
|
|
if (!(ft1 > 0)) return;
|
|
if (ft1 === BattleBuffFunctionType.DigitDefaultSkillDmgMul) {
|
|
this._pushDefaultSkillMul(row, row.functionpar1, flags, defaultSkillId);
|
|
return;
|
|
}
|
|
const parsed = parseBattleBuffSkillListMul(row.functionpar1);
|
|
if (!parsed) {
|
|
console.warn('[BattleBuffDigitStat] functiontype1 参数无效', row.id, ft1, row.functionpar1);
|
|
return;
|
|
}
|
|
const skillIds = this._expandWeaponSkillIds(parsed.skillIds);
|
|
if (ft1 === BattleBuffFunctionType.DigitSkillDmgMul) {
|
|
flags.skillMuls.push({ skillIds, mul: parsed.mul });
|
|
return;
|
|
}
|
|
if (ft1 === BattleBuffFunctionType.DigitSkillForceCrit) {
|
|
flags.forceCritSkills.push({ skillIds, convert: true });
|
|
return;
|
|
}
|
|
console.warn('[BattleBuffDigitStat] 未知 functiontype1', row.id, ft1);
|
|
}
|
|
|
|
/**
|
|
* 113:对锁定数字的 Hero.skill_id 技能施加伤害倍数。
|
|
* @param row Battlebuff 行
|
|
* @param raw functionpar / functionpar1(倍数)
|
|
* @param flags 塔战斗标记
|
|
* @param defaultSkillId Hero.skill_id
|
|
*/
|
|
private _pushDefaultSkillMul(
|
|
row: ITableBattlebuff,
|
|
raw: string | number | null | undefined,
|
|
flags: DigitCombatFlags,
|
|
defaultSkillId: number,
|
|
): void {
|
|
const nums = parseFunctionParNums(raw);
|
|
const mul = nums.length > 0 ? Math.floor(nums[0]) : 0;
|
|
if (!(mul > 0)) {
|
|
console.warn('[BattleBuffDigitStat] ft113 倍数无效', row.id, raw);
|
|
return;
|
|
}
|
|
if (!(defaultSkillId > 0)) {
|
|
console.warn('[BattleBuffDigitStat] ft113 目标数字无 skill_id', row.id);
|
|
return;
|
|
}
|
|
flags.skillMuls.push({ skillIds: [defaultSkillId], mul });
|
|
}
|
|
|
|
/**
|
|
* Weapon.id 列表展开为 act_skill(Skill.id),便于伤害管线按 Ability 匹配。
|
|
* @param weaponIds Weapon.id
|
|
*/
|
|
private _expandWeaponSkillIds(weaponIds: readonly number[]): number[] {
|
|
const out: number[] = [];
|
|
for (let i = 0; i < weaponIds.length; i++) {
|
|
const wid = weaponIds[i];
|
|
const w = gg.data.table.getTableData<ITableWeapon>(TableNames.Weapon, wid);
|
|
if (!w) {
|
|
console.warn('[BattleBuffDigitStat] 技能列表缺 Weapon', wid);
|
|
continue;
|
|
}
|
|
const sid = Math.floor(Number(w.act_skill) || 0);
|
|
if (!(sid > 0)) {
|
|
console.warn('[BattleBuffDigitStat] Weapon 无 act_skill', wid);
|
|
continue;
|
|
}
|
|
if (out.indexOf(sid) < 0) out.push(sid);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
private _buildSnaps(session: BattleSession, playerId: number): DigitUnitSnap[] {
|
|
const battle = this._battle;
|
|
const out: DigitUnitSnap[] = [];
|
|
const seen: EntityId[] = [];
|
|
const push = (entityId: EntityId): void => {
|
|
if (this._hasSeen(seen, entityId)) return;
|
|
const loadout = getTowerLoadoutService().getLoadout(entityId);
|
|
if (!loadout) return;
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, loadout.weapon1Id);
|
|
if (!hero) return;
|
|
if (!session.world.isAlive(entityId)) return;
|
|
if (!session.world.getComponent(entityId, CombatComp)) return;
|
|
seen.push(entityId);
|
|
out.push({
|
|
entityId,
|
|
heroType: this._resolveElementType(loadout.weapon1Id, hero),
|
|
level: this._resolveMatchLevel(hero, loadout.level),
|
|
rank: 0,
|
|
});
|
|
};
|
|
const player = battle?.getPlayer(playerId);
|
|
const deployed = player?.deployedUnits ?? [];
|
|
for (let i = 0; i < deployed.length; i++) push(deployed[i]);
|
|
session.world.view2(CombatComp, TeamComp).forEach((entityId, _c, team) => {
|
|
if (team.ownerId !== playerId) return;
|
|
push(entityId);
|
|
});
|
|
for (let elem = 1; elem <= 5; elem++) {
|
|
const group: DigitUnitSnap[] = [];
|
|
for (let i = 0; i < out.length; i++) {
|
|
if (out[i].heroType === elem) group.push(out[i]);
|
|
}
|
|
group.sort((a, b) => b.level - a.level || a.entityId - b.entityId);
|
|
for (let i = 0; i < group.length; i++) group[i].rank = i + 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
private _resolveMatchLevel(hero: ITableHero, loadoutLevel: number): number {
|
|
if (isElementHero(hero)) {
|
|
const hlv = Math.floor(Number(hero.level) || 0);
|
|
if (hlv > 0) return hlv;
|
|
}
|
|
return Math.max(1, Math.floor(Number(loadoutLevel) || 0));
|
|
}
|
|
|
|
private _hasSeen(seen: readonly EntityId[], eid: EntityId): boolean {
|
|
for (let i = 0; i < seen.length; i++) {
|
|
if (seen[i] === eid) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _hasDigitPassives(playerId: number): boolean {
|
|
const passives = BattleBuffCarryStore.getPassives(playerId);
|
|
for (let i = 0; i < passives.length; i++) {
|
|
const row = passives[i].row as ITableBattlebuff | undefined;
|
|
if (!row) continue;
|
|
const ft = Math.floor(Number(row.functiontype) || 0);
|
|
const ft1 = Math.floor(Number(row.functiontype1) || 0);
|
|
if (this._isDigitFt(ft) || ft1 > 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _isStatFt(ft: number): boolean {
|
|
return ft === BattleBuffFunctionType.DigitDamage
|
|
|| ft === BattleBuffFunctionType.DigitCritRate
|
|
|| ft === BattleBuffFunctionType.DigitCritDamage
|
|
|| ft === BattleBuffFunctionType.DigitAttackSpeed;
|
|
}
|
|
|
|
private _isDigitFt(ft: number): boolean {
|
|
return this._isStatFt(ft)
|
|
|| ft === BattleBuffFunctionType.DigitGrantSkill
|
|
|| ft === BattleBuffFunctionType.DigitRange
|
|
|| ft === BattleBuffFunctionType.DigitPierce
|
|
|| ft === BattleBuffFunctionType.DigitExtraArrow
|
|
|| ft === BattleBuffFunctionType.DigitChanceMulDmg
|
|
|| ft === BattleBuffFunctionType.DigitCondForceCrit
|
|
|| ft === BattleBuffFunctionType.DigitSkillDmgMul
|
|
|| ft === BattleBuffFunctionType.DigitSkillForceCrit
|
|
|| ft === BattleBuffFunctionType.DigitDefaultSkillDmgMul;
|
|
}
|
|
|
|
private _resolveElementType(weapon1Id: number, hero: ITableHero): number {
|
|
const provider = WeaponComposeConfigProvider.instance;
|
|
provider.load();
|
|
const w2 = provider.getWeapon2IdForWeapon1(weapon1Id);
|
|
if (w2 != null) {
|
|
const t = Math.floor(Number(provider.getWeapon2Type(w2)) || 0);
|
|
if (t >= 1 && t <= 5) return t;
|
|
}
|
|
return Math.floor(Number(hero.herotype) || 0);
|
|
}
|
|
|
|
private _clearDigitMods(session: BattleSession, entityId: EntityId): void {
|
|
const world = session.world;
|
|
if (!world.isAlive(entityId)) return;
|
|
const attr = world.getComponent(entityId, AttributeComp);
|
|
if (!attr) return;
|
|
const next = [];
|
|
for (let i = 0; i < attr.modifiers.length; i++) {
|
|
const src = String(attr.modifiers[i].source ?? '');
|
|
if (src.indexOf(DIGIT_STAT_SOURCE) === 0) continue;
|
|
next.push(attr.modifiers[i]);
|
|
}
|
|
attr.modifiers = next;
|
|
}
|
|
|
|
private _ensureAttr(session: BattleSession, entityId: EntityId): void {
|
|
if (!session.world.getComponent(entityId, AttributeComp)) {
|
|
session.world.addComponent(entityId, new AttributeComp());
|
|
}
|
|
}
|
|
|
|
private _applyStat(session: BattleSession, entityId: EntityId, ft: number, wan: number, source: string): void {
|
|
const world = session.world;
|
|
if (!world.isAlive(entityId)) return;
|
|
if (!world.getComponent(entityId, CombatComp)) return;
|
|
this._ensureAttr(session, entityId);
|
|
const attrSvc = session.attributes;
|
|
if (ft === BattleBuffFunctionType.DigitDamage) {
|
|
attrSvc.addModifier(entityId, {
|
|
attr: AttrId.Attack, op: 'Mul', value: 1 + wan / 10000, source,
|
|
});
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitCritRate) {
|
|
attrSvc.addModifier(entityId, {
|
|
attr: AttrId.CritRate, op: 'Add', value: wan / 10000, source,
|
|
});
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitCritDamage) {
|
|
attrSvc.addModifier(entityId, {
|
|
attr: AttrId.CritDamage, op: 'Add', value: wan / 10000, source,
|
|
});
|
|
return;
|
|
}
|
|
if (ft === BattleBuffFunctionType.DigitAttackSpeed) {
|
|
attrSvc.addModifier(entityId, {
|
|
attr: 'aspd_add', op: 'Add', value: wan, source,
|
|
});
|
|
}
|
|
}
|
|
|
|
private _emptyFlags(): DigitCombatFlags {
|
|
return {
|
|
skillMuls: [],
|
|
forceCritSkills: [],
|
|
chanceMul: null,
|
|
condCrit: null,
|
|
pierceAdd: 0,
|
|
extraArrow: 0,
|
|
};
|
|
}
|
|
|
|
private _clearFlags(entityId: EntityId): void {
|
|
for (let i = this._flags.length - 1; i >= 0; i--) {
|
|
if (this._flags[i].entityId === entityId) this._flags.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
private _setFlags(entityId: EntityId, flags: DigitCombatFlags): void {
|
|
this._clearFlags(entityId);
|
|
const empty = flags.skillMuls.length <= 0
|
|
&& flags.forceCritSkills.length <= 0
|
|
&& !flags.chanceMul
|
|
&& !flags.condCrit
|
|
&& flags.pierceAdd <= 0
|
|
&& flags.extraArrow <= 0;
|
|
if (empty) return;
|
|
this._flags.push({ entityId, flags });
|
|
}
|
|
}
|
|
|
|
let _host: BattleBuffDigitStatHost | null = null;
|
|
|
|
/** 局内数字属性 Host */
|
|
export function getBattleBuffDigitStatHost(): BattleBuffDigitStatHost {
|
|
if (!_host) _host = new BattleBuffDigitStatHost();
|
|
return _host;
|
|
}
|
|
|