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.
536 lines
20 KiB
536 lines
20 KiB
|
3 weeks ago
|
import type { BattleSession } from 'db://assets/mx/module/battle';
|
||
|
|
import { AttributeComp, CombatComp } from 'db://assets/mx/module/battle/ecs/Components';
|
||
|
|
import type { ComposedUnit } from 'db://assets/mx/module/merge';
|
||
|
|
import type { EntityId } from 'db://assets/mx/module/ecs';
|
||
|
|
import { GEvent } from 'db://assets/mx/module/event/GEvent';
|
||
|
|
import type { Player } from '../../game/Player';
|
||
|
|
import { WeaponComposeConfigProvider } from '../bag/WeaponComposeConfigProvider';
|
||
|
|
import type { MapManager } from '../map/legacy/MapManager';
|
||
|
|
import { composeKey } from '../runtime/ComposeUnitKey';
|
||
|
|
import { findEntityIdByGridIndex } from '../runtime/DeployEntityQuery';
|
||
|
|
|
||
|
|
/** 攻速符落点(背包槽 / 地图格) */
|
||
|
|
export type BattleGameAttackSpeedDropTarget =
|
||
|
|
| { kind: 'map'; gridIndex: number }
|
||
|
|
| { kind: 'bag'; slotIndex: number };
|
||
|
|
|
||
|
|
/** 已绑定到具体攻击单位的攻速层 */
|
||
|
|
type AttackSpeedLayer = {
|
||
|
|
/** modifier source(buffId + skill 槽 + 落点唯一) */
|
||
|
|
source: string;
|
||
|
|
/** 万分比加成 */
|
||
|
|
bonusWan: number;
|
||
|
|
/** 受益 ECS 实体 */
|
||
|
|
entityId: EntityId;
|
||
|
|
/** buff 配置 ID,用于叠加判定 */
|
||
|
|
buffId: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 背包落点待绑定(字块上场后再挂到对应单位) */
|
||
|
|
type PendingBagAttackSpeed = {
|
||
|
|
/** 背包槽索引 */
|
||
|
|
bagSlot: number;
|
||
|
|
source: string;
|
||
|
|
bonusWan: number;
|
||
|
|
buffId: number;
|
||
|
|
/** 是否已绑定到实体 */
|
||
|
|
applied: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 背包→地图落点后待重试绑定(DeploySync 晚于 drop 一帧时) */
|
||
|
|
type DeferredBagPlaceBind = {
|
||
|
|
bagSlot: number;
|
||
|
|
gridIndex: number;
|
||
|
|
buffId: number;
|
||
|
|
source: string;
|
||
|
|
bonusWan: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 攻速 buff modifier source 前缀 */
|
||
|
|
const ASPD_SOURCE_PREFIX = 'battle_buff_aspd_';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 局内主动攻速符(functiontype=17)宿主。
|
||
|
|
* 落点字块类型与封神榜/授仙箓一致(多格组合字已合成武将 / type1 普通单字;type2 未合成散字不可);
|
||
|
|
* 攻速仅作用于落点对应的已上场攻击单位(背包可预绑定,上场后生效);
|
||
|
|
* 每个 buffId 对同一单位仅可成功施放一次(背包预绑与上场后为同一实例;同 buffId 不叠加,不同 buffId 可叠加)。
|
||
|
|
*/
|
||
|
|
export class BattleGameGlobalAttackSpeedHost {
|
||
|
|
private static _inst: BattleGameGlobalAttackSpeedHost | null = null;
|
||
|
|
|
||
|
|
/** 已绑定单位的攻速层 */
|
||
|
|
private _layers: AttackSpeedLayer[] = [];
|
||
|
|
/** 背包落点待绑定 */
|
||
|
|
private _pendingBag: PendingBagAttackSpeed[] = [];
|
||
|
|
/** 落点待重试绑定 */
|
||
|
|
private _deferredBagPlace: DeferredBagPlaceBind[] = [];
|
||
|
|
/** 已施放单位:buffId + 单位 usageKey(同 buffId 同单位仅一次) */
|
||
|
|
private _usedTargets: { buffId: number; targetKey: string }[] = [];
|
||
|
|
/** 背包槽 → 上场实体(同一字块实例 bag / map 共用去重) */
|
||
|
|
private _bagSlotEntityLinks: { bagSlot: number; entityId: EntityId }[] = [];
|
||
|
|
|
||
|
|
/** 单例 */
|
||
|
|
static get(): BattleGameGlobalAttackSpeedHost {
|
||
|
|
if (!BattleGameGlobalAttackSpeedHost._inst) {
|
||
|
|
BattleGameGlobalAttackSpeedHost._inst = new BattleGameGlobalAttackSpeedHost();
|
||
|
|
}
|
||
|
|
return BattleGameGlobalAttackSpeedHost._inst;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 重置局内状态(战斗结束) */
|
||
|
|
reset(): void {
|
||
|
|
this._layers.length = 0;
|
||
|
|
this._pendingBag.length = 0;
|
||
|
|
this._deferredBagPlace.length = 0;
|
||
|
|
this._usedTargets.length = 0;
|
||
|
|
this._bagSlotEntityLinks.length = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 快照实体已绑定的攻速符。
|
||
|
|
* @param entityId 塔实体
|
||
|
|
*/
|
||
|
|
snapshot(entityId: EntityId): { buffId: number; source: string; value: number }[] {
|
||
|
|
const out: { buffId: number; source: string; value: number }[] = [];
|
||
|
|
for (let i = 0; i < this._layers.length; i++) {
|
||
|
|
const layer = this._layers[i];
|
||
|
|
if (layer.entityId !== entityId) continue;
|
||
|
|
out.push({ buffId: layer.buffId, source: layer.source, value: layer.bonusWan });
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 拆塔后把攻速符绑回新实体。
|
||
|
|
* @param session 战斗会话
|
||
|
|
* @param entityId 新实体
|
||
|
|
* @param buffs 快照
|
||
|
|
*/
|
||
|
|
restore(session: BattleSession, entityId: EntityId, buffs: readonly { buffId: number; source: string; value: number }[]): void {
|
||
|
|
for (let i = 0; i < buffs.length; i++) {
|
||
|
|
const b = buffs[i];
|
||
|
|
if (this._hasEntityBuffLayer(b.buffId, entityId)) continue;
|
||
|
|
this._bindLayer(session, entityId, b.source, b.value, b.buffId);
|
||
|
|
this._markTargetUsed(b.buffId, `entity:${entityId}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 实体卖掉后清层(字上的绑定快照仍保留)。
|
||
|
|
* @param entityId 塔实体
|
||
|
|
*/
|
||
|
|
forgetEntity(entityId: EntityId): void {
|
||
|
|
if (!(entityId > 0)) return;
|
||
|
|
for (let i = this._layers.length - 1; i >= 0; i--) {
|
||
|
|
if (this._layers[i].entityId === entityId) this._layers.splice(i, 1);
|
||
|
|
}
|
||
|
|
for (let i = this._bagSlotEntityLinks.length - 1; i >= 0; i--) {
|
||
|
|
if (this._bagSlotEntityLinks[i].entityId === entityId) this._bagSlotEntityLinks.splice(i, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 指定 buffId 是否已对目标单位施放过。
|
||
|
|
* @param buffId Battlebuff.id(functiontype=17)
|
||
|
|
*/
|
||
|
|
isTargetUsed(
|
||
|
|
buffId: number,
|
||
|
|
target: BattleGameAttackSpeedDropTarget,
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
): boolean {
|
||
|
|
if (buffId <= 0) return false;
|
||
|
|
if (target.kind === 'map') {
|
||
|
|
const entityId = this.resolveMapTargetEntityId(session, player, map, target.gridIndex);
|
||
|
|
if (entityId != null && this._hasEntityBuffLayer(buffId, entityId)) return true;
|
||
|
|
}
|
||
|
|
const keys = this._collectUsageKeys(buffId, session, player, map, target);
|
||
|
|
for (let i = 0; i < keys.length; i++) {
|
||
|
|
if (this._hasUsedTarget(buffId, keys[i])) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 解析攻速符去重键:已上场 → entity;多格未上场 → compose;单字未上场 → map 格;背包 → bag 槽。
|
||
|
|
*/
|
||
|
|
static resolveUsageKey(
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
target: BattleGameAttackSpeedDropTarget,
|
||
|
|
): string {
|
||
|
|
if (target.kind === 'bag') {
|
||
|
|
return `bag:${target.slotIndex}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const gridIndex = target.gridIndex;
|
||
|
|
const host = BattleGameGlobalAttackSpeedHost.get();
|
||
|
|
const entityId = host.resolveMapTargetEntityId(session, player, map, gridIndex);
|
||
|
|
if (entityId != null) {
|
||
|
|
return `entity:${entityId}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const compose = BattleGameGlobalAttackSpeedHost._findCompositionAt(player, gridIndex);
|
||
|
|
if (compose && BattleGameGlobalAttackSpeedHost._isMultiCellComposition(compose)) {
|
||
|
|
return `compose:${composeKey(compose)}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
return `map:${gridIndex}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 解析地图落点对应的已部署攻击单位。
|
||
|
|
* @returns 实体 ID;无单位时 null
|
||
|
|
*/
|
||
|
|
resolveMapTargetEntityId(
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
gridIndex: number,
|
||
|
|
): EntityId | null {
|
||
|
|
if (!player || !map) return null;
|
||
|
|
const entityId = findEntityIdByGridIndex(gridIndex, session, map, player);
|
||
|
|
if (entityId == null) return null;
|
||
|
|
if (!session.world.isAlive(entityId)) return null;
|
||
|
|
if (!session.world.getComponent(entityId, CombatComp)) return null;
|
||
|
|
return entityId;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 拖拽落点施放:提升落点对应攻击单位攻速。
|
||
|
|
* @param buffId buff 配置 ID(去重 + source 标识)
|
||
|
|
* @param skillSlot skill 槽(仅用于 source 标识不同槽位的同一 buffId)
|
||
|
|
* @param target 落点字块
|
||
|
|
* @param bonusWan functionpar 第二段万分比
|
||
|
|
* @returns 是否成功
|
||
|
|
*/
|
||
|
|
tryActivate(
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
buffId: number,
|
||
|
|
skillSlot: number,
|
||
|
|
target: BattleGameAttackSpeedDropTarget,
|
||
|
|
bonusWan: number,
|
||
|
|
): boolean {
|
||
|
|
if (!player) {
|
||
|
|
console.log('[BattleGameAttackSpeed] 无 player');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (buffId <= 0) {
|
||
|
|
console.log('[BattleGameAttackSpeed] 非法 buffId', buffId);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (this.isTargetUsed(buffId, target, session, player, map)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const wan = Math.floor(Number(bonusWan) || 0);
|
||
|
|
if (wan <= 0) {
|
||
|
|
console.log('[BattleGameAttackSpeed] bonusWan 无效', bonusWan);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const usageKey = BattleGameGlobalAttackSpeedHost.resolveUsageKey(session, player, map, target);
|
||
|
|
const source = BattleGameGlobalAttackSpeedHost._sourceFor(buffId, skillSlot, usageKey);
|
||
|
|
|
||
|
|
if (target.kind === 'map') {
|
||
|
|
const entityId = this.resolveMapTargetEntityId(session, player, map, target.gridIndex);
|
||
|
|
if (entityId == null) return false;
|
||
|
|
this._markTargetUsed(buffId, usageKey);
|
||
|
|
this._bindLayer(session, entityId, source, wan, buffId);
|
||
|
|
this._syncHeroBindAspd(player, target.gridIndex, entityId);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._markTargetUsed(buffId, usageKey);
|
||
|
|
this._pendingBag.push({
|
||
|
|
bagSlot: target.slotIndex,
|
||
|
|
source,
|
||
|
|
bonusWan: wan,
|
||
|
|
buffId,
|
||
|
|
applied: false,
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 背包字块落到地图后,将待绑定攻速挂到对应单位。
|
||
|
|
* @param bagSlot 来源背包槽
|
||
|
|
* @param gridIndex 落点地图格
|
||
|
|
*/
|
||
|
|
onBagPlacedOnMap(
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
bagSlot: number,
|
||
|
|
gridIndex: number,
|
||
|
|
): void {
|
||
|
|
if (!player || !map) return;
|
||
|
|
const entityId = this.resolveMapTargetEntityId(session, player, map, gridIndex);
|
||
|
|
if (entityId != null) {
|
||
|
|
this._linkBagSlotEntity(bagSlot, entityId);
|
||
|
|
this._bindPendingBagSlot(session, entityId, bagSlot);
|
||
|
|
this._syncHeroBindAspd(player, gridIndex, entityId);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const pending = this._findPendingBag(bagSlot);
|
||
|
|
if (!pending || pending.applied) return;
|
||
|
|
this._deferredBagPlace.push({
|
||
|
|
bagSlot,
|
||
|
|
gridIndex,
|
||
|
|
buffId: pending.buffId,
|
||
|
|
source: pending.source,
|
||
|
|
bonusWan: pending.bonusWan,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 塔部署/升级后补施加已绑定的攻速 buff(仅该实体) */
|
||
|
|
refreshEntity(session: BattleSession, entityId: EntityId, player?: Player | null, map?: MapManager | null): void {
|
||
|
|
if (player && map) {
|
||
|
|
this._retryDeferredBagBind(session, player, map, entityId);
|
||
|
|
}
|
||
|
|
for (let i = 0; i < this._layers.length; i++) {
|
||
|
|
const layer = this._layers[i];
|
||
|
|
if (layer.entityId !== entityId) continue;
|
||
|
|
this._applyModifier(session, entityId, layer.source, layer.bonusWan);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private _findPendingBag(bagSlot: number): PendingBagAttackSpeed | null {
|
||
|
|
for (let i = 0; i < this._pendingBag.length; i++) {
|
||
|
|
const pending = this._pendingBag[i];
|
||
|
|
if (pending.bagSlot === bagSlot && !pending.applied) return pending;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _bindPendingBagSlot(session: BattleSession, entityId: EntityId, bagSlot: number): void {
|
||
|
|
for (let i = 0; i < this._pendingBag.length; i++) {
|
||
|
|
const pending = this._pendingBag[i];
|
||
|
|
if (pending.applied || pending.bagSlot !== bagSlot) continue;
|
||
|
|
this._linkBagSlotEntity(bagSlot, entityId);
|
||
|
|
this._markTargetUsed(pending.buffId, `entity:${entityId}`);
|
||
|
|
this._bindLayer(session, entityId, pending.source, pending.bonusWan, pending.buffId);
|
||
|
|
pending.applied = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 收集同一字块实例在 bag / map 上的全部去重键 */
|
||
|
|
private _collectUsageKeys(
|
||
|
|
buffId: number,
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
target: BattleGameAttackSpeedDropTarget,
|
||
|
|
): string[] {
|
||
|
|
const keys: string[] = [];
|
||
|
|
const pushKey = (key: string): void => {
|
||
|
|
if (keys.indexOf(key) >= 0) return;
|
||
|
|
keys.push(key);
|
||
|
|
};
|
||
|
|
|
||
|
|
pushKey(BattleGameGlobalAttackSpeedHost.resolveUsageKey(session, player, map, target));
|
||
|
|
|
||
|
|
if (target.kind === 'bag') {
|
||
|
|
const linkedEntity = this._findEntityForBagSlot(target.slotIndex);
|
||
|
|
if (linkedEntity != null) {
|
||
|
|
pushKey(`entity:${linkedEntity}`);
|
||
|
|
}
|
||
|
|
return keys;
|
||
|
|
}
|
||
|
|
|
||
|
|
const entityId = this.resolveMapTargetEntityId(session, player, map, target.gridIndex);
|
||
|
|
if (entityId != null) {
|
||
|
|
pushKey(`entity:${entityId}`);
|
||
|
|
const linkedBagSlot = this._findBagSlotForEntity(entityId);
|
||
|
|
if (linkedBagSlot != null) {
|
||
|
|
pushKey(`bag:${linkedBagSlot}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let i = 0; i < this._deferredBagPlace.length; i++) {
|
||
|
|
const row = this._deferredBagPlace[i];
|
||
|
|
if (row.buffId !== buffId) continue;
|
||
|
|
if (!BattleGameGlobalAttackSpeedHost._isSameMapTileUnit(player, row.gridIndex, target.gridIndex)) continue;
|
||
|
|
if (!this._hasUsedTarget(row.buffId, `bag:${row.bagSlot}`)) continue;
|
||
|
|
pushKey(`bag:${row.bagSlot}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let i = 0; i < this._usedTargets.length; i++) {
|
||
|
|
const row = this._usedTargets[i];
|
||
|
|
if (row.buffId !== buffId) continue;
|
||
|
|
const bagPrefix = 'bag:';
|
||
|
|
if (!row.targetKey.startsWith(bagPrefix)) continue;
|
||
|
|
const bagSlot = Number(row.targetKey.slice(bagPrefix.length));
|
||
|
|
if (!Number.isFinite(bagSlot)) continue;
|
||
|
|
|
||
|
|
const linkedEntity = this._findEntityForBagSlot(bagSlot);
|
||
|
|
if (entityId != null && linkedEntity === entityId) {
|
||
|
|
pushKey(row.targetKey);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let j = 0; j < this._deferredBagPlace.length; j++) {
|
||
|
|
const def = this._deferredBagPlace[j];
|
||
|
|
if (def.bagSlot !== bagSlot || def.buffId !== buffId) continue;
|
||
|
|
if (BattleGameGlobalAttackSpeedHost._isSameMapTileUnit(player, def.gridIndex, target.gridIndex)) {
|
||
|
|
pushKey(row.targetKey);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return keys;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 实体是否已绑定指定 buffId 的攻速层 */
|
||
|
|
private _hasEntityBuffLayer(buffId: number, entityId: EntityId): boolean {
|
||
|
|
for (let i = 0; i < this._layers.length; i++) {
|
||
|
|
const layer = this._layers[i];
|
||
|
|
if (layer.buffId === buffId && layer.entityId === entityId) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 两地图格是否同一字块实例(同格或多格组合占格) */
|
||
|
|
private static _isSameMapTileUnit(player: Player | null, gridA: number, gridB: number): boolean {
|
||
|
|
if (gridA === gridB) return true;
|
||
|
|
if (!player) return false;
|
||
|
|
const compose = player.compositions.find(c => c.gridIndices.includes(gridA)) ?? null;
|
||
|
|
if (!compose) return false;
|
||
|
|
return compose.gridIndices.includes(gridB);
|
||
|
|
}
|
||
|
|
|
||
|
|
private _linkBagSlotEntity(bagSlot: number, entityId: EntityId): void {
|
||
|
|
for (let i = 0; i < this._bagSlotEntityLinks.length; i++) {
|
||
|
|
if (this._bagSlotEntityLinks[i].bagSlot === bagSlot) {
|
||
|
|
this._bagSlotEntityLinks[i].entityId = entityId;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this._bagSlotEntityLinks.push({ bagSlot, entityId });
|
||
|
|
}
|
||
|
|
|
||
|
|
private _findBagSlotForEntity(entityId: EntityId): number | null {
|
||
|
|
for (let i = 0; i < this._bagSlotEntityLinks.length; i++) {
|
||
|
|
if (this._bagSlotEntityLinks[i].entityId === entityId) {
|
||
|
|
return this._bagSlotEntityLinks[i].bagSlot;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _findEntityForBagSlot(bagSlot: number): EntityId | null {
|
||
|
|
for (let i = 0; i < this._bagSlotEntityLinks.length; i++) {
|
||
|
|
if (this._bagSlotEntityLinks[i].bagSlot === bagSlot) {
|
||
|
|
return this._bagSlotEntityLinks[i].entityId;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _retryDeferredBagBind(
|
||
|
|
session: BattleSession,
|
||
|
|
player: Player,
|
||
|
|
map: MapManager,
|
||
|
|
entityId: EntityId,
|
||
|
|
): void {
|
||
|
|
for (let i = this._deferredBagPlace.length - 1; i >= 0; i--) {
|
||
|
|
const row = this._deferredBagPlace[i];
|
||
|
|
const atGrid = this.resolveMapTargetEntityId(session, player, map, row.gridIndex);
|
||
|
|
if (atGrid !== entityId) continue;
|
||
|
|
this._linkBagSlotEntity(row.bagSlot, entityId);
|
||
|
|
this._markTargetUsed(row.buffId, `entity:${entityId}`);
|
||
|
|
this._bindLayer(session, entityId, row.source, row.bonusWan, row.buffId);
|
||
|
|
const pending = this._findPendingBag(row.bagSlot);
|
||
|
|
if (pending) pending.applied = true;
|
||
|
|
this._deferredBagPlace.splice(i, 1);
|
||
|
|
this._syncHeroBindAspd(player, row.gridIndex, entityId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 攻速变化后写回组合内每个字的绑定。
|
||
|
|
* @param player 玩家
|
||
|
|
* @param gridIndex 落点格
|
||
|
|
* @param entityId 塔实体
|
||
|
|
*/
|
||
|
|
private _syncHeroBindAspd(player: Player, gridIndex: number, entityId: EntityId): void {
|
||
|
|
const compose = BattleGameGlobalAttackSpeedHost._findCompositionAt(player, gridIndex);
|
||
|
|
if (!compose) return;
|
||
|
|
const merge = player.bagSession?.merge;
|
||
|
|
if (!merge) return;
|
||
|
|
const prev = merge.readCompositionHeroBind(compose);
|
||
|
|
merge.writeCompositionHeroBind(compose, {
|
||
|
|
weapon1Id: prev?.weapon1Id ?? compose.weapon1Id,
|
||
|
|
level: prev?.level ?? compose.level,
|
||
|
|
exp: prev?.exp ?? 0,
|
||
|
|
aspd: this.snapshot(entityId),
|
||
|
|
range: prev?.range ?? [],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private _bindLayer(session: BattleSession, entityId: EntityId, source: string, bonusWan: number, buffId: number): void {
|
||
|
|
this._layers.push({ source, bonusWan, entityId, buffId });
|
||
|
|
this._applyModifier(session, entityId, source, bonusWan);
|
||
|
|
GEvent.Ins.emit(GEvent.UpdateBestPlace);
|
||
|
|
}
|
||
|
|
|
||
|
|
private _hasUsedTarget(buffId: number, targetKey: string): boolean {
|
||
|
|
for (let i = 0; i < this._usedTargets.length; i++) {
|
||
|
|
const row = this._usedTargets[i];
|
||
|
|
if (row.buffId === buffId && row.targetKey === targetKey) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _markTargetUsed(buffId: number, targetKey: string): void {
|
||
|
|
if (this._hasUsedTarget(buffId, targetKey)) return;
|
||
|
|
this._usedTargets.push({ buffId, targetKey });
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _sourceFor(buffId: number, skillSlot: number, usageKey: string): string {
|
||
|
|
return `${ASPD_SOURCE_PREFIX}b${buffId}_s${skillSlot}_${usageKey}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _findCompositionAt(player: Player | null, gridIndex: number): ComposedUnit | null {
|
||
|
|
if (!player) return null;
|
||
|
|
return player.compositions.find(c => c.gridIndices.includes(gridIndex)) ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _isMultiCellComposition(compose: ComposedUnit): boolean {
|
||
|
|
WeaponComposeConfigProvider.instance.load();
|
||
|
|
const provider = WeaponComposeConfigProvider.instance;
|
||
|
|
return !provider.isSingleCellWeapon1(compose.weapon1Id) && compose.gridIndices.length >= 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _applyModifier(session: BattleSession, entityId: EntityId, source: string, bonusWan: number): void {
|
||
|
|
const world = session.world;
|
||
|
|
if (!world.isAlive(entityId)) return;
|
||
|
|
if (!world.getComponent(entityId, CombatComp)) return;
|
||
|
|
|
||
|
|
if (!world.getComponent(entityId, AttributeComp)) {
|
||
|
|
world.addComponent(entityId, new AttributeComp());
|
||
|
|
}
|
||
|
|
|
||
|
|
const attr = world.getComponent(entityId, AttributeComp);
|
||
|
|
if (attr?.modifiers.some(m => m.source === source)) return;
|
||
|
|
|
||
|
|
session.attributes.addModifier(entityId, {
|
||
|
|
attr: 'aspd_add',
|
||
|
|
op: 'Add',
|
||
|
|
value: bonusWan,
|
||
|
|
source,
|
||
|
|
group: source,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 获取局内攻速符宿主 */
|
||
|
|
export function getBattleGameGlobalAttackSpeedHost(): BattleGameGlobalAttackSpeedHost {
|
||
|
|
return BattleGameGlobalAttackSpeedHost.get();
|
||
|
|
}
|