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/mx/module/battle/unit/UnitFactory.ts

114 lines
3.7 KiB

import type { BattleServices } from 'db://assets/mx/module/battle/combat/BattleServices';
import type { Cell } from 'db://assets/mx/module/battle/battlefield/Cell';
import type { EntityId } from 'db://assets/mx/module/battle/ecs/EntityId';
import type { UnitDef } from 'db://assets/mx/module/battle/config/BattleConfigTypes';
import {
TransformComp,
TeamComp,
UnitMetaComp,
HealthComp,
CombatComp,
AbilitySetComp,
TagComp,
MovementComp,
GridOccupancyComp,
} from '../ecs/Components';
import { BattleTeamId } from '../ecs/BattleTeamId';
import { traceCombatDamage } from 'db://assets/mx/tools/CombatDamageTrace';
import { finitePositiveHp } from 'db://assets/mx/tools/FiniteHp';
/**
* 单位工厂:按 UnitDef 创建 ECS 实体并挂到 GridMap / SpatialGrid。
*/
export class UnitFactory {
/**
* @param services 战斗服务
* @param def 单位定义
* @param ownerId 所属/进攻目标玩家 ID
* @param anchor 锚点格
* @param cells 占地格
* @returns 实体 ID
*/
static create(
services: BattleServices,
def: UnitDef,
ownerId: number,
anchor: Cell,
cells: Cell[],
): EntityId {
const world = services.world;
const id = world.spawn();
const teamId = def.team === 'player'
? ownerId
: BattleTeamId.enemyForPlayer(ownerId);
world.addComponent(id, new TransformComp(anchor.row, anchor.col));
world.addComponent(id, new TeamComp(teamId, ownerId));
world.addComponent(id, new UnitMetaComp(def.id, 1));
if (def.components.health) {
const maxHp = finitePositiveHp(def.components.health.maxHp, 1);
world.addComponent(id, new HealthComp(maxHp, maxHp));
traceCombatDamage('unitSpawnHealth', { entity: id, unitDefId: def.id, maxHp });
}
if (def.components.combat) {
const c = def.components.combat;
world.addComponent(id, new CombatComp(
c.attack ?? 10,
c.attackSpeed ?? 1,
c.range ?? 3,
c.critRate ?? 0,
c.critDamage ?? 1.5,
));
}
if (def.components.abilities?.length) {
world.addComponent(id, new AbilitySetComp([...def.components.abilities]));
}
if (def.components.tags) {
world.addComponent(id, new TagComp([...def.components.tags]));
}
if (def.components.movement) {
const w = services.grid.cellToWorld(anchor);
world.addComponent(id, new MovementComp(
def.components.movement.speed,
[],
0,
w.x,
w.y,
false,
));
}
const blocksPath = def.components.occupancy?.blocksPath ?? false;
world.addComponent(id, new GridOccupancyComp(
anchor,
cells.map(cell => ({ ...cell })),
blocksPath,
));
services.grid.occupy(cells, id);
if (blocksPath) {
let walkChanged = false;
for (const cell of cells) {
if (services.grid.isWalkable(cell)) walkChanged = true;
services.grid.setWalkable(cell, false);
}
// 占地挡路改了可走性却未 invalidate 时,距离场会一直用旧障碍
if (walkChanged) services.pathfinder.invalidate();
}
services.spatial.updateEntity(id, anchor);
if (world.hasComponent(id, CombatComp)) {
services.attributes.syncCombatToAttributes(id);
}
if (world.hasComponent(id, AbilitySetComp)) {
services.abilities.bindEntity(id);
}
return id;
}
}