diff --git a/src/core/config/GameConfig.ts b/src/core/config/GameConfig.ts index 42b7ba7..6bd2a31 100644 --- a/src/core/config/GameConfig.ts +++ b/src/core/config/GameConfig.ts @@ -26,17 +26,30 @@ export const GAME_CONFIG = { roomMaxHeight: 10 }, - enemy: { - baseHp: 8, - baseAttack: 3, - minSpeed: 80, - maxSpeed: 130, - baseHpPerFloor: 5, - attackPerTwoFloors: 1, + enemies: { + rat: { + baseHp: 8, + baseAttack: 3, + baseDefense: 0, + minSpeed: 80, + maxSpeed: 110, + expValue: 5 + }, + bat: { + baseHp: 6, + baseAttack: 4, + baseDefense: 0, + minSpeed: 110, + maxSpeed: 140, + expValue: 8 + } + }, + + enemyScaling: { baseCount: 3, baseCountPerFloor: 3, - ratExp: 5, - batExp: 8 + hpPerFloor: 5, + attackPerTwoFloors: 1, }, leveling: { diff --git a/src/engine/simulation/simulation.ts b/src/engine/simulation/simulation.ts index 937e3a3..70522b4 100644 --- a/src/engine/simulation/simulation.ts +++ b/src/engine/simulation/simulation.ts @@ -128,7 +128,8 @@ function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): S w.actors.delete(target.id); // Spawn EXP Orb - const expAmount = target.type === "rat" ? GAME_CONFIG.enemy.ratExp : GAME_CONFIG.enemy.batExp; + const enemyDef = (GAME_CONFIG.enemies as any)[target.type || ""]; + const expAmount = enemyDef?.expValue || 0; const orbId = Math.max(0, ...w.actors.keys(), target.id) + 1; w.actors.set(orbId, { diff --git a/src/engine/world/generator.ts b/src/engine/world/generator.ts index f2af278..297d196 100644 --- a/src/engine/world/generator.ts +++ b/src/engine/world/generator.ts @@ -210,8 +210,10 @@ function generatePatch(width: number, height: number, fillChance: number, iterat function placeEnemies(floor: number, rooms: Room[], actors: Map, random: () => number): void { let enemyId = 2; - const numEnemies = GAME_CONFIG.enemy.baseCount + floor * GAME_CONFIG.enemy.baseCountPerFloor; // Simplified for now + const numEnemies = GAME_CONFIG.enemyScaling.baseCount + floor * GAME_CONFIG.enemyScaling.baseCountPerFloor; + const enemyTypes = Object.keys(GAME_CONFIG.enemies); + for (let i = 0; i < numEnemies && i < rooms.length - 1; i++) { const roomIdx = 1 + Math.floor(random() * (rooms.length - 1)); const room = rooms[roomIdx]; @@ -219,21 +221,24 @@ function placeEnemies(floor: number, rooms: Room[], actors: Map const enemyX = room.x + 1 + Math.floor(random() * (room.width - 2)); const enemyY = room.y + 1 + Math.floor(random() * (room.height - 2)); - const baseHp = GAME_CONFIG.enemy.baseHp + floor * GAME_CONFIG.enemy.baseHpPerFloor; - const baseAttack = GAME_CONFIG.enemy.baseAttack + Math.floor(floor / 2) * GAME_CONFIG.enemy.attackPerTwoFloors; + const type = enemyTypes[Math.floor(random() * enemyTypes.length)] as keyof typeof GAME_CONFIG.enemies; + const enemyDef = GAME_CONFIG.enemies[type]; + + const scaledHp = enemyDef.baseHp + floor * GAME_CONFIG.enemyScaling.hpPerFloor; + const scaledAttack = enemyDef.baseAttack + Math.floor(floor / 2) * GAME_CONFIG.enemyScaling.attackPerTwoFloors; actors.set(enemyId, { id: enemyId, isPlayer: false, - type: random() < 0.5 ? "rat" : "bat", + type, pos: { x: enemyX, y: enemyY }, - speed: GAME_CONFIG.enemy.minSpeed + Math.floor(random() * (GAME_CONFIG.enemy.maxSpeed - GAME_CONFIG.enemy.minSpeed)), + speed: enemyDef.minSpeed + Math.floor(random() * (enemyDef.maxSpeed - enemyDef.minSpeed)), energy: 0, stats: { - maxHp: baseHp + Math.floor(random() * 4), - hp: baseHp + Math.floor(random() * 4), - attack: baseAttack + Math.floor(random() * 2), - defense: 0, + maxHp: scaledHp + Math.floor(random() * 4), + hp: scaledHp + Math.floor(random() * 4), + attack: scaledAttack + Math.floor(random() * 2), + defense: enemyDef.baseDefense, level: 0, exp: 0, expToNextLevel: 0 diff --git a/src/rendering/__tests__/DungeonRenderer.test.ts b/src/rendering/__tests__/DungeonRenderer.test.ts index a42c95e..0663b80 100644 --- a/src/rendering/__tests__/DungeonRenderer.test.ts +++ b/src/rendering/__tests__/DungeonRenderer.test.ts @@ -69,6 +69,8 @@ describe('DungeonRenderer', () => { setDepth: vi.fn().mockReturnThis(), setScale: vi.fn().mockReturnThis(), play: vi.fn().mockReturnThis(), + setPosition: vi.fn().mockReturnThis(), + setVisible: vi.fn().mockReturnThis(), destroy: vi.fn(), })), container: vi.fn().mockReturnValue({ @@ -140,4 +142,67 @@ describe('DungeonRenderer', () => { expect(corpse1.destroy).toHaveBeenCalledTimes(1); expect(corpse2.destroy).toHaveBeenCalledTimes(1); }); + + it('should render exp_orb as a circle and not as an enemy sprite', () => { + renderer.initializeFloor(mockWorld); + + // Add an exp_orb to the world + mockWorld.actors.set(99, { + id: 99, + isPlayer: false, + type: 'exp_orb', + pos: { x: 5, y: 5 }, + speed: 0, + energy: 0 + }); + + // Make the tile visible for it to render + (renderer as any).visible[5 * mockWorld.width + 5] = 1; + + // Reset mocks + mockScene.add.sprite.mockClear(); + + // Mock scene.add.circle + mockScene.add.circle = vi.fn().mockReturnValue({ + setStrokeStyle: vi.fn().mockReturnThis(), + setDepth: vi.fn().mockReturnThis(), + setPosition: vi.fn().mockReturnThis(), + setVisible: vi.fn().mockReturnThis(), + }); + + renderer.render([]); + + // Should NOT have added an enemy sprite for the orb + const spriteCalls = mockScene.add.sprite.mock.calls; + // Any sprite added that isn't the player (which isn't in mockWorld.actors here except if we added it) + // The current loop skips a.isPlayer and then checks if type is in GAME_CONFIG.enemies + expect(spriteCalls.length).toBe(0); + + // Should HAVE added a circle for the orb + expect(mockScene.add.circle).toHaveBeenCalled(); + }); + + it('should render any enemy type defined in config as a sprite', () => { + renderer.initializeFloor(mockWorld); + + // Add a rat (defined in config) + mockWorld.actors.set(100, { + id: 100, + isPlayer: false, + type: 'rat', + pos: { x: 2, y: 2 }, + speed: 100, + energy: 0, + stats: { hp: 10, maxHp: 10, attack: 2, defense: 0, level: 1, exp: 0, expToNextLevel: 0 } + }); + + (renderer as any).visible[2 * mockWorld.width + 2] = 1; + mockScene.add.sprite.mockClear(); + + renderer.render([]); + + // Should have added a sprite for the rat + const ratSpriteCall = mockScene.add.sprite.mock.calls.find((call: any) => call[2] === 'rat'); + expect(ratSpriteCall).toBeDefined(); + }); });