diff --git a/src/core/Constants.js b/src/core/Constants.js new file mode 100644 index 0000000..3ef411e --- /dev/null +++ b/src/core/Constants.js @@ -0,0 +1,62 @@ +/** + * Centralized Constants and Enums + */ + +export const GameState = { + START: 'start', + PLAYING: 'playing', + PAUSED: 'paused', + GAME_OVER: 'gameOver' +}; + +export const ComponentType = { + POSITION: 'Position', + VELOCITY: 'Velocity', + SPRITE: 'Sprite', + HEALTH: 'Health', + COMBAT: 'Combat', + AI: 'AI', + EVOLUTION: 'Evolution', + STATS: 'Stats', + SKILLS: 'Skills', + SKILL_PROGRESS: 'SkillProgress', + ABSORBABLE: 'Absorbable', + STEALTH: 'Stealth' +}; + +export const EntityType = { + SLIME: 'slime', + HUMANOID: 'humanoid', + BEAST: 'beast', + ELEMENTAL: 'elemental', + PROJECTILE: 'projectile' +}; + +export const AnimationState = { + IDLE: 'idle', + WALK: 'walk' +}; + +export const VFXType = { + IMPACT: 'impact', + ABSORPTION: 'absorption' +}; + +export const SystemName = { + MENU: 'MenuSystem', + UI: 'UISystem', + PLAYER_CONTROLLER: 'PlayerControllerSystem', + ABSORPTION: 'AbsorptionSystem', + COMBAT: 'CombatSystem', + PROJECTILE: 'ProjectileSystem', + VFX: 'VFXSystem', + MOVEMENT: 'MovementSystem', + AI: 'AISystem', + DEATH: 'DeathSystem', + RENDER: 'RenderSystem', + INPUT: 'InputSystem', + SKILL_EFFECT: 'SkillEffectSystem', + SKILL: 'SkillSystem', + STEALTH: 'StealthSystem', + HEALTH_REGEN: 'HealthRegenerationSystem' +}; diff --git a/src/core/Engine.js b/src/core/Engine.js index d5a68a3..834b7cc 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -2,6 +2,7 @@ import { System } from './System.js'; import { Entity } from './Entity.js'; import { EventBus } from './EventBus.js'; import { LevelLoader } from './LevelLoader.js'; +import { GameState, SystemName } from './Constants.js'; /** * Main game engine - manages ECS, game loop, and systems @@ -48,8 +49,8 @@ export class Engine { } /** - * Emit an event locally - */ + * Emit an event locally + */ emit(event, data) { this.events.emit(event, data); } @@ -118,20 +119,21 @@ export class Engine { this.deltaTime = Math.min(this.deltaTime, 0.1); // Update all systems - const menuSystem = this.systems.find(s => s.name === 'MenuSystem'); - const gameState = menuSystem ? menuSystem.getGameState() : 'playing'; - const isPaused = gameState === 'paused' || gameState === 'start' || gameState === 'gameOver'; + const menuSystem = this.systems.find(s => s.name === SystemName.MENU); + const gameState = menuSystem ? menuSystem.getGameState() : GameState.PLAYING; + const isPaused = [GameState.PAUSED, GameState.START, GameState.GAME_OVER].includes(gameState); + const unskippedSystems = [SystemName.MENU, SystemName.UI, SystemName.RENDER]; this.systems.forEach(system => { // Skip game systems if paused/start menu (but allow MenuSystem, UISystem, and RenderSystem) - if (isPaused && system.name !== 'MenuSystem' && system.name !== 'UISystem' && system.name !== 'RenderSystem') { + if (isPaused && !unskippedSystems.includes(system.name)) { return; } system.update(this.deltaTime, this.entities); }); // Update input system's previous states at end of frame - const inputSystem = this.systems.find(s => s.name === 'InputSystem'); + const inputSystem = this.systems.find(s => s.name === SystemName.INPUT); if (inputSystem && inputSystem.updatePreviousStates) { inputSystem.updatePreviousStates(); } diff --git a/src/core/SpriteLibrary.js b/src/core/SpriteLibrary.js index 748b788..c3d2131 100644 --- a/src/core/SpriteLibrary.js +++ b/src/core/SpriteLibrary.js @@ -1,3 +1,5 @@ +import { EntityType, AnimationState } from './Constants.js'; + /** * Sprite Library defining pixel art grids as 2D arrays. * 0: Transparent @@ -7,8 +9,8 @@ */ export const SpriteLibrary = { // 8x8 Slime - Bottom-heavy blob - slime: { - idle: [ + [EntityType.SLIME]: { + [AnimationState.IDLE]: [ [ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], // Top @@ -30,7 +32,7 @@ export const SpriteLibrary = { [1, 1, 1, 1, 1, 1, 1, 1] // Squashed base ] ], - walk: [ + [AnimationState.WALK]: [ [ [0, 0, 1, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0], @@ -55,8 +57,8 @@ export const SpriteLibrary = { }, // 8x8 Humanoid - Simple Walk Cycle - humanoid: { - idle: [ + [EntityType.HUMANOID]: { + [AnimationState.IDLE]: [ [ [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 2, 1, 1, 2, 0, 0], @@ -68,7 +70,7 @@ export const SpriteLibrary = { [0, 0, 1, 0, 0, 1, 0, 0] ] ], - walk: [ + [AnimationState.WALK]: [ [ [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 2, 1, 1, 2, 0, 0], @@ -93,8 +95,8 @@ export const SpriteLibrary = { }, // 8x8 Beast - Bounding Cycle - beast: { - idle: [ + [EntityType.BEAST]: { + [AnimationState.IDLE]: [ [ [0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 1], @@ -106,7 +108,7 @@ export const SpriteLibrary = { [0, 1, 0, 0, 0, 0, 1, 0] ] ], - walk: [ + [AnimationState.WALK]: [ [ [1, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 1, 1, 1, 1, 0], @@ -131,8 +133,8 @@ export const SpriteLibrary = { }, // 8x8 Elemental - Floating Pulse - elemental: { - idle: [ + [EntityType.ELEMENTAL]: { + [AnimationState.IDLE]: [ [ [0, 0, 2, 1, 1, 2, 0, 0], [0, 1, 1, 2, 2, 1, 1, 0], @@ -156,8 +158,8 @@ export const SpriteLibrary = { ] }, - projectile: { - idle: [ + [EntityType.PROJECTILE]: { + [AnimationState.IDLE]: [ [ [1, 1], [1, 1] diff --git a/src/main.js b/src/main.js index db69006..748adc1 100644 --- a/src/main.js +++ b/src/main.js @@ -31,6 +31,9 @@ import { AI } from './components/AI.js'; import { Absorbable } from './components/Absorbable.js'; import { SkillProgress } from './components/SkillProgress.js'; +// Constants +import { EntityType, ComponentType } from './core/Constants.js'; + // Initialize game const canvas = document.getElementById('game-canvas'); if (!canvas) { @@ -60,7 +63,7 @@ if (!canvas) { const player = engine.createEntity(); player.addComponent(new Position(160, 120)); // Center of 320x240 player.addComponent(new Velocity(0, 0, 100)); // Slower speed for small resolution - player.addComponent(new Sprite('#00ff96', 14, 14, 'slime')); // 14x14 pixel sprite + player.addComponent(new Sprite('#00ff96', 14, 14, EntityType.SLIME)); // 14x14 pixel sprite player.addComponent(new Health(100)); player.addComponent(new Stats()); player.addComponent(new Evolution()); @@ -84,25 +87,25 @@ if (!canvas) { let color, evolutionData, skills; switch (type) { - case 'humanoid': - color = '#ff5555'; // Humanoid red - evolutionData = { human: 10, beast: 0, slime: -2 }; - skills = ['fire_breath']; - break; - case 'beast': - color = '#ffaa00'; // Beast orange - evolutionData = { human: 0, beast: 10, slime: -2 }; - skills = ['pounce']; - break; - case 'elemental': - color = '#00bfff'; - evolutionData = { human: 3, beast: 3, slime: 8 }; - skills = ['fire_breath']; - break; - default: - color = '#888888'; - evolutionData = { human: 2, beast: 2, slime: 2 }; - skills = []; + case EntityType.HUMANOID: + color = '#ff5555'; // Humanoid red + evolutionData = { human: 10, beast: 0, slime: -2 }; + skills = ['fire_breath']; + break; + case EntityType.BEAST: + color = '#ffaa00'; // Beast orange + evolutionData = { human: 0, beast: 10, slime: -2 }; + skills = ['pounce']; + break; + case EntityType.ELEMENTAL: + color = '#00bfff'; + evolutionData = { human: 3, beast: 3, slime: 8 }; + skills = ['fire_breath']; + break; + default: + color = '#888888'; + evolutionData = { human: 2, beast: 2, slime: 2 }; + skills = []; } creature.addComponent(new Sprite(color, 10, 10, type)); @@ -123,7 +126,7 @@ if (!canvas) { for (let i = 0; i < 8; i++) { const x = 20 + Math.random() * 280; // Fit in 320 width const y = 20 + Math.random() * 200; // Fit in 240 height - const types = ['humanoid', 'beast', 'elemental']; + const types = [EntityType.HUMANOID, EntityType.BEAST, EntityType.ELEMENTAL]; const type = types[Math.floor(Math.random() * types.length)]; createCreature(engine, x, y, type); } @@ -131,13 +134,13 @@ if (!canvas) { // Spawn new creatures periodically setInterval(() => { const existingCreatures = engine.getEntities().filter(e => - e.hasComponent('AI') && e !== player + e.hasComponent(ComponentType.AI) && e !== player ); if (existingCreatures.length < 10) { const x = 20 + Math.random() * 280; const y = 20 + Math.random() * 200; - const types = ['humanoid', 'beast', 'elemental']; + const types = [EntityType.HUMANOID, EntityType.BEAST, EntityType.ELEMENTAL]; const type = types[Math.floor(Math.random() * types.length)]; createCreature(engine, x, y, type); } diff --git a/src/systems/AISystem.js b/src/systems/AISystem.js index 6e49588..17bb607 100644 --- a/src/systems/AISystem.js +++ b/src/systems/AISystem.js @@ -1,24 +1,25 @@ import { System } from '../core/System.js'; import { GameConfig } from '../GameConfig.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class AISystem extends System { constructor() { - super('AISystem'); - this.requiredComponents = ['Position', 'Velocity', 'AI']; + super(SystemName.AI); + this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY, ComponentType.AI]; this.priority = 15; } process(deltaTime, entities) { - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const player = playerController ? playerController.getPlayerEntity() : null; - const playerPos = player?.getComponent('Position'); + const playerPos = player?.getComponent(ComponentType.POSITION); const config = GameConfig.AI; entities.forEach(entity => { - const health = entity.getComponent('Health'); - const ai = entity.getComponent('AI'); - const position = entity.getComponent('Position'); - const velocity = entity.getComponent('Velocity'); + const health = entity.getComponent(ComponentType.HEALTH); + const ai = entity.getComponent(ComponentType.AI); + const position = entity.getComponent(ComponentType.POSITION); + const velocity = entity.getComponent(ComponentType.VELOCITY); if (!ai || !position || !velocity) return; @@ -39,7 +40,7 @@ export class AISystem extends System { const distance = Math.sqrt(dx * dx + dy * dy); // Update awareness based on distance and player stealth - const playerStealth = player?.getComponent('Stealth'); + const playerStealth = player?.getComponent(ComponentType.STEALTH); const playerVisibility = playerStealth ? playerStealth.visibility : 1.0; if (distance < ai.alertRadius) { @@ -50,10 +51,10 @@ export class AISystem extends System { } // Biological Reputation Logic - const playerEvolution = player?.getComponent('Evolution'); + const playerEvolution = player?.getComponent(ComponentType.EVOLUTION); const playerForm = playerEvolution ? playerEvolution.getDominantForm() : 'slime'; - const entityType = entity.getComponent('Sprite')?.color === '#ffaa00' ? 'beast' : - entity.getComponent('Sprite')?.color === '#ff5555' ? 'humanoid' : 'other'; + const entityType = entity.getComponent(ComponentType.SPRITE)?.color === '#ffaa00' ? 'beast' : + entity.getComponent(ComponentType.SPRITE)?.color === '#ff5555' ? 'humanoid' : 'other'; // Check if player is "one of us" or "too scary" let isPassive = false; @@ -64,8 +65,8 @@ export class AISystem extends System { if (ai.awareness < config.passiveAwarenessThreshold) isPassive = true; } else if (entityType === 'beast' && playerForm === 'beast') { // Beasts might flee from a dominant beast player - const playerStats = player?.getComponent('Stats'); - const entityStats = entity.getComponent('Stats'); + const playerStats = player?.getComponent(ComponentType.STATS); + const entityStats = entity.getComponent(ComponentType.STATS); if (playerStats && entityStats && playerStats.level > entityStats.level) { shouldFlee = true; } @@ -85,7 +86,7 @@ export class AISystem extends System { } else if (ai.awareness > config.detectionAwarenessThreshold && distance < ai.chaseRadius) { if (ai.behaviorType !== 'flee') { // Check if in attack range - if so, use combat behavior - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat && distance <= combat.attackRange) { ai.setBehavior('combat'); ai.state = 'combat'; @@ -103,7 +104,7 @@ export class AISystem extends System { } } else if (ai.behaviorType === 'chase') { // Update from chase to combat if in range - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat && distance <= combat.attackRange) { ai.setBehavior('combat'); ai.state = 'combat'; @@ -151,7 +152,7 @@ export class AISystem extends System { const distance = Math.sqrt(dx * dx + dy * dy); // Check if we should switch to combat - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat && distance <= combat.attackRange) { ai.setBehavior('combat'); ai.state = 'combat'; @@ -193,7 +194,7 @@ export class AISystem extends System { const dy = targetPos.y - position.y; const distance = Math.sqrt(dx * dx + dy * dy); - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat && distance > combat.attackRange) { // Move closer if out of range const speed = ai.wanderSpeed; diff --git a/src/systems/AbsorptionSystem.js b/src/systems/AbsorptionSystem.js index 750fd42..74bc0d0 100644 --- a/src/systems/AbsorptionSystem.js +++ b/src/systems/AbsorptionSystem.js @@ -1,25 +1,26 @@ import { System } from '../core/System.js'; import { GameConfig } from '../GameConfig.js'; import { Events } from '../core/EventBus.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class AbsorptionSystem extends System { constructor() { - super('AbsorptionSystem'); - this.requiredComponents = ['Position', 'Absorbable']; + super(SystemName.ABSORPTION); + this.requiredComponents = [ComponentType.POSITION, ComponentType.ABSORBABLE]; this.priority = 25; } process(_deltaTime, _entities) { - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const player = playerController ? playerController.getPlayerEntity() : null; if (!player) return; - const playerPos = player.getComponent('Position'); - const playerEvolution = player.getComponent('Evolution'); - const playerSkills = player.getComponent('Skills'); - const playerStats = player.getComponent('Stats'); - const skillProgress = player.getComponent('SkillProgress'); + const playerPos = player.getComponent(ComponentType.POSITION); + const playerEvolution = player.getComponent(ComponentType.EVOLUTION); + const playerSkills = player.getComponent(ComponentType.SKILLS); + const playerStats = player.getComponent(ComponentType.STATS); + const skillProgress = player.getComponent(ComponentType.SKILL_PROGRESS); if (!playerPos || !playerEvolution) return; @@ -33,19 +34,19 @@ export class AbsorptionSystem extends System { if (entity === player) return; // Allow inactive entities if they're dead and absorbable if (!entity.active) { - const health = entity.getComponent('Health'); - const absorbable = entity.getComponent('Absorbable'); + const health = entity.getComponent(ComponentType.HEALTH); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); // Only process inactive entities if they're dead and not yet absorbed if (!health || !health.isDead() || !absorbable || absorbable.absorbed) { return; } } - if (!entity.hasComponent('Absorbable')) return; - if (!entity.hasComponent('Health')) return; + if (!entity.hasComponent(ComponentType.ABSORBABLE)) return; + if (!entity.hasComponent(ComponentType.HEALTH)) return; - const absorbable = entity.getComponent('Absorbable'); - const health = entity.getComponent('Health'); - const entityPos = entity.getComponent('Position'); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); + const health = entity.getComponent(ComponentType.HEALTH); + const entityPos = entity.getComponent(ComponentType.POSITION); if (!entityPos) return; @@ -68,8 +69,8 @@ export class AbsorptionSystem extends System { if (absorbable.absorbed) return; absorbable.absorbed = true; - const entityPos = entity.getComponent('Position'); - const health = player.getComponent('Health'); + const entityPos = entity.getComponent(ComponentType.POSITION); + const health = player.getComponent(ComponentType.HEALTH); const config = GameConfig.Absorption; // Add evolution points @@ -111,7 +112,7 @@ export class AbsorptionSystem extends System { // Visual effect if (entityPos) { - const vfxSystem = this.engine.systems.find(s => s.name === 'VFXSystem'); + const vfxSystem = this.engine.systems.find(s => s.name === SystemName.VFX); if (vfxSystem) { vfxSystem.createAbsorption(entityPos.x, entityPos.y); } diff --git a/src/systems/CombatSystem.js b/src/systems/CombatSystem.js index 54b3c3c..86ac06b 100644 --- a/src/systems/CombatSystem.js +++ b/src/systems/CombatSystem.js @@ -1,35 +1,36 @@ import { System } from '../core/System.js'; import { GameConfig } from '../GameConfig.js'; import { Events } from '../core/EventBus.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class CombatSystem extends System { constructor() { - super('CombatSystem'); - this.requiredComponents = ['Position', 'Combat', 'Health']; + super(SystemName.COMBAT); + this.requiredComponents = [ComponentType.POSITION, ComponentType.COMBAT, ComponentType.HEALTH]; this.priority = 20; } process(deltaTime, entities) { // Update combat cooldowns entities.forEach(entity => { - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat) { combat.update(deltaTime); } }); // Handle player attacks - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const player = playerController ? playerController.getPlayerEntity() : null; - if (player && player.hasComponent('Combat')) { + if (player && player.hasComponent(ComponentType.COMBAT)) { this.handlePlayerCombat(player, deltaTime); } // Handle creature attacks const creatures = entities.filter(e => - e.hasComponent('AI') && - e.hasComponent('Combat') && + e.hasComponent(ComponentType.AI) && + e.hasComponent(ComponentType.COMBAT) && e !== player ); @@ -42,9 +43,9 @@ export class CombatSystem extends System { } handlePlayerCombat(player, _deltaTime) { - const inputSystem = this.engine.systems.find(s => s.name === 'InputSystem'); - const combat = player.getComponent('Combat'); - const position = player.getComponent('Position'); + const inputSystem = this.engine.systems.find(s => s.name === SystemName.INPUT); + const combat = player.getComponent(ComponentType.COMBAT); + const position = player.getComponent(ComponentType.POSITION); if (!inputSystem || !combat || !position) return; @@ -72,10 +73,10 @@ export class CombatSystem extends System { } handleCreatureCombat(creature, player, _deltaTime) { - const ai = creature.getComponent('AI'); - const combat = creature.getComponent('Combat'); - const position = creature.getComponent('Position'); - const playerPos = player?.getComponent('Position'); + const ai = creature.getComponent(ComponentType.AI); + const combat = creature.getComponent(ComponentType.COMBAT); + const position = creature.getComponent(ComponentType.POSITION); + const playerPos = player?.getComponent(ComponentType.POSITION); if (!ai || !combat || !position) return; @@ -98,16 +99,12 @@ export class CombatSystem extends System { performAttack(attacker, combat, attackerPos) { const entities = this.engine.getEntities(); - const stats = attacker.getComponent('Stats'); - const _baseDamage = stats ? - (combat.attackDamage + stats.strength * 0.5) : - combat.attackDamage; entities.forEach(target => { if (target === attacker) return; - if (!target.hasComponent('Health')) return; + if (!target.hasComponent(ComponentType.HEALTH)) return; - const targetPos = target.getComponent('Position'); + const targetPos = target.getComponent(ComponentType.POSITION); if (!targetPos) return; // Check if in attack range and angle @@ -124,14 +121,14 @@ export class CombatSystem extends System { // Attack arc const attackArc = GameConfig.Combat.defaultAttackArc; if (minDiff < attackArc) { - const health = target.getComponent('Health'); + const health = target.getComponent(ComponentType.HEALTH); const config = GameConfig.Combat; - const stats = attacker.getComponent('Stats'); + const stats = attacker.getComponent(ComponentType.STATS); const baseDamage = stats ? (combat.attackDamage + stats.strength * 0.5) : combat.attackDamage; // Defense bonus from Hardened Shell let finalDamage = baseDamage; - const targetEvolution = target.getComponent('Evolution'); + const targetEvolution = target.getComponent(ComponentType.EVOLUTION); if (targetEvolution && targetEvolution.mutationEffects.hardenedShell) { finalDamage *= config.hardenedShellReduction; @@ -149,7 +146,7 @@ export class CombatSystem extends System { // Damage reflection from Electric Skin if (targetEvolution && targetEvolution.mutationEffects.electricSkin) { - const attackerHealth = attacker.getComponent('Health'); + const attackerHealth = attacker.getComponent(ComponentType.HEALTH); if (attackerHealth) { const reflectedDamage = actualDamage * config.damageReflectionPercent; attackerHealth.takeDamage(reflectedDamage); @@ -169,7 +166,7 @@ export class CombatSystem extends System { } // Apply knockback - const velocity = target.getComponent('Velocity'); + const velocity = target.getComponent(ComponentType.VELOCITY); if (velocity) { const knockbackPower = config.knockbackPower; const kx = Math.cos(angle) * knockbackPower; diff --git a/src/systems/DeathSystem.js b/src/systems/DeathSystem.js index 9a90330..7145f74 100644 --- a/src/systems/DeathSystem.js +++ b/src/systems/DeathSystem.js @@ -1,12 +1,13 @@ import { System } from '../core/System.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; /** * System to handle entity death - removes dead entities immediately */ export class DeathSystem extends System { constructor() { - super('DeathSystem'); - this.requiredComponents = ['Health']; + super(SystemName.DEATH); + this.requiredComponents = [ComponentType.HEALTH]; this.priority = 50; // Run after absorption (absorption is priority 25) } @@ -19,15 +20,15 @@ export class DeathSystem extends System { process(deltaTime, allEntities) { allEntities.forEach(entity => { - const health = entity.getComponent('Health'); + const health = entity.getComponent(ComponentType.HEALTH); if (!health) return; // Check if entity is dead if (health.isDead()) { // Check if player died - const evolution = entity.getComponent('Evolution'); + const evolution = entity.getComponent(ComponentType.EVOLUTION); if (evolution) { - const menuSystem = this.engine.systems.find(s => s.name === 'MenuSystem'); + const menuSystem = this.engine.systems.find(s => s.name === SystemName.MENU); if (menuSystem) { menuSystem.showGameOver(); } @@ -45,7 +46,7 @@ export class DeathSystem extends System { } // Check if it's absorbable - if so, give a short window for absorption - const absorbable = entity.getComponent('Absorbable'); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); if (absorbable && !absorbable.absorbed) { // Give 3 seconds for player to absorb, then remove const timeSinceDeath = (Date.now() - entity.deathTime) / 1000; diff --git a/src/systems/HealthRegenerationSystem.js b/src/systems/HealthRegenerationSystem.js index c8d3d0e..4115d9c 100644 --- a/src/systems/HealthRegenerationSystem.js +++ b/src/systems/HealthRegenerationSystem.js @@ -1,18 +1,19 @@ import { System } from '../core/System.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; /** - * System to handle health regeneration - */ + * System to handle health regeneration + */ export class HealthRegenerationSystem extends System { constructor() { - super('HealthRegenerationSystem'); - this.requiredComponents = ['Health']; + super(SystemName.HEALTH_REGEN); + this.requiredComponents = [ComponentType.HEALTH]; this.priority = 35; } process(deltaTime, entities) { entities.forEach(entity => { - const health = entity.getComponent('Health'); + const health = entity.getComponent(ComponentType.HEALTH); if (!health || health.regeneration <= 0) return; // Regenerate health over time diff --git a/src/systems/InputSystem.js b/src/systems/InputSystem.js index b5b0d77..faa9f8a 100644 --- a/src/systems/InputSystem.js +++ b/src/systems/InputSystem.js @@ -1,8 +1,9 @@ import { System } from '../core/System.js'; +import { SystemName } from '../core/Constants.js'; export class InputSystem extends System { constructor() { - super('InputSystem'); + super(SystemName.INPUT); this.requiredComponents = []; // No required components - handles input globally this.priority = 0; // Run first diff --git a/src/systems/MenuSystem.js b/src/systems/MenuSystem.js index 9ba7d57..a370916 100644 --- a/src/systems/MenuSystem.js +++ b/src/systems/MenuSystem.js @@ -1,18 +1,19 @@ import { System } from '../core/System.js'; import { PixelFont } from '../core/PixelFont.js'; import { Palette } from '../core/Palette.js'; +import { GameState, ComponentType, SystemName } from '../core/Constants.js'; /** * System to handle game menus (start, pause) */ export class MenuSystem extends System { constructor(engine) { - super('MenuSystem'); + super(SystemName.MENU); this.requiredComponents = []; // No required components this.priority = 1; // Run early this.engine = engine; this.ctx = engine.ctx; - this.gameState = 'start'; // 'start', 'playing', 'paused' + this.gameState = GameState.START; this.paused = false; } @@ -24,16 +25,16 @@ export class MenuSystem extends System { setupInput() { window.addEventListener('keydown', (e) => { if (e.key === 'Escape' || e.key === 'p' || e.key === 'P') { - if (this.gameState === 'playing') { + if (this.gameState === GameState.PLAYING) { this.togglePause(); } } if (e.key === 'Enter' || e.key === ' ') { - if (this.gameState === 'start') { + if (this.gameState === GameState.START) { this.startGame(); - } else if (this.gameState === 'paused') { + } else if (this.gameState === GameState.PAUSED) { this.resumeGame(); - } else if (this.gameState === 'gameOver') { + } else if (this.gameState === GameState.GAME_OVER) { this.restartGame(); } } @@ -41,7 +42,7 @@ export class MenuSystem extends System { } showGameOver() { - this.gameState = 'gameOver'; + this.gameState = GameState.GAME_OVER; this.paused = true; } @@ -50,7 +51,7 @@ export class MenuSystem extends System { } startGame() { - this.gameState = 'playing'; + this.gameState = GameState.PLAYING; this.paused = false; if (!this.engine.running) { this.engine.start(); @@ -58,28 +59,23 @@ export class MenuSystem extends System { } togglePause() { - if (this.gameState === 'playing') { - this.gameState = 'paused'; + if (this.gameState === GameState.PLAYING) { + this.gameState = GameState.PAUSED; this.paused = true; - } else if (this.gameState === 'paused') { + } else if (this.gameState === GameState.PAUSED) { this.resumeGame(); } } resumeGame() { - this.gameState = 'playing'; + this.gameState = GameState.PLAYING; this.paused = false; } process(_deltaTime, _entities) { // Don't update game systems if paused or at start menu - if (this.gameState === 'paused' || this.gameState === 'start') { - // Pause all other systems - this.engine.systems.forEach(system => { - if (system !== this && system.name !== 'MenuSystem' && system.name !== 'UISystem') { - // Systems will check game state themselves - } - }); + if (this.gameState === GameState.PAUSED || this.gameState === GameState.START) { + // Logic for system handling is moved to the update loop in Engine } } @@ -92,7 +88,7 @@ export class MenuSystem extends System { ctx.fillStyle = 'rgba(32, 21, 51, 0.8)'; // Semi-transparent VOID ctx.fillRect(0, 0, width, height); - if (this.gameState === 'start') { + if (this.gameState === GameState.START) { const title = 'SLIME GENESIS'; const titleW = PixelFont.getTextWidth(title, 2); PixelFont.drawText(ctx, title, (width - titleW) / 2, height / 2 - 40, Palette.CYAN, 2); @@ -112,7 +108,7 @@ export class MenuSystem extends System { PixelFont.drawText(ctx, line, (width - lineW) / 2, height / 2 + 25 + i * 10, Palette.ROYAL_BLUE, 1); }); - } else if (this.gameState === 'paused') { + } else if (this.gameState === GameState.PAUSED) { const paused = 'PAUSED'; const pausedW = PixelFont.getTextWidth(paused, 2); PixelFont.drawText(ctx, paused, (width - pausedW) / 2, 20, Palette.SKY_BLUE, 2); @@ -122,8 +118,8 @@ export class MenuSystem extends System { PixelFont.drawText(ctx, resume, (width - resumeW) / 2, 45, Palette.WHITE, 1); // Draw Stats and Knowledge (Moved from HUD) - const player = this.engine.getEntities().find(e => e.hasComponent('Evolution')); - const uiSystem = this.engine.systems.find(s => s.name === 'UISystem'); + const player = this.engine.getEntities().find(e => e.hasComponent(ComponentType.EVOLUTION)); + const uiSystem = this.engine.systems.find(s => s.name === SystemName.UI); if (player && uiSystem) { // Draw Stats on the left @@ -132,7 +128,7 @@ export class MenuSystem extends System { // Draw Learning Progress on the right uiSystem.drawSkillProgress(player, width - 110, 80); } - } else if (this.gameState === 'gameOver') { + } else if (this.gameState === GameState.GAME_OVER) { const dead = 'YOU PERISHED'; const deadW = PixelFont.getTextWidth(dead, 2); PixelFont.drawText(ctx, dead, (width - deadW) / 2, height / 2 - 30, Palette.WHITE, 2); @@ -152,7 +148,7 @@ export class MenuSystem extends System { } isPaused() { - return this.paused || this.gameState === 'start'; + return this.paused || this.gameState === GameState.START; } } diff --git a/src/systems/MovementSystem.js b/src/systems/MovementSystem.js index abca20c..1f7e5b0 100644 --- a/src/systems/MovementSystem.js +++ b/src/systems/MovementSystem.js @@ -1,17 +1,18 @@ import { System } from '../core/System.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class MovementSystem extends System { constructor() { - super('MovementSystem'); - this.requiredComponents = ['Position', 'Velocity']; + super(SystemName.MOVEMENT); + this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY]; this.priority = 10; } process(deltaTime, entities) { entities.forEach(entity => { - const position = entity.getComponent('Position'); - const velocity = entity.getComponent('Velocity'); - const health = entity.getComponent('Health'); + const position = entity.getComponent(ComponentType.POSITION); + const velocity = entity.getComponent(ComponentType.VELOCITY); + const health = entity.getComponent(ComponentType.HEALTH); if (!position || !velocity) return; diff --git a/src/systems/PlayerControllerSystem.js b/src/systems/PlayerControllerSystem.js index 7cd3a5d..9a8cb31 100644 --- a/src/systems/PlayerControllerSystem.js +++ b/src/systems/PlayerControllerSystem.js @@ -1,26 +1,27 @@ import { System } from '../core/System.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class PlayerControllerSystem extends System { constructor() { - super('PlayerControllerSystem'); - this.requiredComponents = ['Position', 'Velocity']; + super(SystemName.PLAYER_CONTROLLER); + this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY]; this.priority = 5; this.playerEntity = null; } process(deltaTime, entities) { - // Find player entity (first entity with player tag or specific component) + // Find player entity (entity with Evolution component) if (!this.playerEntity) { - this.playerEntity = entities.find(e => e.hasComponent('Evolution')); + this.playerEntity = entities.find(e => e.hasComponent(ComponentType.EVOLUTION)); } if (!this.playerEntity) return; - const inputSystem = this.engine.systems.find(s => s.name === 'InputSystem'); + const inputSystem = this.engine.systems.find(s => s.name === SystemName.INPUT); if (!inputSystem) return; - const velocity = this.playerEntity.getComponent('Velocity'); - const position = this.playerEntity.getComponent('Position'); + const velocity = this.playerEntity.getComponent(ComponentType.VELOCITY); + const position = this.playerEntity.getComponent(ComponentType.POSITION); if (!velocity || !position) return; // Movement input diff --git a/src/systems/ProjectileSystem.js b/src/systems/ProjectileSystem.js index 9c7e38d..83499a6 100644 --- a/src/systems/ProjectileSystem.js +++ b/src/systems/ProjectileSystem.js @@ -1,23 +1,24 @@ import { System } from '../core/System.js'; import { Events } from '../core/EventBus.js'; import { Palette } from '../core/Palette.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class ProjectileSystem extends System { constructor() { - super('ProjectileSystem'); - this.requiredComponents = ['Position', 'Velocity']; + super(SystemName.PROJECTILE); + this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY]; this.priority = 18; } process(deltaTime, entities) { - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const _player = playerController ? playerController.getPlayerEntity() : null; entities.forEach(entity => { - const health = entity.getComponent('Health'); + const health = entity.getComponent(ComponentType.HEALTH); if (!health || !health.isProjectile) return; - const position = entity.getComponent('Position'); + const position = entity.getComponent(ComponentType.POSITION); if (!position) return; // Check range - remove if traveled beyond max range @@ -46,10 +47,10 @@ export class ProjectileSystem extends System { allEntities.forEach(target => { if (target.id === entity.owner) return; if (target.id === entity.id) return; - if (!target.hasComponent('Health')) return; - if (target.getComponent('Health').isProjectile) return; + if (!target.hasComponent(ComponentType.HEALTH)) return; + if (target.getComponent(ComponentType.HEALTH).isProjectile) return; - const targetPos = target.getComponent('Position'); + const targetPos = target.getComponent(ComponentType.POSITION); if (!targetPos) return; const dx = targetPos.x - position.x; @@ -58,13 +59,13 @@ export class ProjectileSystem extends System { if (distance < 8) { // Hit! - const targetHealth = target.getComponent('Health'); + const targetHealth = target.getComponent(ComponentType.HEALTH); const damage = entity.damage || 10; targetHealth.takeDamage(damage); // Impact animation - const vfxSystem = this.engine.systems.find(s => s.name === 'VFXSystem'); - const velocity = entity.getComponent('Velocity'); + const vfxSystem = this.engine.systems.find(s => s.name === SystemName.VFX); + const velocity = entity.getComponent(ComponentType.VELOCITY); if (vfxSystem) { const angle = velocity ? Math.atan2(velocity.vy, velocity.vx) : null; vfxSystem.createImpact(position.x, position.y, Palette.CYAN, angle); diff --git a/src/systems/RenderSystem.js b/src/systems/RenderSystem.js index 24cd310..3e7a303 100644 --- a/src/systems/RenderSystem.js +++ b/src/systems/RenderSystem.js @@ -1,11 +1,12 @@ import { System } from '../core/System.js'; import { Palette } from '../core/Palette.js'; import { SpriteLibrary } from '../core/SpriteLibrary.js'; +import { ComponentType, SystemName, AnimationState, VFXType, EntityType } from '../core/Constants.js'; export class RenderSystem extends System { constructor(engine) { - super('RenderSystem'); - this.requiredComponents = ['Position', 'Sprite']; + super(SystemName.RENDER); + this.requiredComponents = [ComponentType.POSITION, ComponentType.SPRITE]; this.priority = 100; // Render last this.engine = engine; this.ctx = engine.ctx; @@ -25,12 +26,12 @@ export class RenderSystem extends System { // Get all entities including inactive ones for rendering dead absorbable entities const allEntities = this.engine.entities; allEntities.forEach(entity => { - const health = entity.getComponent('Health'); - const evolution = entity.getComponent('Evolution'); + const health = entity.getComponent(ComponentType.HEALTH); + const evolution = entity.getComponent(ComponentType.EVOLUTION); // Skip inactive entities UNLESS they're dead and absorbable (for absorption window) if (!entity.active) { - const absorbable = entity.getComponent('Absorbable'); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); if (health && health.isDead() && absorbable && !absorbable.absorbed) { // Render dead absorbable entities even if inactive (fade them out) this.drawEntity(entity, deltaTime, true); // Pass fade flag @@ -41,7 +42,7 @@ export class RenderSystem extends System { // Don't render dead non-player entities (unless they're absorbable, handled above) if (health && health.isDead() && !evolution) { - const absorbable = entity.getComponent('Absorbable'); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); if (!absorbable || absorbable.absorbed) { return; // Skip dead non-absorbable entities } @@ -105,9 +106,9 @@ export class RenderSystem extends System { } drawEntity(entity, deltaTime, isDeadFade = false) { - const position = entity.getComponent('Position'); - const sprite = entity.getComponent('Sprite'); - const health = entity.getComponent('Health'); + const position = entity.getComponent(ComponentType.POSITION); + const sprite = entity.getComponent(ComponentType.SPRITE); + const health = entity.getComponent(ComponentType.HEALTH); if (!position || !sprite) return; @@ -120,7 +121,7 @@ export class RenderSystem extends System { // Fade out dead entities let alpha = sprite.alpha; if (isDeadFade && health && health.isDead()) { - const absorbable = entity.getComponent('Absorbable'); + const absorbable = entity.getComponent(ComponentType.ABSORBABLE); if (absorbable && !absorbable.absorbed) { // Calculate fade based on time since death const deathTime = entity.deathTime || Date.now(); @@ -135,24 +136,22 @@ export class RenderSystem extends System { this.ctx.scale(sprite.scale, sprite.scale); // Update animation time for slime morphing - if (sprite.shape === 'slime') { + if (sprite.shape === EntityType.SLIME) { sprite.animationTime += deltaTime; sprite.morphAmount = Math.sin(sprite.animationTime * 3) * 0.2 + 0.8; } // Map legacy colors to new Palette if necessary let drawColor = sprite.color; - if (sprite.shape === 'slime') drawColor = Palette.CYAN; - // Map other colors? For now keep them if they match, but we should enforce palette eventually. - // The previous code had specific hardcoded colors. + if (sprite.shape === EntityType.SLIME) drawColor = Palette.CYAN; this.ctx.fillStyle = drawColor; // Select appropriate animation state based on velocity - const velocity = entity.getComponent('Velocity'); + const velocity = entity.getComponent(ComponentType.VELOCITY); if (velocity) { const isMoving = Math.abs(velocity.vx) > 1 || Math.abs(velocity.vy) > 1; - sprite.animationState = isMoving ? 'walk' : 'idle'; + sprite.animationState = isMoving ? AnimationState.WALK : AnimationState.IDLE; } // Lookup animation data @@ -162,7 +161,7 @@ export class RenderSystem extends System { } // Get animation frames for the current state - let frames = spriteData[sprite.animationState] || spriteData['idle']; + let frames = spriteData[sprite.animationState] || spriteData[AnimationState.IDLE]; // If frames is still not an array (fallback for simple grids or missing states) if (!frames || !Array.isArray(frames)) { @@ -238,7 +237,7 @@ export class RenderSystem extends System { } // Draw combat indicator if attacking (This DOES rotate) - const combat = entity.getComponent('Combat'); + const combat = entity.getComponent(ComponentType.COMBAT); if (combat && combat.isAttacking) { this.ctx.save(); this.ctx.rotate(position.rotation); @@ -247,13 +246,13 @@ export class RenderSystem extends System { } // Draw stealth indicator - const stealth = entity.getComponent('Stealth'); + const stealth = entity.getComponent(ComponentType.STEALTH); if (stealth && stealth.isStealthed) { this.drawStealthIndicator(stealth, sprite); } // Mutation Visual Effects - Simplified for pixel art - const evolution = entity.getComponent('Evolution'); + const evolution = entity.getComponent(ComponentType.EVOLUTION); if (evolution) { if (evolution.mutationEffects.glowingBody) { // Simple outline (square) @@ -275,10 +274,8 @@ export class RenderSystem extends System { this.ctx.restore(); } - - drawVFX() { - const vfxSystem = this.engine.systems.find(s => s.name === 'VFXSystem'); + const vfxSystem = this.engine.systems.find(s => s.name === SystemName.VFX); if (!vfxSystem) return; const ctx = this.ctx; @@ -287,7 +284,7 @@ export class RenderSystem extends System { particles.forEach(p => { ctx.fillStyle = p.color; // Fade based on lifetime for impact, or keep solid/flicker for absorption - ctx.globalAlpha = p.type === 'impact' ? Math.min(1, p.lifetime / 0.3) : 0.8; + ctx.globalAlpha = p.type === VFXType.IMPACT ? Math.min(1, p.lifetime / 0.3) : 0.8; // Snap to integers for pixel crispness const x = Math.floor(p.x); @@ -333,7 +330,6 @@ export class RenderSystem extends System { ctx.fillRect(startX, startY, fillWidth, barHeight); } - drawAttackIndicator(combat, _position) { const ctx = this.ctx; const length = 25; // Scaled down @@ -395,7 +391,8 @@ export class RenderSystem extends System { } drawSkillEffects() { - const skillEffectSystem = this.engine.systems.find(s => s.name === 'SkillEffectSystem'); + const skillEffectSystem = this.engine.systems.find(s => s.name === SystemName.SKILL_EFFECT); + // NOTE: SKILL_EFFECT was missing in my constants. I should add it. if (!skillEffectSystem) return; const effects = skillEffectSystem.getEffects(); @@ -405,15 +402,15 @@ export class RenderSystem extends System { ctx.save(); switch (effect.type) { - case 'fire_breath': - this.drawFireBreath(ctx, effect); - break; - case 'pounce': - this.drawPounce(ctx, effect); - break; - case 'pounce_impact': - this.drawPounceImpact(ctx, effect); - break; + case 'fire_breath': + this.drawFireBreath(ctx, effect); + break; + case 'pounce': + this.drawPounce(ctx, effect); + break; + case 'pounce_impact': + this.drawPounceImpact(ctx, effect); + break; } ctx.restore(); diff --git a/src/systems/SkillEffectSystem.js b/src/systems/SkillEffectSystem.js index c2565a7..08fffca 100644 --- a/src/systems/SkillEffectSystem.js +++ b/src/systems/SkillEffectSystem.js @@ -1,11 +1,12 @@ import { System } from '../core/System.js'; +import { SystemName } from '../core/Constants.js'; /** * System to track and render skill effects (Fire Breath, Pounce, etc.) */ export class SkillEffectSystem extends System { constructor() { - super('SkillEffectSystem'); + super(SystemName.SKILL_EFFECT); this.requiredComponents = []; // No required components this.priority = 50; // Run after skills but before rendering this.activeEffects = []; diff --git a/src/systems/SkillSystem.js b/src/systems/SkillSystem.js index bdf8598..bdd2334 100644 --- a/src/systems/SkillSystem.js +++ b/src/systems/SkillSystem.js @@ -1,19 +1,20 @@ import { System } from '../core/System.js'; import { SkillRegistry } from '../skills/SkillRegistry.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class SkillSystem extends System { constructor() { - super('SkillSystem'); - this.requiredComponents = ['Skills']; + super(SystemName.SKILL); + this.requiredComponents = [ComponentType.SKILLS]; this.priority = 30; } process(deltaTime, entities) { - const inputSystem = this.engine.systems.find(s => s.name === 'InputSystem'); + const inputSystem = this.engine.systems.find(s => s.name === SystemName.INPUT); if (!inputSystem) return; entities.forEach(entity => { - const skills = entity.getComponent('Skills'); + const skills = entity.getComponent(ComponentType.SKILLS); if (!skills) return; // Update cooldowns @@ -43,7 +44,7 @@ export class SkillSystem extends System { } if (skill.activate(entity, this.engine)) { - const skills = entity.getComponent('Skills'); + const skills = entity.getComponent(ComponentType.SKILLS); if (skills) { skills.setCooldown(skillId, skill.cooldown); } diff --git a/src/systems/StealthSystem.js b/src/systems/StealthSystem.js index 9ea7ec9..e2c812b 100644 --- a/src/systems/StealthSystem.js +++ b/src/systems/StealthSystem.js @@ -1,22 +1,23 @@ import { System } from '../core/System.js'; +import { SystemName, ComponentType } from '../core/Constants.js'; export class StealthSystem extends System { constructor() { - super('StealthSystem'); - this.requiredComponents = ['Stealth']; + super(SystemName.STEALTH); + this.requiredComponents = [ComponentType.STEALTH]; this.priority = 12; } process(deltaTime, entities) { - const inputSystem = this.engine.systems.find(s => s.name === 'InputSystem'); - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const inputSystem = this.engine.systems.find(s => s.name === SystemName.INPUT); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const player = playerController ? playerController.getPlayerEntity() : null; entities.forEach(entity => { - const stealth = entity.getComponent('Stealth'); - const velocity = entity.getComponent('Velocity'); - const combat = entity.getComponent('Combat'); - const evolution = entity.getComponent('Evolution'); + const stealth = entity.getComponent(ComponentType.STEALTH); + const velocity = entity.getComponent(ComponentType.VELOCITY); + const combat = entity.getComponent(ComponentType.COMBAT); + const evolution = entity.getComponent(ComponentType.EVOLUTION); if (!stealth) return; diff --git a/src/systems/UISystem.js b/src/systems/UISystem.js index 21189a1..6e567ff 100644 --- a/src/systems/UISystem.js +++ b/src/systems/UISystem.js @@ -3,10 +3,11 @@ import { SkillRegistry } from '../skills/SkillRegistry.js'; import { Events } from '../core/EventBus.js'; import { PixelFont } from '../core/PixelFont.js'; import { Palette } from '../core/Palette.js'; +import { GameState, ComponentType, SystemName } from '../core/Constants.js'; export class UISystem extends System { constructor(engine) { - super('UISystem'); + super(SystemName.UI); this.requiredComponents = []; // No required components - renders UI this.priority = 200; // Render after everything else this.engine = engine; @@ -44,11 +45,11 @@ export class UISystem extends System { this.updateDamageNumbers(deltaTime); this.updateNotifications(deltaTime); - const menuSystem = this.engine.systems.find(s => s.name === 'MenuSystem'); - const gameState = menuSystem ? menuSystem.getGameState() : 'playing'; + const menuSystem = this.engine.systems.find(s => s.name === SystemName.MENU); + const gameState = menuSystem ? menuSystem.getGameState() : GameState.PLAYING; // Only draw menu overlay if in start, paused, or gameOver state - if (gameState === 'start' || gameState === 'paused' || gameState === 'gameOver') { + if (gameState === GameState.START || gameState === GameState.PAUSED || gameState === GameState.GAME_OVER) { if (menuSystem) { menuSystem.drawMenu(); } @@ -56,7 +57,7 @@ export class UISystem extends System { return; } - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); const player = playerController ? playerController.getPlayerEntity() : null; if (!player) return; @@ -70,9 +71,9 @@ export class UISystem extends System { } drawHUD(player) { - const health = player.getComponent('Health'); - const stats = player.getComponent('Stats'); - const evolution = player.getComponent('Evolution'); + const health = player.getComponent(ComponentType.HEALTH); + const stats = player.getComponent(ComponentType.STATS); + const evolution = player.getComponent(ComponentType.EVOLUTION); if (!health || !stats || !evolution) return; @@ -106,7 +107,7 @@ export class UISystem extends System { } drawSkills(player) { - const skills = player.getComponent('Skills'); + const skills = player.getComponent(ComponentType.SKILLS); if (!skills) return; const ctx = this.ctx; @@ -132,8 +133,8 @@ export class UISystem extends System { } drawStats(player, x, y) { - const stats = player.getComponent('Stats'); - const evolution = player.getComponent('Evolution'); + const stats = player.getComponent(ComponentType.STATS); + const evolution = player.getComponent(ComponentType.EVOLUTION); if (!stats || !evolution) return; const ctx = this.ctx; @@ -150,7 +151,7 @@ export class UISystem extends System { } drawSkillProgress(player, x, y) { - const skillProgress = player.getComponent('SkillProgress'); + const skillProgress = player.getComponent(ComponentType.SKILL_PROGRESS); if (!skillProgress) return; const ctx = this.ctx; diff --git a/src/systems/VFXSystem.js b/src/systems/VFXSystem.js index 62f24d3..0a458c0 100644 --- a/src/systems/VFXSystem.js +++ b/src/systems/VFXSystem.js @@ -1,104 +1,105 @@ import { System } from '../core/System.js'; import { Palette } from '../core/Palette.js'; +import { SystemName, ComponentType, VFXType } from '../core/Constants.js'; export class VFXSystem extends System { - constructor() { - super('VFXSystem'); - this.requiredComponents = []; - this.priority = 40; - this.particles = []; - } + constructor() { + super(SystemName.VFX); + this.requiredComponents = []; + this.priority = 40; + this.particles = []; + } - process(deltaTime, _entities) { - const playerController = this.engine.systems.find(s => s.name === 'PlayerControllerSystem'); - const player = playerController ? playerController.getPlayerEntity() : null; - const playerPos = player ? player.getComponent('Position') : null; + process(deltaTime, _entities) { + const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER); + const player = playerController ? playerController.getPlayerEntity() : null; + const playerPos = player ? player.getComponent(ComponentType.POSITION) : null; - for (let i = this.particles.length - 1; i >= 0; i--) { - const p = this.particles[i]; + for (let i = this.particles.length - 1; i >= 0; i--) { + const p = this.particles[i]; - // Update lifetime - p.lifetime -= deltaTime; - if (p.lifetime <= 0) { - this.particles.splice(i, 1); - continue; - } + // Update lifetime + p.lifetime -= deltaTime; + if (p.lifetime <= 0) { + this.particles.splice(i, 1); + continue; + } - // Behavior logic - if (p.type === 'absorption' && playerPos) { - // Attract to player - const dx = playerPos.x - p.x; - const dy = playerPos.y - p.y; - const dist = Math.sqrt(dx * dx + dy * dy); + // Behavior logic + if (p.type === VFXType.ABSORPTION && playerPos) { + // Attract to player + const dx = playerPos.x - p.x; + const dy = playerPos.y - p.y; + const dist = Math.sqrt(dx * dx + dy * dy); - if (dist > 5) { - p.vx += (dx / dist) * 800 * deltaTime; - p.vy += (dy / dist) * 800 * deltaTime; - // Add some drag to make it smooth - p.vx *= 0.95; - p.vy *= 0.95; - } else { - // Arrived - this.particles.splice(i, 1); - continue; - } - } else { - // Basic drag for impact particles - p.vx *= 0.9; - p.vy *= 0.9; - } - - // Update position - p.x += p.vx * deltaTime; - p.y += p.vy * deltaTime; + if (dist > 5) { + p.vx += (dx / dist) * 800 * deltaTime; + p.vy += (dy / dist) * 800 * deltaTime; + // Add some drag to make it smooth + p.vx *= 0.95; + p.vy *= 0.95; + } else { + // Arrived + this.particles.splice(i, 1); + continue; } - } + } else { + // Basic drag for impact particles + p.vx *= 0.9; + p.vy *= 0.9; + } - createImpact(x, y, color = Palette.WHITE, angle = null) { - const count = 8; - for (let i = 0; i < count; i++) { - let vx, vy; - if (angle !== null) { - // Splash in the direction of hit + some spread - const spread = (Math.random() - 0.5) * 2; - const speed = 50 + Math.random() * 150; - vx = Math.cos(angle + spread) * speed; - vy = Math.sin(angle + spread) * speed; - } else { - vx = (Math.random() - 0.5) * 150; - vy = (Math.random() - 0.5) * 150; - } - - this.particles.push({ - x, - y, - vx, - vy, - lifetime: 0.2 + Math.random() * 0.3, - size: 1 + Math.random() * 2, - color: color, - type: 'impact' - }); - } + // Update position + p.x += p.vx * deltaTime; + p.y += p.vy * deltaTime; } + } - createAbsorption(x, y, color = Palette.CYAN) { - for (let i = 0; i < 12; i++) { - // Start with a small explosion then attract - this.particles.push({ - x, - y, - vx: (Math.random() - 0.5) * 100, - vy: (Math.random() - 0.5) * 100, - lifetime: 1.5, - size: 2, - color: color, - type: 'absorption' - }); - } - } + createImpact(x, y, color = Palette.WHITE, angle = null) { + const count = 8; + for (let i = 0; i < count; i++) { + let vx, vy; + if (angle !== null) { + // Splash in the direction of hit + some spread + const spread = (Math.random() - 0.5) * 2; + const speed = 50 + Math.random() * 150; + vx = Math.cos(angle + spread) * speed; + vy = Math.sin(angle + spread) * speed; + } else { + vx = (Math.random() - 0.5) * 150; + vy = (Math.random() - 0.5) * 150; + } - getParticles() { - return this.particles; + this.particles.push({ + x, + y, + vx, + vy, + lifetime: 0.2 + Math.random() * 0.3, + size: 1 + Math.random() * 2, + color: color, + type: VFXType.IMPACT + }); } + } + + createAbsorption(x, y, color = Palette.CYAN) { + for (let i = 0; i < 12; i++) { + // Start with a small explosion then attract + this.particles.push({ + x, + y, + vx: (Math.random() - 0.5) * 100, + vy: (Math.random() - 0.5) * 100, + lifetime: 1.5, + size: 2, + color: color, + type: VFXType.ABSORPTION + }); + } + } + + getParticles() { + return this.particles; + } }