Feature/VFX and animations #5
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 { EventBus } from './EventBus.js';
|
||||
import { LevelLoader } from './LevelLoader.js';
|
||||
import { GameState, SystemName } from './Constants.js';
|
||||
|
||||
/**
|
||||
* Main game engine - manages ECS, game loop, and systems
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
17
src/main.js
17
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,17 +87,17 @@ if (!canvas) {
|
|||
let color, evolutionData, skills;
|
||||
|
||||
switch (type) {
|
||||
case 'humanoid':
|
||||
case EntityType.HUMANOID:
|
||||
color = '#ff5555'; // Humanoid red
|
||||
evolutionData = { human: 10, beast: 0, slime: -2 };
|
||||
skills = ['fire_breath'];
|
||||
break;
|
||||
case 'beast':
|
||||
case EntityType.BEAST:
|
||||
color = '#ffaa00'; // Beast orange
|
||||
evolutionData = { human: 0, beast: 10, slime: -2 };
|
||||
skills = ['pounce'];
|
||||
break;
|
||||
case 'elemental':
|
||||
case EntityType.ELEMENTAL:
|
||||
color = '#00bfff';
|
||||
evolutionData = { human: 3, beast: 3, slime: 8 };
|
||||
skills = ['fire_breath'];
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { System } from '../core/System.js';
|
||||
import { SystemName, ComponentType } from '../core/Constants.js';
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
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');
|
||||
super(SystemName.VFX);
|
||||
this.requiredComponents = [];
|
||||
this.priority = 40;
|
||||
this.particles = [];
|
||||
}
|
||||
|
||||
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 ? player.getComponent('Position') : null;
|
||||
const playerPos = player ? player.getComponent(ComponentType.POSITION) : null;
|
||||
|
||||
for (let i = this.particles.length - 1; i >= 0; i--) {
|
||||
const p = this.particles[i];
|
||||
|
|
@ -25,7 +26,7 @@ export class VFXSystem extends System {
|
|||
}
|
||||
|
||||
// Behavior logic
|
||||
if (p.type === 'absorption' && playerPos) {
|
||||
if (p.type === VFXType.ABSORPTION && playerPos) {
|
||||
// Attract to player
|
||||
const dx = playerPos.x - p.x;
|
||||
const dy = playerPos.y - p.y;
|
||||
|
|
@ -77,7 +78,7 @@ export class VFXSystem extends System {
|
|||
lifetime: 0.2 + Math.random() * 0.3,
|
||||
size: 1 + Math.random() * 2,
|
||||
color: color,
|
||||
type: 'impact'
|
||||
type: VFXType.IMPACT
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +94,7 @@ export class VFXSystem extends System {
|
|||
lifetime: 1.5,
|
||||
size: 2,
|
||||
color: color,
|
||||
type: 'absorption'
|
||||
type: VFXType.ABSORPTION
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue