Auto reload last reloadble weapon when reload is triggered
This commit is contained in:
@@ -163,6 +163,7 @@ export type Inventory = {
|
|||||||
export type RunState = {
|
export type RunState = {
|
||||||
stats: Stats;
|
stats: Stats;
|
||||||
inventory: Inventory;
|
inventory: Inventory;
|
||||||
|
lastReloadableWeaponId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BaseActor {
|
export interface BaseActor {
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
public runState: RunState = {
|
public runState: RunState = {
|
||||||
stats: { ...GAME_CONFIG.player.initialStats },
|
stats: { ...GAME_CONFIG.player.initialStats },
|
||||||
inventory: { gold: 0, items: [] }
|
inventory: { gold: 0, items: [] },
|
||||||
|
lastReloadableWeaponId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
public gameInput!: GameInput;
|
public gameInput!: GameInput;
|
||||||
@@ -388,14 +389,16 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.runState = {
|
this.runState = {
|
||||||
stats: { ...p.stats },
|
stats: { ...p.stats },
|
||||||
inventory: { gold: p.inventory.gold, items: [...p.inventory.items] }
|
inventory: { gold: p.inventory.gold, items: [...p.inventory.items] },
|
||||||
|
lastReloadableWeaponId: this.runState.lastReloadableWeaponId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public restartGame() {
|
public restartGame() {
|
||||||
this.runState = {
|
this.runState = {
|
||||||
stats: { ...GAME_CONFIG.player.initialStats },
|
stats: { ...GAME_CONFIG.player.initialStats },
|
||||||
inventory: { gold: 0, items: [] }
|
inventory: { gold: 0, items: [] },
|
||||||
|
lastReloadableWeaponId: null
|
||||||
};
|
};
|
||||||
this.floorIndex = 1;
|
this.floorIndex = 1;
|
||||||
this.loadFloor(this.floorIndex);
|
this.loadFloor(this.floorIndex);
|
||||||
@@ -436,6 +439,8 @@ export class GameScene extends Phaser.Scene {
|
|||||||
if (item.currentAmmo > 0) {
|
if (item.currentAmmo > 0) {
|
||||||
item.currentAmmo--;
|
item.currentAmmo--;
|
||||||
}
|
}
|
||||||
|
// Track as last used reloadable weapon
|
||||||
|
this.runState.lastReloadableWeaponId = item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dungeonRenderer.showProjectile(
|
this.dungeonRenderer.showProjectile(
|
||||||
@@ -483,6 +488,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
if (ammoItem && ammoItem.quantity && ammoItem.quantity > 0) {
|
if (ammoItem && ammoItem.quantity && ammoItem.quantity > 0) {
|
||||||
item.reloadingTurnsLeft = GAME_CONFIG.player.reloadDuration;
|
item.reloadingTurnsLeft = GAME_CONFIG.player.reloadDuration;
|
||||||
|
this.runState.lastReloadableWeaponId = item.id;
|
||||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||||
console.log(`Started reloading ${item.name}. Duration: ${item.reloadingTurnsLeft}`);
|
console.log(`Started reloading ${item.name}. Duration: ${item.reloadingTurnsLeft}`);
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ export class PlayerInputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!weaponToReload && this.scene.runState.lastReloadableWeaponId) {
|
||||||
|
const item = player.inventory.items.find(it => it.id === this.scene.runState.lastReloadableWeaponId);
|
||||||
|
if (item && item.type === "Weapon" && item.weaponType === "ranged") {
|
||||||
|
weaponToReload = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (weaponToReload) {
|
if (weaponToReload) {
|
||||||
this.scene.startReload(player, weaponToReload);
|
this.scene.startReload(player, weaponToReload);
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/scenes/systems/__tests__/ReloadLastWeapon.test.ts
Normal file
145
src/scenes/systems/__tests__/ReloadLastWeapon.test.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
// Mock Phaser
|
||||||
|
vi.mock('phaser', () => {
|
||||||
|
class MockEventEmitter {
|
||||||
|
on = vi.fn().mockReturnThis();
|
||||||
|
once = vi.fn().mockReturnThis();
|
||||||
|
emit = vi.fn().mockReturnThis();
|
||||||
|
off = vi.fn().mockReturnThis();
|
||||||
|
removeAllListeners = vi.fn().mockReturnThis();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: {
|
||||||
|
Events: {
|
||||||
|
EventEmitter: MockEventEmitter
|
||||||
|
},
|
||||||
|
Scene: class {
|
||||||
|
events = new MockEventEmitter();
|
||||||
|
add = {
|
||||||
|
graphics: vi.fn(),
|
||||||
|
sprite: vi.fn(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { PlayerInputHandler } from '../PlayerInputHandler';
|
||||||
|
import { GameScene } from '../../GameScene';
|
||||||
|
import type { CombatantActor, RangedWeaponItem } from '../../../core/types';
|
||||||
|
|
||||||
|
// Minimal mock for GameScene
|
||||||
|
const createMockScene = () => {
|
||||||
|
const scene = {
|
||||||
|
gameInput: {
|
||||||
|
on: vi.fn(),
|
||||||
|
},
|
||||||
|
targetingSystem: {
|
||||||
|
itemId: null,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
entityAccessor: {
|
||||||
|
getPlayer: vi.fn(),
|
||||||
|
},
|
||||||
|
runState: {
|
||||||
|
lastReloadableWeaponId: null,
|
||||||
|
},
|
||||||
|
startReload: vi.fn(),
|
||||||
|
} as any;
|
||||||
|
return scene;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Reload Last Used Weapon Logic', () => {
|
||||||
|
let scene: any;
|
||||||
|
let inputHandler: PlayerInputHandler;
|
||||||
|
let reloadCallback: Function;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scene = createMockScene();
|
||||||
|
inputHandler = new PlayerInputHandler(scene);
|
||||||
|
inputHandler.registerListeners();
|
||||||
|
|
||||||
|
// Find the reload listener
|
||||||
|
const reloadCall = scene.gameInput.on.mock.calls.find((call: any[]) => call[0] === 'reload');
|
||||||
|
reloadCallback = reloadCall[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reload the last reloadable weapon if nothing else is targeted or equipped', () => {
|
||||||
|
const pistol: RangedWeaponItem = {
|
||||||
|
id: 'pistol-1',
|
||||||
|
name: 'Pistol',
|
||||||
|
type: 'Weapon',
|
||||||
|
weaponType: 'ranged',
|
||||||
|
currentAmmo: 0,
|
||||||
|
reloadingTurnsLeft: 0,
|
||||||
|
stats: { attack: 1, range: 5, magazineSize: 6, ammoType: '9mm', projectileSpeed: 10 },
|
||||||
|
textureKey: 'weapons',
|
||||||
|
spriteIndex: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const player: CombatantActor = {
|
||||||
|
id: 1,
|
||||||
|
pos: { x: 0, y: 0 },
|
||||||
|
category: 'combatant',
|
||||||
|
isPlayer: true,
|
||||||
|
type: 'player',
|
||||||
|
inventory: { items: [pistol], gold: 0 },
|
||||||
|
equipment: { mainHand: { type: 'Weapon', weaponType: 'melee', id: 'sword-1' } as any },
|
||||||
|
stats: {} as any,
|
||||||
|
energy: 100,
|
||||||
|
speed: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.entityAccessor.getPlayer.mockReturnValue(player);
|
||||||
|
scene.runState.lastReloadableWeaponId = 'pistol-1';
|
||||||
|
|
||||||
|
// Trigger reload (simulating 'R' press)
|
||||||
|
reloadCallback();
|
||||||
|
|
||||||
|
expect(scene.startReload).toHaveBeenCalledWith(player, pistol);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize targeted item over last used', () => {
|
||||||
|
const pistol1: RangedWeaponItem = { id: 'p1', name: 'P1', type: 'Weapon', weaponType: 'ranged' } as any;
|
||||||
|
const pistol2: RangedWeaponItem = { id: 'p2', name: 'P2', type: 'Weapon', weaponType: 'ranged' } as any;
|
||||||
|
|
||||||
|
const player: CombatantActor = {
|
||||||
|
id: 1, inventory: { items: [pistol1, pistol2] }, equipment: {}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
scene.entityAccessor.getPlayer.mockReturnValue(player);
|
||||||
|
scene.targetingSystem.itemId = 'p2';
|
||||||
|
scene.runState.lastReloadableWeaponId = 'p1';
|
||||||
|
|
||||||
|
reloadCallback();
|
||||||
|
|
||||||
|
expect(scene.startReload).toHaveBeenCalledWith(player, pistol2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize equipped ranged weapon over last used', () => {
|
||||||
|
const pistol1: RangedWeaponItem = { id: 'p1', name: 'P1', type: 'Weapon', weaponType: 'ranged' } as any;
|
||||||
|
const pistol2: RangedWeaponItem = { id: 'p2', name: 'P2', type: 'Weapon', weaponType: 'ranged' } as any;
|
||||||
|
|
||||||
|
const player: CombatantActor = {
|
||||||
|
id: 1, inventory: { items: [pistol1, pistol2] }, equipment: { mainHand: pistol2 }
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
scene.entityAccessor.getPlayer.mockReturnValue(player);
|
||||||
|
scene.runState.lastReloadableWeaponId = 'p1';
|
||||||
|
|
||||||
|
reloadCallback();
|
||||||
|
|
||||||
|
expect(scene.startReload).toHaveBeenCalledWith(player, pistol2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if no weapon is found', () => {
|
||||||
|
const player: CombatantActor = { id: 1, inventory: { items: [] }, equipment: {} } as any;
|
||||||
|
scene.entityAccessor.getPlayer.mockReturnValue(player);
|
||||||
|
|
||||||
|
reloadCallback();
|
||||||
|
|
||||||
|
expect(scene.startReload).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user