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.
114 lines
3.7 KiB
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;
|
|
}
|
|
}
|
|
|