Add reload logic for ranged weapons
This commit is contained in:
@@ -38,7 +38,8 @@ export const GAME_CONFIG = {
|
|||||||
passiveNodes: [] as string[]
|
passiveNodes: [] as string[]
|
||||||
},
|
},
|
||||||
speed: 100,
|
speed: 100,
|
||||||
viewRadius: 8
|
viewRadius: 8,
|
||||||
|
reloadDuration: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
map: {
|
map: {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ export function createRangedWeapon(
|
|||||||
textureKey: t.textureKey,
|
textureKey: t.textureKey,
|
||||||
spriteIndex: t.spriteIndex,
|
spriteIndex: t.spriteIndex,
|
||||||
currentAmmo: t.magazineSize,
|
currentAmmo: t.magazineSize,
|
||||||
|
reloadingTurnsLeft: 0,
|
||||||
variant,
|
variant,
|
||||||
stats: {
|
stats: {
|
||||||
attack: t.attack + attackBonus,
|
attack: t.attack + attackBonus,
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export interface RangedWeaponItem extends BaseItem {
|
|||||||
type: "Weapon";
|
type: "Weapon";
|
||||||
weaponType: "ranged";
|
weaponType: "ranged";
|
||||||
currentAmmo: number; // Runtime state - moved to top level for easier access
|
currentAmmo: number; // Runtime state - moved to top level for easier access
|
||||||
|
reloadingTurnsLeft: number;
|
||||||
stats: {
|
stats: {
|
||||||
attack: number;
|
attack: number;
|
||||||
range: number;
|
range: number;
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
createMeleeWeapon,
|
createMeleeWeapon,
|
||||||
createRangedWeapon,
|
createRangedWeapon,
|
||||||
createArmour,
|
createArmour,
|
||||||
createUpgradeScroll
|
createUpgradeScroll,
|
||||||
|
createAmmo
|
||||||
} from "../../core/config/Items";
|
} from "../../core/config/Items";
|
||||||
import { seededRandom } from "../../core/math";
|
import { seededRandom } from "../../core/math";
|
||||||
import * as ROT from "rot-js";
|
import * as ROT from "rot-js";
|
||||||
@@ -60,6 +61,7 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
|||||||
createMeleeWeapon("iron_sword", "sharp"),
|
createMeleeWeapon("iron_sword", "sharp"),
|
||||||
createConsumable("throwing_dagger", 3),
|
createConsumable("throwing_dagger", 3),
|
||||||
createRangedWeapon("pistol"),
|
createRangedWeapon("pistol"),
|
||||||
|
createAmmo("ammo_9mm", 10),
|
||||||
createArmour("leather_armor", "heavy"),
|
createArmour("leather_armor", "heavy"),
|
||||||
createUpgradeScroll(2)
|
createUpgradeScroll(2)
|
||||||
] : [])
|
] : [])
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Reading types.ts to verify actor structure before next step
|
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import {
|
import {
|
||||||
type EntityId,
|
type EntityId,
|
||||||
@@ -6,7 +5,9 @@ import {
|
|||||||
type Action,
|
type Action,
|
||||||
type RunState,
|
type RunState,
|
||||||
type World,
|
type World,
|
||||||
type UIUpdatePayload
|
type UIUpdatePayload,
|
||||||
|
type CombatantActor,
|
||||||
|
type RangedWeaponItem,
|
||||||
} from "../core/types";
|
} from "../core/types";
|
||||||
import { TILE_SIZE } from "../core/constants";
|
import { TILE_SIZE } from "../core/constants";
|
||||||
import { inBounds, isBlocked, isPlayerOnExit, tryDestructTile } from "../engine/world/world-logic";
|
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.input.keyboard?.on("keydown-C", () => {
|
||||||
this.events.emit("toggle-character");
|
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", () => {
|
this.input.keyboard?.on("keydown-SPACE", () => {
|
||||||
if (!this.awaitingPlayer) return;
|
if (!this.awaitingPlayer) return;
|
||||||
@@ -192,29 +220,19 @@ export class GameScene extends Phaser.Scene {
|
|||||||
if (item.type === "Weapon" && item.weaponType === "ranged") {
|
if (item.type === "Weapon" && item.weaponType === "ranged") {
|
||||||
// Check Ammo
|
// Check Ammo
|
||||||
if (item.currentAmmo <= 0) {
|
if (item.currentAmmo <= 0) {
|
||||||
|
if (item.reloadingTurnsLeft > 0) {
|
||||||
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try Reload
|
// Try Reload
|
||||||
const ammoId = `ammo_${item.stats.ammoType}`;
|
this.startReload(player, item);
|
||||||
const ammoItem = player.inventory.items.find(it => it.id === ammoId); // Simple check
|
return;
|
||||||
|
|
||||||
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");
|
// Is it already reloading?
|
||||||
console.log("Reloaded. Ammo:", item.currentAmmo);
|
if (item.reloadingTurnsLeft > 0) {
|
||||||
this.commitPlayerAction({ type: "wait" });
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
||||||
this.emitUIUpdate();
|
|
||||||
} else {
|
|
||||||
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "No Ammo!", "#ff0000");
|
|
||||||
console.log("No ammo found for", item.name);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,6 +615,44 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.awaitingPlayer = false;
|
this.awaitingPlayer = false;
|
||||||
this.cameraController.enableFollowMode();
|
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)
|
// Check for pickups right after move (before enemy turn, so you get it efficiently)
|
||||||
if (action.type === "move") {
|
if (action.type === "move") {
|
||||||
const player = this.entityAccessor.getPlayer();
|
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 } {
|
private getPointerTilePos(pointer: Phaser.Input.Pointer): { x: number, y: number } {
|
||||||
const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y);
|
const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ describe("InventoryUtils", () => {
|
|||||||
textureKey: "items",
|
textureKey: "items",
|
||||||
spriteIndex: 7,
|
spriteIndex: 7,
|
||||||
currentAmmo: 6,
|
currentAmmo: 6,
|
||||||
|
reloadingTurnsLeft: 0,
|
||||||
stats: {
|
stats: {
|
||||||
attack: 8,
|
attack: 8,
|
||||||
range: 5,
|
range: 5,
|
||||||
@@ -315,6 +316,7 @@ describe("InventoryUtils", () => {
|
|||||||
textureKey: "items",
|
textureKey: "items",
|
||||||
spriteIndex: 7,
|
spriteIndex: 7,
|
||||||
currentAmmo: 4,
|
currentAmmo: 4,
|
||||||
|
reloadingTurnsLeft: 0,
|
||||||
stats: {
|
stats: {
|
||||||
attack: 8,
|
attack: 8,
|
||||||
range: 5,
|
range: 5,
|
||||||
|
|||||||
@@ -250,6 +250,18 @@ export class QuickSlotComponent {
|
|||||||
}).setOrigin(1, 1);
|
}).setOrigin(1, 1);
|
||||||
slot.add(display);
|
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 {
|
} else {
|
||||||
this.itemMap[i] = null;
|
this.itemMap[i] = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user