170 lines
6.1 KiB
TypeScript
170 lines
6.1 KiB
TypeScript
import { System } from '../core/System.ts';
|
|
import { GameConfig } from '../GameConfig.ts';
|
|
import { Events } from '../core/EventBus.ts';
|
|
import { SystemName, ComponentType } from '../core/Constants.ts';
|
|
import type { Entity } from '../core/Entity.ts';
|
|
import type { Position } from '../components/Position.ts';
|
|
import type { Evolution } from '../components/Evolution.ts';
|
|
import type { Skills } from '../components/Skills.ts';
|
|
import type { Stats } from '../components/Stats.ts';
|
|
import type { SkillProgress } from '../components/SkillProgress.ts';
|
|
import type { Absorbable } from '../components/Absorbable.ts';
|
|
import type { Health } from '../components/Health.ts';
|
|
import type { PlayerControllerSystem } from './PlayerControllerSystem.ts';
|
|
import type { VFXSystem } from './VFXSystem.ts';
|
|
|
|
/**
|
|
* System responsible for identifying dead absorbable entities near the player and processing absorption.
|
|
*/
|
|
export class AbsorptionSystem extends System {
|
|
constructor() {
|
|
super(SystemName.ABSORPTION);
|
|
this.requiredComponents = [ComponentType.POSITION, ComponentType.ABSORBABLE];
|
|
this.priority = 25;
|
|
}
|
|
|
|
/**
|
|
* Check for absorbable entities within range of the player and initiate absorption if applicable.
|
|
* @param _deltaTime - Time elapsed since last frame
|
|
* @param _entities - Matching entities (not used, uses raw engine entities)
|
|
*/
|
|
process(_deltaTime: number, _entities: Entity[]): void {
|
|
const playerController = this.engine.systems.find(
|
|
(s) => s.name === SystemName.PLAYER_CONTROLLER
|
|
) as PlayerControllerSystem | undefined;
|
|
const player = playerController ? playerController.getPlayerEntity() : null;
|
|
|
|
if (!player) return;
|
|
|
|
const playerPos = player.getComponent<Position>(ComponentType.POSITION);
|
|
const playerEvolution = player.getComponent<Evolution>(ComponentType.EVOLUTION);
|
|
const playerSkills = player.getComponent<Skills>(ComponentType.SKILLS);
|
|
const playerStats = player.getComponent<Stats>(ComponentType.STATS);
|
|
const skillProgress = player.getComponent<SkillProgress>(ComponentType.SKILL_PROGRESS);
|
|
|
|
if (!playerPos || !playerEvolution) return;
|
|
|
|
const allEntities = this.engine.entities;
|
|
const config = GameConfig.Absorption;
|
|
|
|
allEntities.forEach((entity) => {
|
|
if (entity === player) return;
|
|
if (!entity.active) {
|
|
const health = entity.getComponent<Health>(ComponentType.HEALTH);
|
|
const absorbable = entity.getComponent<Absorbable>(ComponentType.ABSORBABLE);
|
|
if (!health || !health.isDead() || !absorbable || absorbable.absorbed) {
|
|
return;
|
|
}
|
|
}
|
|
if (!entity.hasComponent(ComponentType.ABSORBABLE)) return;
|
|
if (!entity.hasComponent(ComponentType.HEALTH)) return;
|
|
|
|
const absorbable = entity.getComponent<Absorbable>(ComponentType.ABSORBABLE);
|
|
const health = entity.getComponent<Health>(ComponentType.HEALTH);
|
|
const entityPos = entity.getComponent<Position>(ComponentType.POSITION);
|
|
|
|
if (!entityPos) return;
|
|
|
|
if (health && health.isDead() && absorbable && !absorbable.absorbed) {
|
|
const dx = playerPos.x - entityPos.x;
|
|
const dy = playerPos.y - entityPos.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance <= config.range) {
|
|
this.absorbEntity(
|
|
player,
|
|
entity,
|
|
absorbable,
|
|
playerEvolution,
|
|
playerSkills,
|
|
playerStats,
|
|
skillProgress
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Process the absorption of an entity by the player.
|
|
*/
|
|
absorbEntity(
|
|
player: Entity,
|
|
entity: Entity,
|
|
absorbable: Absorbable,
|
|
evolution: Evolution,
|
|
skills: Skills | undefined,
|
|
stats: Stats | undefined,
|
|
skillProgress: SkillProgress | undefined
|
|
): void {
|
|
if (absorbable.absorbed) return;
|
|
|
|
absorbable.absorbed = true;
|
|
const entityPos = entity.getComponent<Position>(ComponentType.POSITION);
|
|
const health = player.getComponent<Health>(ComponentType.HEALTH);
|
|
const config = GameConfig.Absorption;
|
|
|
|
evolution.addEvolution(
|
|
absorbable.evolutionData.human,
|
|
absorbable.evolutionData.beast,
|
|
absorbable.evolutionData.slime
|
|
);
|
|
|
|
if (skillProgress && absorbable.skillsGranted && absorbable.skillsGranted.length > 0) {
|
|
absorbable.skillsGranted.forEach((skill) => {
|
|
const currentProgress = skillProgress.addSkillProgress(skill.id);
|
|
const required = skillProgress.requiredAbsorptions;
|
|
|
|
if (currentProgress >= required && skills && !skills.hasSkill(skill.id)) {
|
|
skills.addSkill(skill.id, false);
|
|
this.engine.emit(Events.SKILL_LEARNED, { id: skill.id });
|
|
console.log(`Learned skill: ${skill.id}!`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (health) {
|
|
const healPercent =
|
|
config.healPercentMin + Math.random() * (config.healPercentMax - config.healPercentMin);
|
|
const healAmount = health.maxHp * healPercent;
|
|
health.heal(healAmount);
|
|
}
|
|
|
|
if (absorbable.shouldMutate() && stats) {
|
|
this.applyMutation(stats);
|
|
evolution.checkMutations(stats, this.engine);
|
|
}
|
|
|
|
if (entityPos) {
|
|
const vfxSystem = this.engine.systems.find((s) => s.name === SystemName.VFX) as
|
|
| VFXSystem
|
|
| undefined;
|
|
if (vfxSystem) {
|
|
vfxSystem.createAbsorption(entityPos.x, entityPos.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply a random stat mutation (positive or negative) to an entity's stats.
|
|
* @param stats - The stats component to mutate
|
|
*/
|
|
applyMutation(stats: Stats): void {
|
|
type StatName = 'strength' | 'agility' | 'intelligence' | 'constitution' | 'perception';
|
|
const mutations: Array<{ stat: StatName; amount: number }> = [
|
|
{ stat: 'strength', amount: 5 },
|
|
{ stat: 'agility', amount: 5 },
|
|
{ stat: 'intelligence', amount: 5 },
|
|
{ stat: 'constitution', amount: 5 },
|
|
{ stat: 'perception', amount: 5 },
|
|
];
|
|
|
|
const mutation = mutations[Math.floor(Math.random() * mutations.length)];
|
|
stats[mutation.stat] += mutation.amount;
|
|
|
|
if (Math.random() < 0.3) {
|
|
const negativeStat = mutations[Math.floor(Math.random() * mutations.length)];
|
|
stats[negativeStat.stat] = Math.max(1, stats[negativeStat.stat] - 2);
|
|
}
|
|
}
|
|
}
|