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/bond/InBattleBondHost.ts

170 lines
6.5 KiB

3 weeks ago
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<number, TextGrid> | undefined;
/** group2 伙伴上次是否在 map 攻击激活 */
private _group2PartnerActive = new Map<number, boolean>();
bindRuntime(runtime: IBattleRuntimeHandle): void {
this._runtime = runtime;
}
bindMapGrids(mapGrids: ReadonlyMap<number, TextGrid> | undefined): void {
this._mapGrids = mapGrids;
}
get mapGrids(): ReadonlyMap<number, TextGrid> | 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<number, EntityId[]>();
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<number, number>();
for (const entry of entries) {
countById.set(entry.id, (countById.get(entry.id) ?? 0) + 1);
}
const seen = createMembershipSet<number>();
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<number>();
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<number>();
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;
}