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