107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
import { System } from '../core/System.ts';
|
|
import { Events } from '../core/EventBus.ts';
|
|
import { Palette } from '../core/Palette.ts';
|
|
import { SystemName, ComponentType } from '../core/Constants.ts';
|
|
import type { Entity } from '../core/Entity.ts';
|
|
import type { Health } from '../components/Health.ts';
|
|
import type { Position } from '../components/Position.ts';
|
|
import type { Velocity } from '../components/Velocity.ts';
|
|
import type { VFXSystem } from './VFXSystem.ts';
|
|
|
|
/**
|
|
* System responsible for managing projectile movement, range limits, lifetimes, and collisions.
|
|
*/
|
|
export class ProjectileSystem extends System {
|
|
constructor() {
|
|
super(SystemName.PROJECTILE);
|
|
this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY];
|
|
this.priority = 18;
|
|
}
|
|
|
|
/**
|
|
* Process logic for all projectiles, checking for range, lifetime, and target collisions.
|
|
* @param deltaTime - Time elapsed since last frame in seconds
|
|
* @param entities - Entities matching system requirements
|
|
*/
|
|
process(deltaTime: number, entities: Entity[]): void {
|
|
entities.forEach((entity) => {
|
|
const health = entity.getComponent<Health>(ComponentType.HEALTH);
|
|
if (!health || !health.isProjectile) return;
|
|
|
|
const position = entity.getComponent<Position>(ComponentType.POSITION);
|
|
if (!position) return;
|
|
|
|
if (
|
|
entity.startX !== undefined &&
|
|
entity.startY !== undefined &&
|
|
entity.maxRange !== undefined
|
|
) {
|
|
const dx = position.x - entity.startX;
|
|
const dy = position.y - entity.startY;
|
|
const distanceTraveled = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distanceTraveled >= entity.maxRange) {
|
|
this.engine.removeEntity(entity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (entity.lifetime !== undefined) {
|
|
entity.lifetime -= deltaTime;
|
|
if (entity.lifetime <= 0) {
|
|
this.engine.removeEntity(entity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const allEntities = this.engine.getEntities();
|
|
allEntities.forEach((target) => {
|
|
if (target.id === entity.owner) return;
|
|
if (target.id === entity.id) return;
|
|
if (!target.hasComponent(ComponentType.HEALTH)) return;
|
|
const targetHealth = target.getComponent<Health>(ComponentType.HEALTH);
|
|
if (targetHealth && targetHealth.isProjectile) return;
|
|
|
|
const targetPos = target.getComponent<Position>(ComponentType.POSITION);
|
|
if (!targetPos) return;
|
|
|
|
const dx = targetPos.x - position.x;
|
|
const dy = targetPos.y - position.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance < 8) {
|
|
const targetHealthComp = target.getComponent<Health>(ComponentType.HEALTH);
|
|
const damage = entity.damage || 10;
|
|
if (targetHealthComp) {
|
|
targetHealthComp.takeDamage(damage);
|
|
|
|
const vfxSystem = this.engine.systems.find((s) => s.name === SystemName.VFX) as
|
|
| VFXSystem
|
|
| undefined;
|
|
const velocity = entity.getComponent<Velocity>(ComponentType.VELOCITY);
|
|
if (vfxSystem) {
|
|
const angle = velocity ? Math.atan2(velocity.vy, velocity.vx) : null;
|
|
vfxSystem.createImpact(position.x, position.y, Palette.CYAN, angle);
|
|
}
|
|
|
|
if (targetHealthComp.isDead()) {
|
|
this.engine.emit(Events.ENTITY_DIED, { entity: target });
|
|
}
|
|
}
|
|
|
|
this.engine.removeEntity(entity);
|
|
}
|
|
});
|
|
|
|
const canvas = this.engine.canvas;
|
|
if (
|
|
position.x < 0 ||
|
|
position.x > canvas.width ||
|
|
position.y < 0 ||
|
|
position.y > canvas.height
|
|
) {
|
|
this.engine.removeEntity(entity);
|
|
}
|
|
});
|
|
}
|
|
}
|