Add reload logic for ranged weapons
This commit is contained in:
@@ -38,7 +38,8 @@ export const GAME_CONFIG = {
|
||||
passiveNodes: [] as string[]
|
||||
},
|
||||
speed: 100,
|
||||
viewRadius: 8
|
||||
viewRadius: 8,
|
||||
reloadDuration: 3,
|
||||
},
|
||||
|
||||
map: {
|
||||
|
||||
@@ -157,6 +157,7 @@ export function createRangedWeapon(
|
||||
textureKey: t.textureKey,
|
||||
spriteIndex: t.spriteIndex,
|
||||
currentAmmo: t.magazineSize,
|
||||
reloadingTurnsLeft: 0,
|
||||
variant,
|
||||
stats: {
|
||||
attack: t.attack + attackBonus,
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface RangedWeaponItem extends BaseItem {
|
||||
type: "Weapon";
|
||||
weaponType: "ranged";
|
||||
currentAmmo: number; // Runtime state - moved to top level for easier access
|
||||
reloadingTurnsLeft: number;
|
||||
stats: {
|
||||
attack: number;
|
||||
range: number;
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
createMeleeWeapon,
|
||||
createRangedWeapon,
|
||||
createArmour,
|
||||
createUpgradeScroll
|
||||
createUpgradeScroll,
|
||||
createAmmo
|
||||
} from "../../core/config/Items";
|
||||
import { seededRandom } from "../../core/math";
|
||||
import * as ROT from "rot-js";
|
||||
@@ -60,6 +61,7 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
||||
createMeleeWeapon("iron_sword", "sharp"),
|
||||
createConsumable("throwing_dagger", 3),
|
||||
createRangedWeapon("pistol"),
|
||||
createAmmo("ammo_9mm", 10),
|
||||
createArmour("leather_armor", "heavy"),
|
||||
createUpgradeScroll(2)
|
||||
] : [])
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Reading types.ts to verify actor structure before next step
|
||||
import Phaser from "phaser";
|
||||
import {
|
||||
type EntityId,
|
||||
@@ -6,7 +5,9 @@ import {
|
||||
type Action,
|
||||
type RunState,
|
||||
type World,
|
||||
type UIUpdatePayload
|
||||
type UIUpdatePayload,
|
||||
type CombatantActor,
|
||||
type RangedWeaponItem,
|
||||
} from "../core/types";
|
||||
import { TILE_SIZE } from "../core/constants";
|
||||
import { inBounds, isBlocked, isPlayerOnExit, tryDestructTile } from "../engine/world/world-logic";
|
||||
@@ -126,6 +127,33 @@ export class GameScene extends Phaser.Scene {
|
||||
this.input.keyboard?.on("keydown-C", () => {
|
||||
this.events.emit("toggle-character");
|
||||
});
|
||||
this.input.keyboard?.on("keydown-R", () => {
|
||||
const player = this.entityAccessor.getPlayer();
|
||||
if (!player || !player.inventory) return;
|
||||
|
||||
// Check for active targeted item first
|
||||
const activeId = this.targetingSystem.itemId;
|
||||
let weaponToReload: RangedWeaponItem | null = null;
|
||||
|
||||
if (activeId) {
|
||||
const item = player.inventory.items.find(it => it.id === activeId);
|
||||
if (item && item.type === "Weapon" && item.weaponType === "ranged") {
|
||||
weaponToReload = item;
|
||||
}
|
||||
}
|
||||
|
||||
// If no active targeted weapon, check main hand
|
||||
if (!weaponToReload && player.equipment?.mainHand) {
|
||||
const item = player.equipment.mainHand;
|
||||
if (item.type === "Weapon" && item.weaponType === "ranged") {
|
||||
weaponToReload = item;
|
||||
}
|
||||
}
|
||||
|
||||
if (weaponToReload) {
|
||||
this.startReload(player, weaponToReload);
|
||||
}
|
||||
});
|
||||
|
||||
this.input.keyboard?.on("keydown-SPACE", () => {
|
||||
if (!this.awaitingPlayer) return;
|
||||
@@ -192,29 +220,19 @@ export class GameScene extends Phaser.Scene {
|
||||
if (item.type === "Weapon" && item.weaponType === "ranged") {
|
||||
// Check Ammo
|
||||
if (item.currentAmmo <= 0) {
|
||||
// Try Reload
|
||||
const ammoId = `ammo_${item.stats.ammoType}`;
|
||||
const ammoItem = player.inventory.items.find(it => it.id === ammoId); // Simple check
|
||||
|
||||
if (ammoItem && ammoItem.quantity && ammoItem.quantity > 0) {
|
||||
const needed = item.stats.magazineSize - item.currentAmmo;
|
||||
const toTake = Math.min(needed, ammoItem.quantity);
|
||||
|
||||
item.currentAmmo += toTake;
|
||||
ammoItem.quantity -= toTake;
|
||||
|
||||
if (ammoItem.quantity <= 0) {
|
||||
player.inventory.items = player.inventory.items.filter(it => it !== ammoItem);
|
||||
}
|
||||
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloaded!", "#00ff00");
|
||||
console.log("Reloaded. Ammo:", item.currentAmmo);
|
||||
this.commitPlayerAction({ type: "wait" });
|
||||
this.emitUIUpdate();
|
||||
} else {
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "No Ammo!", "#ff0000");
|
||||
console.log("No ammo found for", item.name);
|
||||
if (item.reloadingTurnsLeft > 0) {
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try Reload
|
||||
this.startReload(player, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is it already reloading?
|
||||
if (item.reloadingTurnsLeft > 0) {
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,6 +615,44 @@ export class GameScene extends Phaser.Scene {
|
||||
this.awaitingPlayer = false;
|
||||
this.cameraController.enableFollowMode();
|
||||
|
||||
// Process reloading progress
|
||||
const player = this.entityAccessor.getPlayer();
|
||||
if (player && player.inventory) {
|
||||
// Check all items for reloading (usually only equipped or active)
|
||||
for (const item of player.inventory.items) {
|
||||
if (item.type === "Weapon" && item.weaponType === "ranged" && item.reloadingTurnsLeft > 0) {
|
||||
item.reloadingTurnsLeft--;
|
||||
|
||||
if (item.reloadingTurnsLeft === 0) {
|
||||
// Finalize Reload
|
||||
const ammoId = `ammo_${item.stats.ammoType}`;
|
||||
const ammoItem = player.inventory.items.find(it => it.id === ammoId);
|
||||
|
||||
if (ammoItem && ammoItem.quantity && ammoItem.quantity > 0) {
|
||||
const needed = item.stats.magazineSize - item.currentAmmo;
|
||||
const toTake = Math.min(needed, ammoItem.quantity);
|
||||
|
||||
item.currentAmmo += toTake;
|
||||
ammoItem.quantity -= toTake;
|
||||
|
||||
if (ammoItem.quantity <= 0) {
|
||||
player.inventory.items = player.inventory.items.filter(it => it !== ammoItem);
|
||||
}
|
||||
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloaded!", "#00ff00");
|
||||
console.log(`Reloaded ${item.name}. Ammo:`, item.currentAmmo);
|
||||
} else {
|
||||
// Should be checked at startReload, but safe fallback
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "No Ammo!", "#ff0000");
|
||||
}
|
||||
} else {
|
||||
// Show reloading progress
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for pickups right after move (before enemy turn, so you get it efficiently)
|
||||
if (action.type === "move") {
|
||||
const player = this.entityAccessor.getPlayer();
|
||||
@@ -847,6 +903,34 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
private startReload(player: CombatantActor, item: RangedWeaponItem) {
|
||||
if (item.currentAmmo >= item.stats.magazineSize) {
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Full!", "#aaaaaa");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.reloadingTurnsLeft > 0) return;
|
||||
|
||||
const ammoId = `ammo_${item.stats.ammoType}`;
|
||||
const ammoItem = player.inventory?.items.find(it => it.id === ammoId);
|
||||
|
||||
if (ammoItem && ammoItem.quantity && ammoItem.quantity > 0) {
|
||||
item.reloadingTurnsLeft = GAME_CONFIG.player.reloadDuration;
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||
console.log(`Started reloading ${item.name}. Duration: ${item.reloadingTurnsLeft}`);
|
||||
|
||||
if (this.targetingSystem.isActive && this.targetingSystem.itemId === item.id) {
|
||||
this.targetingSystem.cancel();
|
||||
}
|
||||
|
||||
this.commitPlayerAction({ type: "wait" });
|
||||
this.emitUIUpdate();
|
||||
} else {
|
||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "No Ammo!", "#ff0000");
|
||||
console.log("No ammo found for", item.name);
|
||||
}
|
||||
}
|
||||
|
||||
private getPointerTilePos(pointer: Phaser.Input.Pointer): { x: number, y: number } {
|
||||
const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y);
|
||||
return {
|
||||
|
||||
@@ -266,6 +266,7 @@ describe("InventoryUtils", () => {
|
||||
textureKey: "items",
|
||||
spriteIndex: 7,
|
||||
currentAmmo: 6,
|
||||
reloadingTurnsLeft: 0,
|
||||
stats: {
|
||||
attack: 8,
|
||||
range: 5,
|
||||
@@ -315,6 +316,7 @@ describe("InventoryUtils", () => {
|
||||
textureKey: "items",
|
||||
spriteIndex: 7,
|
||||
currentAmmo: 4,
|
||||
reloadingTurnsLeft: 0,
|
||||
stats: {
|
||||
attack: 8,
|
||||
range: 5,
|
||||
|
||||
@@ -250,6 +250,18 @@ export class QuickSlotComponent {
|
||||
}).setOrigin(1, 1);
|
||||
slot.add(display);
|
||||
}
|
||||
|
||||
// Reloading overlay
|
||||
if (foundItem.type === "Weapon" && foundItem.weaponType === "ranged" && foundItem.reloadingTurnsLeft > 0) {
|
||||
const reloadText = this.scene.add.text(slotSize / 2, slotSize / 2, "RELOADING", {
|
||||
fontSize: "8px",
|
||||
color: "#ff0000",
|
||||
fontStyle: "bold",
|
||||
backgroundColor: "#000000aa",
|
||||
padding: { x: 2, y: 1 }
|
||||
}).setOrigin(0.5, 0.5);
|
||||
slot.add(reloadText);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.itemMap[i] = null;
|
||||
|
||||
Reference in New Issue
Block a user