|
|
|
|
import { _decorator, Component, Node } from 'cc';
|
|
|
|
|
import {
|
|
|
|
|
BATTLE_EVENT_ALL,
|
|
|
|
|
BattleEventType,
|
|
|
|
|
TeamComp,
|
|
|
|
|
type BattleEvent,
|
|
|
|
|
type EntityId,
|
|
|
|
|
} from 'db://assets/mx/module/battle';
|
|
|
|
|
import type { Player } from '../../game/Player';
|
|
|
|
|
import type { IBattleRuntimeHandle } from '../types/ProjectBattleTypes';
|
|
|
|
|
import type { MapManager } from '../map/legacy/MapManager';
|
|
|
|
|
import { TextGrid } from '../view/TextGrid';
|
|
|
|
|
import { Skill } from './Skill';
|
|
|
|
|
import { Gong } from '../heros/Gong';
|
|
|
|
|
import { GONG_WEAPON1_ID } from '../heros/HeroActorRes';
|
|
|
|
|
import { getTowerLoadoutService } from '../combat/TowerLoadoutService';
|
|
|
|
|
import { buildSkillPresentationContext } from './SkillPresentationBridge';
|
|
|
|
|
import { isActiveAbilityId, isMonsterAbilityId, isWeaponAbilityId } from '../config/weapon1/Weapon1BattleIds';
|
|
|
|
|
import { findCompositionForEntity } from '../runtime/DeployEntityQuery';
|
|
|
|
|
import { SkillPresentationEnv } from './SkillPresentationEnv';
|
|
|
|
|
import { BattleAudio } from '../audio/BattleAudio';
|
|
|
|
|
import { SkillNameFloater, ULT_SKILL_RELEASE_DELAY_SEC } from './SkillNameFloater';
|
|
|
|
|
import { HeroActorHost } from '../heros/HeroActorHost';
|
|
|
|
|
import { isHeroActorWeapon1 } from '../heros/HeroActorRes';
|
|
|
|
|
import { isElementCombatHeroId } from '../config/BattleHeroTable';
|
|
|
|
|
import { ensureSpecialSkillPresenters } from '../presentation/SpecialSkillPresenters';
|
|
|
|
|
import { getNezhaChargeHost } from '../presentation/NezhaChargePresenter';
|
|
|
|
|
import { getPagodaSmashHost } from '../presentation/PagodaSmashPresenter';
|
|
|
|
|
import { getStaffSmashHost } from '../presentation/StaffSmashPresenter';
|
|
|
|
|
const { ccclass } = _decorator;
|
|
|
|
|
|
|
|
|
|
/** 皮肤未就绪时 PresentationCue 最多延迟帧数(约 2s@30fps) */
|
|
|
|
|
const PRESENTATION_SKILL_BIND_MAX_RETRIES = 60;
|
|
|
|
|
|
|
|
|
|
const CONFUSION_BUFF_ID = 'debuff_8';
|
|
|
|
|
|
|
|
|
|
/** 单个玩家的技能表现控制器(监听战斗事件 → 驱动 HeroActor Skill) */
|
|
|
|
|
@ccclass('SkillController')
|
|
|
|
|
export class SkillController extends Component {
|
|
|
|
|
|
|
|
|
|
private _player: Player | null = null;
|
|
|
|
|
private _match: IBattleRuntimeHandle | null = null;
|
|
|
|
|
private _map: MapManager | null = null;
|
|
|
|
|
private _mapGrids: ReadonlyMap<number, TextGrid> | undefined;
|
|
|
|
|
private _skills = new Map<EntityId, Skill>();
|
|
|
|
|
/** 上次 bind 时的地图格 index,用于检测搬家后 stale Skill 缓存 */
|
|
|
|
|
private _entityGridIndex = new Map<EntityId, number>();
|
|
|
|
|
private _listener: ((e: BattleEvent) => void) | null = null;
|
|
|
|
|
/** 是否已 bind 到 Player/Match;unbind 幂等,避免 UIBattleGame 与 onDestroy 重复解绑 */
|
|
|
|
|
private _bound = false;
|
|
|
|
|
|
|
|
|
|
get player(): Player | null {
|
|
|
|
|
return this._player;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bind(player: Player, match: IBattleRuntimeHandle): void {
|
|
|
|
|
this.unbind();
|
|
|
|
|
ensureSpecialSkillPresenters();
|
|
|
|
|
this._player = player;
|
|
|
|
|
this._match = match;
|
|
|
|
|
player.skillController = this;
|
|
|
|
|
this._listener = (e) => this._onBattleEvent(e);
|
|
|
|
|
match.on(BATTLE_EVENT_ALL, this._listener);
|
|
|
|
|
this._bound = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unbind(): void {
|
|
|
|
|
if (!this._bound) return;
|
|
|
|
|
this._bound = false;
|
|
|
|
|
if (this._player) {
|
|
|
|
|
this._player.skillController = null;
|
|
|
|
|
this._player = null;
|
|
|
|
|
}
|
|
|
|
|
if (this._match && this._listener) {
|
|
|
|
|
this._match.off(BATTLE_EVENT_ALL, this._listener);
|
|
|
|
|
}
|
|
|
|
|
this._listener = null;
|
|
|
|
|
this._match = null;
|
|
|
|
|
this._map = null;
|
|
|
|
|
this._mapGrids = undefined;
|
|
|
|
|
this._skills.clear();
|
|
|
|
|
this._entityGridIndex.clear();
|
|
|
|
|
getNezhaChargeHost().abortAll();
|
|
|
|
|
getPagodaSmashHost().abortAll();
|
|
|
|
|
getStaffSmashHost().abortAll();
|
|
|
|
|
HeroActorHost.instance.destroyAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 地图布局变更前打断指定实体的表现(含 ctx.cancel) */
|
|
|
|
|
abortEntityPresentation(entityId: EntityId): void {
|
|
|
|
|
if (!this._ownsEntity(entityId)) return;
|
|
|
|
|
const skill = this._skills.get(entityId);
|
|
|
|
|
if (skill?.isValid) skill.abortPresentation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registerEntitySkill(entityId: EntityId, skillRoot: Node): void {
|
|
|
|
|
const skill = skillRoot.getComponent(Skill);
|
|
|
|
|
if (!skill?.isValid) return;
|
|
|
|
|
const prev = this._skills.get(entityId);
|
|
|
|
|
this._skills.set(entityId, skill);
|
|
|
|
|
if (prev === skill) return;
|
|
|
|
|
if (!skill.isPresentationBusy() && !skill.visualChannel.isCastOrRestHold) skill.onIdle();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unregisterEntity(entityId: EntityId): void {
|
|
|
|
|
this._skills.delete(entityId);
|
|
|
|
|
HeroActorHost.instance.destroy(entityId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 按 Player.deployedUnits + 地图字块刷新 entity → Skill 映射(不触发词条物化) */
|
|
|
|
|
refreshEntitySkillBindings(map: MapManager, mapGrids?: ReadonlyMap<number, TextGrid>): void {
|
|
|
|
|
this._map = map;
|
|
|
|
|
this._mapGrids = mapGrids;
|
|
|
|
|
const prev = this._skills;
|
|
|
|
|
const prevGridByEntity = this._entityGridIndex;
|
|
|
|
|
this._skills = new Map();
|
|
|
|
|
this._entityGridIndex = new Map();
|
|
|
|
|
const player = this._player;
|
|
|
|
|
if (!player) return;
|
|
|
|
|
|
|
|
|
|
for (const entityId of player.deployedUnits) {
|
|
|
|
|
const gridIndex = this._resolveGridIndex(entityId);
|
|
|
|
|
const prevGrid = prevGridByEntity.get(entityId);
|
|
|
|
|
const gridMoved = prevGrid != null && gridIndex != null && prevGrid !== gridIndex;
|
|
|
|
|
|
|
|
|
|
const old = prev.get(entityId);
|
|
|
|
|
if (gridMoved && old?.isValid) {
|
|
|
|
|
old.abortPresentation();
|
|
|
|
|
} else if (old) {
|
|
|
|
|
this._skills.set(entityId, old);
|
|
|
|
|
}
|
|
|
|
|
this._tryRegisterSkill(entityId);
|
|
|
|
|
if (gridIndex != null) this._entityGridIndex.set(entityId, gridIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 词条物化后刷新表现(遗留钩子;特殊 Presenter 自读 loadout) */
|
|
|
|
|
refreshEntityLoadoutPresentation(entityId: EntityId): void {
|
|
|
|
|
if (!this._ownsEntity(entityId)) return;
|
|
|
|
|
void entityId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 局内 UI 暂停:冻结各塔 Skill 的 Spine / schedule(不 cancel 逻辑) */
|
|
|
|
|
freezeAllPresentations(): void {
|
|
|
|
|
for (const skill of this._skills.values()) {
|
|
|
|
|
if (skill?.isValid) skill.freezePresentation();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 恢复 UI 暂停冻结的表现 */
|
|
|
|
|
unfreezeAllPresentations(): void {
|
|
|
|
|
for (const skill of this._skills.values()) {
|
|
|
|
|
if (skill?.isValid) skill.unfreezePresentation();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 移动/拆塔等打断未 finish 的表现 */
|
|
|
|
|
abortAllPresentations(): void {
|
|
|
|
|
for (const skill of this._skills.values()) {
|
|
|
|
|
if (skill?.isValid) skill.abortPresentation();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @deprecated 使用 refreshEntitySkillBindings */
|
|
|
|
|
syncFromDeploy(
|
|
|
|
|
_deploySync: unknown,
|
|
|
|
|
map: MapManager,
|
|
|
|
|
mapGrids?: ReadonlyMap<number, TextGrid>,
|
|
|
|
|
): void {
|
|
|
|
|
this.refreshEntitySkillBindings(map, mapGrids);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected onDestroy(): void {
|
|
|
|
|
this.unbind();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _ownsEntity(entityId: EntityId): boolean {
|
|
|
|
|
const player = this._player;
|
|
|
|
|
if (!player) return false;
|
|
|
|
|
if (player.deployedUnits.includes(entityId)) return true;
|
|
|
|
|
const team = this._match?.session.world.getComponent(entityId, TeamComp);
|
|
|
|
|
return team?.ownerId === player.playerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBattleEvent(event: BattleEvent): void {
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case BattleEventType.PresentationCue:
|
|
|
|
|
if (isMonsterAbilityId(event.abilityId) || event.params?.skillKind === 'monster') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._ownsEntity(event.caster)) return;
|
|
|
|
|
this._playPresentationCue(event);
|
|
|
|
|
break;
|
|
|
|
|
case BattleEventType.UnitPlaced:
|
|
|
|
|
if (!this._ownsEntity(event.entity)) return;
|
|
|
|
|
break;
|
|
|
|
|
case BattleEventType.UnitSold:
|
|
|
|
|
if (!this._ownsEntity(event.entity)) return;
|
|
|
|
|
this.unregisterEntity(event.entity);
|
|
|
|
|
break;
|
|
|
|
|
case BattleEventType.BuffApplied:
|
|
|
|
|
if (event.buffId === CONFUSION_BUFF_ID) {
|
|
|
|
|
this._setTowerConfusionFx(event.target, true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case BattleEventType.BuffRemoved:
|
|
|
|
|
if (event.buffId === CONFUSION_BUFF_ID) {
|
|
|
|
|
this._setTowerConfusionFx(event.target, false);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case BattleEventType.EntityKilled:
|
|
|
|
|
case BattleEventType.UnitDespawned:
|
|
|
|
|
if (event.type === BattleEventType.EntityKilled) {
|
|
|
|
|
this._setTowerConfusionFx(event.victim, false);
|
|
|
|
|
} else {
|
|
|
|
|
this._setTowerConfusionFx(event.entity, false);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 混乱:塔格挂「混乱效果」,禁攻由 AbilitySystem debuff_8 判定 */
|
|
|
|
|
private _setTowerConfusionFx(entityId: EntityId, on: boolean): void {
|
|
|
|
|
if (!this._ownsEntity(entityId)) return;
|
|
|
|
|
const gridIndex = this._resolveGridIndex(entityId);
|
|
|
|
|
if (gridIndex == null) return;
|
|
|
|
|
this._textGridAt(gridIndex)?.setConfusionFx(on);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _playPresentationCue(event: Extract<BattleEvent, { type: BattleEventType.PresentationCue }>): void {
|
|
|
|
|
const session = this._match?.session;
|
|
|
|
|
if (!session) return;
|
|
|
|
|
|
|
|
|
|
const ctx = buildSkillPresentationContext(session, {
|
|
|
|
|
caster: event.caster,
|
|
|
|
|
abilityId: event.abilityId,
|
|
|
|
|
cueId: event.cueId,
|
|
|
|
|
castInstanceId: event.castInstanceId,
|
|
|
|
|
targetEntityIds: event.targetEntityIds,
|
|
|
|
|
params: event.params,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._dispatchPresentationCue(event, ctx, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* HeroActor 未就绪时延迟重试,避免首刀 finish() 丢伤害。
|
|
|
|
|
*/
|
|
|
|
|
private _dispatchPresentationCue(
|
|
|
|
|
event: Extract<BattleEvent, { type: BattleEventType.PresentationCue }>,
|
|
|
|
|
ctx: ReturnType<typeof buildSkillPresentationContext>,
|
|
|
|
|
retried: number,
|
|
|
|
|
): void {
|
|
|
|
|
if (!ctx.isAlive) {
|
|
|
|
|
ctx.cancel('caster_dead');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const skill = this._tryRegisterSkill(event.caster);
|
|
|
|
|
if (!skill) {
|
|
|
|
|
if (retried < PRESENTATION_SKILL_BIND_MAX_RETRIES && this._shouldRetrySkillBind(event.caster)) {
|
|
|
|
|
SkillPresentationEnv.scheduleBattle(0, () => {
|
|
|
|
|
this._dispatchPresentationCue(event, ctx, retried + 1);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 无 HeroActor(桃等)或重试耗尽:先 hit 再 finish,避免丢 deferred 产桃/伤害
|
|
|
|
|
this._drainThenFinish(ctx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cue = event.cueId.toLowerCase();
|
|
|
|
|
const isWeaponSk = isWeaponAbilityId(event.abilityId) || cue === 'weaponskill';
|
|
|
|
|
const isUlt = !isWeaponSk && (
|
|
|
|
|
isActiveAbilityId(event.abilityId) || cue === 'skill' || cue.includes('ult')
|
|
|
|
|
);
|
|
|
|
|
const isComboBurst = Number(event.params?.comboAttack) > 0
|
|
|
|
|
|| (ctx.flow.vars.get('__comboAttack') ?? 0) > 0;
|
|
|
|
|
const extraHead = this._extraSkillHead(event.params);
|
|
|
|
|
const skipUltPresentationDelay = isComboBurst || extraHead === 1;
|
|
|
|
|
if (isWeaponSk) {
|
|
|
|
|
const fireWeapon = () => {
|
|
|
|
|
if (!ctx.isAlive) {
|
|
|
|
|
ctx.cancel('caster_dead');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!skill.isValid) {
|
|
|
|
|
this._drainThenFinish(ctx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
BattleAudio.playWeaponSkill(event.caster, true);
|
|
|
|
|
skill.onWeaponSkill(ctx);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[SkillController] onWeaponSkill 异常,释放施法锁', event.abilityId, String(err));
|
|
|
|
|
if (ctx.isAlive) ctx.finish();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (skipUltPresentationDelay) fireWeapon();
|
|
|
|
|
else SkillPresentationEnv.scheduleBattle(ULT_SKILL_RELEASE_DELAY_SEC, fireWeapon);
|
|
|
|
|
} else if (isUlt) {
|
|
|
|
|
const fireUlt = () => {
|
|
|
|
|
if (!ctx.isAlive) {
|
|
|
|
|
ctx.cancel('caster_dead');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!skill.isValid) {
|
|
|
|
|
this._drainThenFinish(ctx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
BattleAudio.playWeaponSkill(event.caster);
|
|
|
|
|
skill.onSkill(ctx);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[SkillController] onSkill 异常,释放施法锁', event.abilityId, String(err));
|
|
|
|
|
if (ctx.isAlive) ctx.finish();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (skipUltPresentationDelay) {
|
|
|
|
|
SkillNameFloater.play(event.caster, event.abilityId);
|
|
|
|
|
fireUlt();
|
|
|
|
|
} else {
|
|
|
|
|
SkillNameFloater.play(event.caster, event.abilityId);
|
|
|
|
|
SkillPresentationEnv.scheduleBattle(ULT_SKILL_RELEASE_DELAY_SEC, fireUlt);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this._fireNormAttack(event, ctx, skill);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Skill.skill 头类型。
|
|
|
|
|
* @param params PlayPresentation 参数
|
|
|
|
|
*/
|
|
|
|
|
private _extraSkillHead(params: Record<string, unknown> | undefined): number {
|
|
|
|
|
const raw = String(params?.extraSkill ?? params?.skillRaw ?? '').trim();
|
|
|
|
|
return Math.floor(Number(raw.split(',')[0]) || 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 先结算剩余 deferred,再解锁施法。
|
|
|
|
|
* @param ctx 表现上下文
|
|
|
|
|
*/
|
|
|
|
|
private _drainThenFinish(ctx: ReturnType<typeof buildSkillPresentationContext>): void {
|
|
|
|
|
while (ctx.isAlive && ctx.hit()) {
|
|
|
|
|
/* drain deferred slices */
|
|
|
|
|
}
|
|
|
|
|
ctx.finish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 普攻 cue:异常必须 finish,避免施法锁卡住后续 OnTick */
|
|
|
|
|
private _fireNormAttack(event: Extract<BattleEvent, { type: BattleEventType.PresentationCue }>, ctx: ReturnType<typeof buildSkillPresentationContext>, skill: Skill): void {
|
|
|
|
|
if (!ctx.isAlive) {
|
|
|
|
|
ctx.cancel('caster_dead');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!skill.isValid) {
|
|
|
|
|
this._drainThenFinish(ctx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const heroId = getTowerLoadoutService().getLoadout(event.caster)?.weapon1Id;
|
|
|
|
|
if (heroId !== GONG_WEAPON1_ID && !(skill instanceof Gong)) {
|
|
|
|
|
BattleAudio.playWeaponAttack(event.caster);
|
|
|
|
|
}
|
|
|
|
|
skill.onAttack(ctx);
|
|
|
|
|
this._syncPartnerAttackVisual(event.caster, skill);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[SkillController] onAttack 异常,释放施法锁', event.abilityId, String(err));
|
|
|
|
|
if (ctx.isAlive) ctx.finish();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 组合体伴侣字块同步攻击动画(HeroActor 由子类 playPartnerAttackVisual 处理) */
|
|
|
|
|
private _syncPartnerAttackVisual(entityId: EntityId, mainSkill: Skill): void {
|
|
|
|
|
if (HeroActorHost.instance.getActor(entityId) === mainSkill) {
|
|
|
|
|
const timeScale = Math.max(0.01, SkillPresentationEnv.getFinalAttackSpeed(entityId));
|
|
|
|
|
mainSkill.playPartnerAttackVisual(timeScale);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 地图格上的字块:先查绑定表,缺则从格节点现找(AI 对账晚于 refresh 时表会空)。
|
|
|
|
|
* @param gridIndex 地图格
|
|
|
|
|
*/
|
|
|
|
|
private _textGridAt(gridIndex: number): TextGrid | null {
|
|
|
|
|
const tracked = this._mapGrids?.get(gridIndex);
|
|
|
|
|
if (tracked?.isValid) return tracked;
|
|
|
|
|
const parent = this._map?.getMapGridByIndex(gridIndex)?.node;
|
|
|
|
|
if (!parent?.isValid) return null;
|
|
|
|
|
const live = parent.getComponentInChildren(TextGrid);
|
|
|
|
|
return live?.isValid ? live : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _resolveGridIndex(entityId: EntityId): number | null {
|
|
|
|
|
const session = this._match?.session;
|
|
|
|
|
const map = this._map;
|
|
|
|
|
const player = this._player;
|
|
|
|
|
if (!session || !map || !player) return null;
|
|
|
|
|
const comp = findCompositionForEntity(entityId, player, session, map);
|
|
|
|
|
if (!comp?.gridIndices.length) return null;
|
|
|
|
|
return SkillPresentationEnv.leftmostGridIndex(comp.gridIndices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 仅 HeroActor 未就绪时重试绑定;桃等无 Actor 塔立即结算。
|
|
|
|
|
* @param entityId 施法实体
|
|
|
|
|
*/
|
|
|
|
|
private _shouldRetrySkillBind(entityId: EntityId): boolean {
|
|
|
|
|
const loadout = getTowerLoadoutService().getLoadout(entityId);
|
|
|
|
|
if (loadout && !isHeroActorWeapon1(loadout.weapon1Id) && !isElementCombatHeroId(loadout.weapon1Id)) return false;
|
|
|
|
|
const session = this._match?.session;
|
|
|
|
|
const player = this._player;
|
|
|
|
|
const map = this._map;
|
|
|
|
|
if (!session || !player || !map) return true;
|
|
|
|
|
const comp = findCompositionForEntity(entityId, player, session, map);
|
|
|
|
|
if (comp && !isHeroActorWeapon1(comp.weapon1Id) && !isElementCombatHeroId(comp.weapon1Id)) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 绑定 HeroActor Skill;全部 Weapon1 英雄已迁新方案,不再从 grid 皮肤找 Skill* */
|
|
|
|
|
private _tryRegisterSkill(entityId: EntityId): Skill | null {
|
|
|
|
|
const actor = HeroActorHost.instance.getActor(entityId);
|
|
|
|
|
if (actor?.isValid) {
|
|
|
|
|
this.registerEntitySkill(entityId, actor.node);
|
|
|
|
|
return this._skills.get(entityId) ?? actor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const session = this._match?.session;
|
|
|
|
|
const player = this._player;
|
|
|
|
|
const map = this._map;
|
|
|
|
|
if (!session || !player || !map) return null;
|
|
|
|
|
|
|
|
|
|
const comp = findCompositionForEntity(entityId, player, session, map);
|
|
|
|
|
if (!comp) return null;
|
|
|
|
|
if (isElementCombatHeroId(comp.weapon1Id)) {
|
|
|
|
|
const gridIndex = this._resolveGridIndex(entityId);
|
|
|
|
|
const grid = gridIndex != null ? this._textGridAt(gridIndex) : null;
|
|
|
|
|
const skill = grid?.ensureNumberSkill(comp.weapon1Id, entityId) ?? null;
|
|
|
|
|
if (!skill?.isValid) return null;
|
|
|
|
|
this.registerEntitySkill(entityId, skill.node);
|
|
|
|
|
return this._skills.get(entityId) ?? skill;
|
|
|
|
|
}
|
|
|
|
|
if (!isHeroActorWeapon1(comp.weapon1Id)) return null;
|
|
|
|
|
|
|
|
|
|
HeroActorHost.instance.syncFromMap(entityId, comp, map, !player.isLocal);
|
|
|
|
|
const created = HeroActorHost.instance.getActor(entityId);
|
|
|
|
|
if (!created?.isValid) return null;
|
|
|
|
|
|
|
|
|
|
this.registerEntitySkill(entityId, created.node);
|
|
|
|
|
return this._skills.get(entityId) ?? created;
|
|
|
|
|
}
|
|
|
|
|
}
|