Fix issue where killing an enemy resulted in orb being rendered with rat sprite on top
This commit is contained in:
@@ -26,17 +26,30 @@ export const GAME_CONFIG = {
|
|||||||
roomMaxHeight: 10
|
roomMaxHeight: 10
|
||||||
},
|
},
|
||||||
|
|
||||||
enemy: {
|
enemies: {
|
||||||
|
rat: {
|
||||||
baseHp: 8,
|
baseHp: 8,
|
||||||
baseAttack: 3,
|
baseAttack: 3,
|
||||||
|
baseDefense: 0,
|
||||||
minSpeed: 80,
|
minSpeed: 80,
|
||||||
maxSpeed: 130,
|
maxSpeed: 110,
|
||||||
baseHpPerFloor: 5,
|
expValue: 5
|
||||||
attackPerTwoFloors: 1,
|
},
|
||||||
|
bat: {
|
||||||
|
baseHp: 6,
|
||||||
|
baseAttack: 4,
|
||||||
|
baseDefense: 0,
|
||||||
|
minSpeed: 110,
|
||||||
|
maxSpeed: 140,
|
||||||
|
expValue: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enemyScaling: {
|
||||||
baseCount: 3,
|
baseCount: 3,
|
||||||
baseCountPerFloor: 3,
|
baseCountPerFloor: 3,
|
||||||
ratExp: 5,
|
hpPerFloor: 5,
|
||||||
batExp: 8
|
attackPerTwoFloors: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
leveling: {
|
leveling: {
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): S
|
|||||||
w.actors.delete(target.id);
|
w.actors.delete(target.id);
|
||||||
|
|
||||||
// Spawn EXP Orb
|
// 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;
|
const orbId = Math.max(0, ...w.actors.keys(), target.id) + 1;
|
||||||
w.actors.set(orbId, {
|
w.actors.set(orbId, {
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,9 @@ function generatePatch(width: number, height: number, fillChance: number, iterat
|
|||||||
|
|
||||||
function placeEnemies(floor: number, rooms: Room[], actors: Map<EntityId, Actor>, random: () => number): void {
|
function placeEnemies(floor: number, rooms: Room[], actors: Map<EntityId, Actor>, random: () => number): void {
|
||||||
let enemyId = 2;
|
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++) {
|
for (let i = 0; i < numEnemies && i < rooms.length - 1; i++) {
|
||||||
const roomIdx = 1 + Math.floor(random() * (rooms.length - 1));
|
const roomIdx = 1 + Math.floor(random() * (rooms.length - 1));
|
||||||
@@ -219,21 +221,24 @@ function placeEnemies(floor: number, rooms: Room[], actors: Map<EntityId, Actor>
|
|||||||
const enemyX = room.x + 1 + Math.floor(random() * (room.width - 2));
|
const enemyX = room.x + 1 + Math.floor(random() * (room.width - 2));
|
||||||
const enemyY = room.y + 1 + Math.floor(random() * (room.height - 2));
|
const enemyY = room.y + 1 + Math.floor(random() * (room.height - 2));
|
||||||
|
|
||||||
const baseHp = GAME_CONFIG.enemy.baseHp + floor * GAME_CONFIG.enemy.baseHpPerFloor;
|
const type = enemyTypes[Math.floor(random() * enemyTypes.length)] as keyof typeof GAME_CONFIG.enemies;
|
||||||
const baseAttack = GAME_CONFIG.enemy.baseAttack + Math.floor(floor / 2) * GAME_CONFIG.enemy.attackPerTwoFloors;
|
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, {
|
actors.set(enemyId, {
|
||||||
id: enemyId,
|
id: enemyId,
|
||||||
isPlayer: false,
|
isPlayer: false,
|
||||||
type: random() < 0.5 ? "rat" : "bat",
|
type,
|
||||||
pos: { x: enemyX, y: enemyY },
|
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,
|
energy: 0,
|
||||||
stats: {
|
stats: {
|
||||||
maxHp: baseHp + Math.floor(random() * 4),
|
maxHp: scaledHp + Math.floor(random() * 4),
|
||||||
hp: baseHp + Math.floor(random() * 4),
|
hp: scaledHp + Math.floor(random() * 4),
|
||||||
attack: baseAttack + Math.floor(random() * 2),
|
attack: scaledAttack + Math.floor(random() * 2),
|
||||||
defense: 0,
|
defense: enemyDef.baseDefense,
|
||||||
level: 0,
|
level: 0,
|
||||||
exp: 0,
|
exp: 0,
|
||||||
expToNextLevel: 0
|
expToNextLevel: 0
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ describe('DungeonRenderer', () => {
|
|||||||
setDepth: vi.fn().mockReturnThis(),
|
setDepth: vi.fn().mockReturnThis(),
|
||||||
setScale: vi.fn().mockReturnThis(),
|
setScale: vi.fn().mockReturnThis(),
|
||||||
play: vi.fn().mockReturnThis(),
|
play: vi.fn().mockReturnThis(),
|
||||||
|
setPosition: vi.fn().mockReturnThis(),
|
||||||
|
setVisible: vi.fn().mockReturnThis(),
|
||||||
destroy: vi.fn(),
|
destroy: vi.fn(),
|
||||||
})),
|
})),
|
||||||
container: vi.fn().mockReturnValue({
|
container: vi.fn().mockReturnValue({
|
||||||
@@ -140,4 +142,67 @@ describe('DungeonRenderer', () => {
|
|||||||
expect(corpse1.destroy).toHaveBeenCalledTimes(1);
|
expect(corpse1.destroy).toHaveBeenCalledTimes(1);
|
||||||
expect(corpse2.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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user