import { createMembershipSet, membershipAdd } from '../../../mx/tools/MembershipSet'; import type { EntityId } from 'db://assets/mx/module/ecs'; import type { IBattleRuntimeHandle } from '../types/ProjectBattleTypes'; import type { Player } from '../../game/Player'; import type { TextGrid } from '../view/TextGrid'; import { parseSkillGroup } from './BondConfigParser'; import { isPartnerWeaponActiveOnMap } from './BondEvaluator'; import { applyBondAttrsToEntity, removeBondModifiers } from './BondEffectApplier'; import { entriesAffectingWeapon } from '../skill/GlobalSkillEffects'; import { getTowerLoadoutService } from '../combat/TowerLoadoutService'; /** 局内羁绊宿主:按伙伴激活态动态物化 group 属性 */ export class InBattleBondHost { private _runtime: IBattleRuntimeHandle | null = null; private _mapGrids: ReadonlyMap | undefined; /** group2 伙伴上次是否在 map 攻击激活 */ private _group2PartnerActive = new Map(); bindRuntime(runtime: IBattleRuntimeHandle): void { this._runtime = runtime; } bindMapGrids(mapGrids: ReadonlyMap | undefined): void { this._mapGrids = mapGrids; } get mapGrids(): ReadonlyMap | undefined { return this._mapGrids; } unbind(): void { this._runtime = null; this._mapGrids = undefined; this._group2PartnerActive.clear(); } /** * group2 羁绊:伙伴 Weapon1 在场且攻击激活时,词条效果翻倍(×2)。 * 配表 group2 为伙伴 Hero.id。 */ getGroup2StackMultiplier(entry: ITableSkill): number { const partnerId = Number((entry as ITableSkill & { group2?: number }).group2); if (!Number.isFinite(partnerId) || partnerId <= 0) return 1; if (!this._runtime) return 1; return isPartnerWeaponActiveOnMap(this._runtime, partnerId, this._mapGrids) ? 2 : 1; } /** 组合激活态变化 / 词条物化后刷新 */ refreshPlayer(player: Player): void { const runtime = this._runtime; if (!runtime) return; const session = runtime.session; const loadoutSvc = getTowerLoadoutService(); const byWeapon = new Map(); for (const entityId of player.deployedUnits) { if (!session.world.isAlive(entityId)) continue; const weapon1Id = loadoutSvc.getLoadout(entityId)?.weapon1Id; if (!weapon1Id) continue; if (!byWeapon.has(weapon1Id)) byWeapon.set(weapon1Id, []); byWeapon.get(weapon1Id)!.push(entityId); } for (const [weapon1Id, entities] of byWeapon) { const entries = entriesAffectingWeapon(player.selectedSkills, weapon1Id); for (const entityId of entities) { this._refreshEntity(entityId, entries); } } this._rematerializeGroup2WeaponsIfPartnerChanged(player); } refreshEntity(entityId: EntityId, player: Player): void { const loadout = getTowerLoadoutService().getLoadout(entityId); if (!loadout) return; const entries = entriesAffectingWeapon(player.selectedSkills, loadout.weapon1Id); this._refreshEntity(entityId, entries); } private _refreshEntity( entityId: EntityId, entries: readonly ITableSkill[], ): void { const runtime = this._runtime; if (!runtime) return; const session = runtime.session; if (!session.world.isAlive(entityId)) return; removeBondModifiers(session, entityId); const countById = new Map(); for (const entry of entries) { countById.set(entry.id, (countById.get(entry.id) ?? 0) + 1); } const seen = createMembershipSet(); for (const entry of entries) { if (seen.has(entry.id)) continue; membershipAdd(seen, entry.id); const groupRaw = String(entry.group ?? '').trim(); if (!groupRaw) continue; const parsed = parseSkillGroup(groupRaw); if (!parsed || parsed.targetType !== 1) continue; if (!isPartnerWeaponActiveOnMap(runtime, parsed.partnerWeapon1Id, this._mapGrids)) { continue; } const stack = countById.get(entry.id) ?? 1; applyBondAttrsToEntity( session, entityId, parsed.attrs, `bond_entry_${entry.id}`, stack, ); } } /** 伙伴上下场后重算带 group2 词条的武器 */ private _rematerializeGroup2WeaponsIfPartnerChanged(player: Player): void { const runtime = this._runtime; if (!runtime) return; const session = runtime.session; const partnerIds = createMembershipSet(); for (const entry of player.selectedSkills) { const partnerId = Number((entry as ITableSkill & { group2?: number }).group2); if (Number.isFinite(partnerId) && partnerId > 0) membershipAdd(partnerIds, partnerId); } if (partnerIds.size === 0) return; let changed = false; for (const partnerId of partnerIds.keys()) { const active = isPartnerWeaponActiveOnMap(runtime, partnerId, this._mapGrids); if (this._group2PartnerActive.get(partnerId) === active) continue; this._group2PartnerActive.set(partnerId, active); changed = true; } if (!changed) return; const weapons = createMembershipSet(); for (const entry of player.selectedSkills) { const partnerId = Number((entry as ITableSkill & { group2?: number }).group2); if (partnerId > 0 && partnerIds.has(partnerId)) { membershipAdd(weapons, Number((entry as ITableSkill & { weapon?: number }).weapon) || 0); } } if (weapons.size === 0) return; const loadoutSvc = getTowerLoadoutService(); for (const entityId of player.deployedUnits) { if (!session.world.isAlive(entityId)) continue; const loadout = loadoutSvc.getLoadout(entityId); if (!loadout || !weapons.has(loadout.weapon1Id)) continue; this.refreshEntity(entityId, player); } } } let _host: InBattleBondHost | null = null; export function getInBattleBondHost(): InBattleBondHost { if (!_host) _host = new InBattleBondHost(); return _host; } export function resetInBattleBondHostForTests(): void { _host = null; }