← RPG results

RPG

Qwen 3.5 122b a10b · typescript

Initial view of RPG

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 90,453 bytes · SHA-256 f8cbb14489eb
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quest of the Shattered Realm - Action Adventure RPG</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.min.js"></script>
<style>
/* ── css: layout ── */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #1a1a2e;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  overflow: hidden;
}

#app {
  display: flex;
  justify-content: center;
  align-items: center;
}

canvas {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  border: 4px solid #3d3d5c;
  border-radius: 8px;
  box-shadow: 0 0 40px rgba(0, 0, 0, 0.5), 0 0 100px rgba(92, 107, 192, 0.2);
}
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./player":"src/player.ts","./world":"src/world.ts","./combat":"src/combat.ts","./inventory":"src/inventory.ts","./save":"src/save.ts","./audio":"src/audio.ts","./particles":"src/particles.ts","./ui":"src/ui.ts"},"src/particles.ts":{"./state":"src/state.ts"},"src/world.ts":{"./state":"src/state.ts","./particles":"src/particles.ts","./audio":"src/audio.ts"},"src/player.ts":{"./state":"src/state.ts","./world":"src/world.ts","./audio":"src/audio.ts","./particles":"src/particles.ts"},"src/combat.ts":{"./state":"src/state.ts","./player":"src/player.ts","./audio":"src/audio.ts","./particles":"src/particles.ts"},"src/inventory.ts":{"./state":"src/state.ts"},"src/save.ts":{"./state":"src/state.ts"},"src/ui.ts":{"./state":"src/state.ts","./player":"src/player.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setup = setup;
exports.draw = draw;
exports.keyPressed = keyPressed;
const state_1 = require("./state");
const player_1 = require("./player");
const world_1 = require("./world");
const combat_1 = require("./combat");
const inventory_1 = require("./inventory");
const save_1 = require("./save");
const audio_1 = require("./audio");
const particles_1 = require("./particles");
const ui_1 = require("./ui");
let p;
let gameState;
let cameraX = 0;
let cameraY = 0;
let areaNameTimer = 0;
let tutorialVisible = true;
let tutorialTimer = 600;
function setup() {
    p = this;
    p.createCanvas(state_1.CANVAS_WIDTH, state_1.CANVAS_HEIGHT);
    p.rectMode(p.CORNER);
    p.ellipseMode(p.CENTER);
    p.textAlign(p.LEFT, p.TOP);
    (0, audio_1.initAudio)();
    // Try to load saved game or start new
    const saved = (0, save_1.loadGame)();
    if (saved) {
        gameState = createFromSave(saved);
    }
    else {
        gameState = createNewGame();
    }
}
function createNewGame() {
    const player = (0, player_1.createPlayer)(12 * 32 + 16, 10 * 32 + 16);
    const inventory = (0, inventory_1.createInventory)();
    const world = (0, world_1.generateArea)("village");
    const ui = {
        showInventory: false,
        showShop: false,
        showSaveMenu: false,
        showGameOver: false,
        showVictory: false,
        message: "",
        messageTimer: 0,
        currentScreen: "game",
        selectedSlot: 0
    };
    return { player, inventory, world, ui, saveData: null };
}
function createFromSave(saved) {
    const player = (0, player_1.createPlayer)(saved.player.x, saved.player.y);
    Object.assign(player, saved.player);
    const inventory = (0, inventory_1.createInventory)();
    for (const [key, qty] of Object.entries(saved.inventory.items || {})) {
        inventory.items.set(key, qty);
    }
    inventory.accessories = [...(saved.inventory.accessories || [])];
    inventory.keyItems = [...(saved.inventory.keyItems || [])];
    const world = (0, world_1.generateArea)(saved.world.currentArea || "village");
    // Restore collected pickups
    for (const pickupId of saved.world.collectedPickups || []) {
        const pickup = world.pickups.find(p => p.id === pickupId);
        if (pickup)
            pickup.collected = true;
    }
    // Restore opened treasures
    for (const treasureId of saved.world.openedTreasures || []) {
        const treasure = world.treasures.find(t => t.id === treasureId);
        if (treasure) {
            treasure.collected = true;
            treasure.chestOpen = true;
        }
    }
    // Restore activated save points
    for (const spId of saved.world.activatedSavePoints || []) {
        const sp = world.savePoints.find(s => s.id === spId);
        if (sp) {
            sp.saved = true;
            sp.active = true;
        }
    }
    const ui = {
        showInventory: false,
        showShop: false,
        showSaveMenu: false,
        showGameOver: false,
        showVictory: false,
        message: "",
        messageTimer: 0,
        currentScreen: "game",
        selectedSlot: 0
    };
    return { player, inventory, world, ui, saveData: saved };
}
function draw() {
    p.background(20, 20, 30);
    if (gameState.ui.showGameOver) {
        (0, ui_1.drawGameOver)(p);
        return;
    }
    if (gameState.ui.showVictory) {
        (0, ui_1.drawVictory)(p, gameState.player);
        return;
    }
    if (gameState.ui.currentScreen === "pause") {
        drawPaused();
        return;
    }
    // Update camera to follow player
    updateCamera();
    // Draw world
    (0, world_1.drawWorld)(p, gameState.world, cameraX, cameraY);
    // Draw pickups
    drawPickups();
    // Draw treasures
    drawTreasures();
    // Draw enemies
    for (const enemy of gameState.world.enemies) {
        if (enemy.health > 0) {
            (0, combat_1.drawEnemy)(p, enemy, cameraX, cameraY);
        }
    }
    // Draw player
    (0, player_1.drawPlayer)(p, gameState.player, cameraX, cameraY);
    // Draw projectiles
    drawProjectiles();
    // Update particles
    (0, particles_1.updateParticles)();
    (0, particles_1.drawParticles)(p);
    // Draw HUD
    drawHUD();
    // Draw messages
    drawLocalMessage(gameState.ui.message || "", gameState.ui.messageTimer, 300);
    // Area name display
    if (areaNameTimer > 0) {
        (0, ui_1.drawAreaName)(p, gameState.world.currentArea, areaNameTimer);
        areaNameTimer--;
    }
    // Tutorial
    if (tutorialTimer > 0) {
        tutorialTimer--;
        if (tutorialTimer <= 0) {
            tutorialVisible = false;
        }
        (0, ui_1.drawTutorial)(p, tutorialVisible);
    }
}
function updateCamera() {
    cameraX = gameState.player.x - state_1.CANVAS_WIDTH / 2;
    cameraY = gameState.player.y - state_1.CANVAS_HEIGHT / 2;
    // Clamp camera to world bounds
    const worldWidth = gameState.world.tiles[0].length * 32;
    const worldHeight = gameState.world.tiles.length * 32;
    cameraX = Math.max(0, Math.min(cameraX, worldWidth - state_1.CANVAS_WIDTH));
    cameraY = Math.max(0, Math.min(cameraY, worldHeight - state_1.CANVAS_HEIGHT));
}
function drawPickups() {
    for (const pickup of gameState.world.pickups) {
        if (pickup.collected)
            continue;
        const screenX = pickup.x * 32 - cameraX;
        const screenY = pickup.y * 32 - cameraY;
        // Bobbing animation
        const bob = Math.sin(p.frameCount * 0.1) * 3;
        switch (pickup.type) {
            case "money":
                p.fill(255, 215, 0);
                p.circle(screenX + 16, screenY + 16 + bob, 14);
                p.fill(255, 235, 59);
                p.circle(screenX + 16, screenY + 16 + bob, 10);
                p.fill(255, 215, 0);
                p.textSize(10);
                p.text("$", screenX + 13, screenY + 20 + bob);
                break;
            case "heart_container":
                p.fill(233, 30, 99);
                drawMiniHeart(screenX + 16, screenY + 16 + bob, 16);
                break;
            case "ranged_weapon":
                p.fill(156, 39, 176);
                p.rect(screenX + 8, screenY + 8 + bob, 16, 16, 4);
                p.fill(255, 255, 255);
                p.textSize(12);
                p.text("🏹", screenX + 10, screenY + 20 + bob);
                break;
            case "item":
                p.fill(100, 200, 100);
                p.circle(screenX + 16, screenY + 16 + bob, 14);
                p.fill(255, 255, 255);
                p.textSize(10);
                p.text("?", screenX + 13, screenY + 20 + bob);
                break;
            case "weapon_upgrade":
                p.fill(255, 152, 0);
                p.rect(screenX + 8, screenY + 8 + bob, 16, 16, 4);
                p.fill(255, 255, 0);
                p.textSize(12);
                p.text("↑", screenX + 12, screenY + 20 + bob);
                break;
        }
    }
}
function drawMiniHeart(p, x, y, size) {
    p.push();
    p.translate(x, y);
    p.scale(size / 20);
    p.fill(233, 30, 99);
    p.noStroke();
    p.beginShape();
    p.vertex(0, -6);
    p.bezierVertex(-4, -12, -12, -6, 0, 10);
    p.bezierVertex(12, -6, 4, -12, 0, -6);
    p.endShape();
    p.pop();
}
function drawTreasures() {
    for (const treasure of gameState.world.treasures) {
        if (treasure.collected)
            continue;
        const screenX = treasure.x * 32 - cameraX;
        const screenY = treasure.y * 32 - cameraY;
        p.fill(treasure.chestOpen ? 139 : 184, 69, 19);
        p.rect(screenX + 4, screenY + 10, 24, 16, 2);
        p.fill(139, 69, 19);
        p.rect(screenX + 6, screenY + 10, 20, 4);
    }
}
function drawProjectiles() {
    // Projectiles are handled in combat module
}
function drawHUD() {
    // Health bar
    (0, ui_1.drawHealthBar)(p, gameState.player, 15, 15);
    // Money
    (0, ui_1.drawMoney)(p, gameState.player.money, 15, 50);
    // Weapon indicator
    (0, ui_1.drawWeaponIndicator)(p, gameState.player.weaponLevel, gameState.player.hasRangedWeapon, gameState.player.ammo, 15, 80);
}
function handlePlayerMovement(dt) {
    (0, player_1.handleInput)(gameState.player, p);
    (0, player_1.updatePlayer)(gameState.player, dt);
    // Check door collision
    const doorResult = (0, world_1.handleDoorCollision)(gameState.world, gameState.player.x, gameState.player.y);
    if (doorResult) {
        changeArea(doorResult.destination, doorResult.x, doorResult.y);
    }
    // Check pickup collision
    checkPickupCollision();
    // Check treasure collision
    checkTreasureCollision();
    // Update enemies and check player damage
    const attackResult = (0, combat_1.updateEnemies)(gameState.world, gameState.player.x, gameState.player.y, dt);
    // Check if player took damage from enemies
    for (const enemy of gameState.world.enemies) {
        if (enemy.health > 0) {
            const dx = gameState.player.x - enemy.x;
            const dy = gameState.player.y - enemy.y;
            const dist = Math.sqrt(dx * dx + dy * dy);
            if (dist < 20 && enemy.attackCooldown > 40) { // Enemy just attacked
                (0, player_1.takeDamage)(gameState.player, enemy.damage);
                if (gameState.player.health <= 0) {
                    gameOver();
                }
            }
        }
    }
}
function keyPressed() {
    if (gameState.ui.showGameOver || gameState.ui.showVictory) {
        if (p.keyCode === 82) { // R key
            restartGame();
        }
        return false;
    }
    if (gameState.ui.currentScreen === "pause") {
        handlePauseInput();
        return false;
    }
    if (p.keyCode === 27) { // ESC
        togglePause();
        return false;
    }
    if (p.keyCode === 73) { // I key
        toggleInventory();
        return false;
    }
    if (p.keyCode === 80) { // P key
        toggleSound();
        return false;
    }
    if (gameState.ui.showInventory) {
        handleInventoryInput();
        return false;
    }
    if (gameState.ui.showShop) {
        handleShopInput();
        return false;
    }
    if (gameState.ui.showSaveMenu) {
        handleSaveInput();
        return false;
    }
    // Game controls
    if (p.keyCode === 32) { // Space - Attack
        if (!gameState.player.isAttacking && gameState.player.attackCooldown <= 0) {
            if (gameState.player.hasRangedWeapon && gameState.player.ammo > 0 && p.keyIsDown(p.SHIFT)) {
                const proj = (0, player_1.performRangedAttack)(gameState.player);
                if (proj) {
                    (0, combat_1.addProjectile)(proj.x, proj.y, proj.vx, proj.vy, 3);
                }
            }
            else {
                (0, player_1.performAttack)(gameState.player);
                const hitEnemies = (0, combat_1.handlePlayerAttack)(gameState.world, gameState.player.x, gameState.player.y, gameState.player.facing, gameState.player.weaponLevel) || [];
                // Check for deaths
                for (const enemy of hitEnemies) {
                    const result = (0, combat_1.checkEnemyDeath)(enemy);
                    if (result.dead) {
                        collectEnemyDrop(result.dropMoney, result.dropItem, enemy.x, enemy.y);
                        // Remove dead enemy
                        const idx = gameState.world.enemies.indexOf(enemy);
                        if (idx > -1) {
                            gameState.world.enemies.splice(idx, 1);
                        }
                    }
                }
            }
        }
        return false;
    }
    if (p.keyCode === 69) { // E key - Interact
        handleInteraction();
        return false;
    }
    return false;
}
function handleInteraction() {
    const player = gameState.player;
    const world = gameState.world;
    // Check for save point
    for (const sp of world.savePoints) {
        const dx = player.x - sp.x * 32;
        const dy = player.y - sp.y * 32;
        if (Math.sqrt(dx * dx + dy * dy) < 24) {
            if (!sp.saved) {
                (0, save_1.activateSavePoint)(world, sp.id);
                (0, save_1.saveGame)(player, gameState.inventory, world);
                (0, audio_1.playSound)("save");
                showMessage("Game Saved!");
                (0, particles_1.createExplosion)(sp.x * 32 + 16, sp.y * 32 + 16, "magic", 10);
            }
            else {
                showMessage("Save Point (already saved)");
            }
            return;
        }
    }
    // Check for shop
    for (const shop of world.shops) {
        const dx = player.x - shop.x * 32;
        const dy = player.y - shop.y * 32;
        if (Math.sqrt(dx * dx + dy * dy) < 24) {
            openShop();
            return;
        }
    }
}
function checkPickupCollision() {
    const player = gameState.player;
    for (const pickup of gameState.world.pickups) {
        if (pickup.collected)
            continue;
        const dx = player.x - pickup.x * 32;
        const dy = player.y - pickup.y * 32;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 24) {
            collectPickup(pickup);
        }
    }
}
function collectPickup(pickup) {
    pickup.collected = true;
    (0, audio_1.playSound)("pickup");
    switch (pickup.type) {
        case "money":
            const moneyMult = getAccessoryEffects().moneyBonus;
            const amount = Math.floor((pickup.value || 10) * (1 + moneyMult));
            gameState.player.money += amount;
            (0, particles_1.createExplosion)(pickup.x * 32 + 16, pickup.y * 32 + 16, "coin", 8);
            showMessage(`+${amount} Gold!`);
            break;
        case "heart_container":
            (0, player_1.addHeartContainer)(gameState.player);
            (0, particles_1.createExplosion)(pickup.x * 32 + 16, pickup.y * 32 + 16, "heart", 15);
            showMessage("Heart Container! Max HP +2");
            break;
        case "ranged_weapon":
            gameState.player.hasRangedWeapon = true;
            gameState.player.ammo = 10;
            (0, particles_1.createExplosion)(pickup.x * 32 + 16, pickup.y * 32 + 16, "magic", 15);
            showMessage("Discovered Bow & Arrows!");
            // Check if mid-game reached
            if (!gameState.world.midGameReached) {
                gameState.world.midGameReached = true;
                showMessage("Mid-game reached! New areas unlocked!");
            }
            break;
        case "item":
            if (pickup.itemName) {
                if ((0, inventory_1.isInventoryFull)(gameState.inventory)) {
                    showMessage("Inventory full!");
                    return;
                }
                if ((0, inventory_1.addItem)(gameState.inventory, pickup.itemName)) {
                    (0, particles_1.createExplosion)(pickup.x * 32 + 16, pickup.y * 32 + 16, "spark", 8);
                    showMessage(`Found: ${state_2.ITEMS[pickup.itemName]?.name || pickup.itemName}`);
                }
            }
            break;
        case "weapon_upgrade":
            if (gameState.player.weaponLevel < 2) {
                (0, player_1.upgradeWeapon)(gameState.player);
                (0, particles_1.createExplosion)(pickup.x * 32 + 16, pickup.y * 32 + 16, "magic", 15);
                showMessage(`Weapon Upgraded to Level ${gameState.player.weaponLevel}!`);
            }
            break;
    }
}
function checkTreasureCollision() {
    const player = gameState.player;
    for (const treasure of gameState.world.treasures) {
        if (treasure.collected)
            continue;
        const dx = player.x - treasure.x * 32;
        const dy = player.y - treasure.y * 32;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 24) {
            openTreasure(treasure);
        }
    }
}
function openTreasure(treasure) {
    treasure.collected = true;
    treasure.chestOpen = true;
    (0, audio_1.playSound)("pickup");
    if (treasure.item === "heart_container") {
        (0, player_1.addHeartContainer)(gameState.player);
        showMessage("Heart Container! Max HP +2");
    }
    else if (treasure.item) {
        if ((0, inventory_1.isInventoryFull)(gameState.inventory)) {
            showMessage("Inventory full!");
            return;
        }
        if ((0, inventory_1.addItem)(gameState.inventory, treasure.item)) {
            showMessage(`Found: ${state_2.ITEMS[treasure.item]?.name || treasure.item}`);
        }
    }
    (0, particles_1.createExplosion)(treasure.x * 32 + 16, treasure.y * 32 + 16, "coin", 12);
}
function collectEnemyDrop(money, item, x, y) {
    if (money > 0) {
        gameState.player.money += money;
        (0, particles_1.createExplosion)(x, y, "coin", 6);
    }
    if (item) {
        if (!(0, inventory_1.isInventoryFull)(gameState.inventory)) {
            if ((0, inventory_1.addItem)(gameState.inventory, item)) {
                (0, particles_1.createExplosion)(x, y, "spark", 8);
            }
        }
    }
}
function changeArea(area, x, y) {
    (0, audio_1.playSound)("door_open");
    gameState.world = (0, world_1.generateArea)(area);
    gameState.player.x = x;
    gameState.player.y = y;
    areaNameTimer = 120;
    // Show area name
    const areaNames = {
        "village": "Peaceful Village",
        "forest": "Dark Forest",
        "caves": "Mysterious Caves",
        "temple": "Ancient Temple",
        "boss_room": "Boss Arena"
    };
    if (area === "boss_room") {
        // Check victory condition after boss defeat
    }
}
function getAccessoryEffects() {
    let defense = 0;
    let speed = 0;
    let moneyBonus = 0;
    for (const acc of gameState.inventory.accessories) {
        switch (acc) {
            case "silver_ring":
                defense += 1;
                break;
            case "speed_boots":
                speed += 0.1;
                break;
            case "lucky_charm":
                moneyBonus += 0.1;
                break;
            case "iron_shield":
                defense += 2;
                break;
        }
    }
    return { defense, speed, moneyBonus };
}
function showMessage(msg) {
    gameState.ui.message = msg;
    gameState.ui.messageTimer = 120;
}
function drawLocalMessage(msg, timer, maxWidth) {
    if (timer <= 0 || !msg)
        return;
    const alpha = Math.min(255, timer * 5);
    p.push();
    p.fill(0, 0, 0, alpha);
    p.rectMode(p.CENTER);
    p.rect(p.width / 2, p.height - 60, maxWidth + 20, 30, 8);
    p.fill(255, 255, 255, alpha);
    p.textAlign(p.CENTER);
    p.textSize(14);
    p.text(msg, p.width / 2, p.height - 55);
    p.rectMode(p.CORNER);
    p.pop();
}
// UI State management
function togglePause() {
    if (gameState.ui.currentScreen === "pause") {
        gameState.ui.currentScreen = "game";
    }
    else {
        gameState.ui.currentScreen = "pause";
    }
}
function drawPaused() {
    (0, ui_1.drawPauseMenu)(p, state_1.CANVAS_WIDTH / 2 - 120, state_1.CANVAS_HEIGHT / 2 - 80, 240, 160, 0);
}
function handlePauseInput() {
    if (p.keyCode === 27) { // ESC
        togglePause();
    }
}
function toggleInventory() {
    gameState.ui.showInventory = !gameState.ui.showInventory;
    gameState.ui.showShop = false;
    gameState.ui.showSaveMenu = false;
}
function handleInventoryInput() {
    if (p.keyCode === p.UP || p.keyCode === 87) {
        gameState.ui.selectedSlot = Math.max(0, gameState.ui.selectedSlot - 1);
    }
    if (p.keyCode === p.DOWN || p.keyCode === 83) {
        const totalSlots = (0, inventory_1.getTotalItemCount)(gameState.inventory);
        gameState.ui.selectedSlot = Math.min(totalSlots - 1, gameState.ui.selectedSlot + 1);
    }
    if (p.keyCode === 13) { // Enter - use item
        // Simplified: just consume first potion found
        if ((0, inventory_1.hasItem)(gameState.inventory, "potion")) {
            const result = (0, inventory_1.useItem)(gameState.inventory, "potion");
            if (result.used && result.effect === "heal_4") {
                (0, player_1.heal)(gameState.player, 4);
                showMessage("Used Health Potion (+4 HP)");
            }
        }
    }
}
function openShop() {
    gameState.ui.showShop = true;
    gameState.ui.showInventory = false;
    gameState.ui.selectedSlot = 0;
}
function handleShopInput() {
    const shop = gameState.world.shops[0];
    if (!shop)
        return;
    if (p.keyCode === p.UP || p.keyCode === 87) {
        gameState.ui.selectedSlot = Math.max(0, gameState.ui.selectedSlot - 1);
    }
    if (p.keyCode === p.DOWN || p.keyCode === 83) {
        gameState.ui.selectedSlot = Math.min(shop.items.length - 1, gameState.ui.selectedSlot + 1);
    }
    if (p.keyCode === 13) { // Enter - buy
        const item = shop.items[gameState.ui.selectedSlot];
        if (item && gameState.player.money >= item.price) {
            gameState.player.money -= item.price;
            (0, audio_1.playSound)("shop_buy");
            if (item.itemType === "potion") {
                (0, inventory_1.addItem)(gameState.inventory, "potion");
                showMessage("Bought Health Potion");
            }
            else if (state_2.ITEMS[item.itemType]) {
                if ((0, inventory_1.isInventoryFull)(gameState.inventory)) {
                    showMessage("Inventory full!");
                    return;
                }
                (0, inventory_1.addItem)(gameState.inventory, item.itemType);
                showMessage(`Bought ${item.name}`);
            }
        }
        else {
            showMessage("Not enough gold!");
        }
    }
}
function handleSaveInput() {
    if (p.keyCode === 13) { // Enter - save
        (0, save_1.saveGame)(gameState.player, gameState.inventory, gameState.world);
        (0, audio_1.playSound)("save");
        showMessage("Game Saved!");
        gameState.ui.showSaveMenu = false;
    }
}
function toggleSound() {
    const currentlyEnabled = true; // Would track actual state
    (0, audio_1.setEnabled)(!currentlyEnabled);
    showMessage(currentlyEnabled ? "Sound OFF" : "Sound ON");
}
function restartGame() {
    localStorage.removeItem("rpg_save_data");
    gameState = createNewGame();
    cameraX = 0;
    cameraY = 0;
    areaNameTimer = 120;
    tutorialTimer = 600;
    tutorialVisible = true;
}
function gameOver() {
    (0, audio_1.playSound)("gameover");
    gameState.ui.showGameOver = true;
    gameState.ui.currentScreen = "gameover";
}
function checkVictory() {
    // Check if boss is defeated and all heart containers collected
    const bossAlive = gameState.world.enemies.some(e => e.type === "boss" && e.health > 0);
    const allHearts = gameState.player.heartContainers >= 5;
    if (!bossAlive && allHearts && gameState.world.currentArea === "boss_room") {
        (0, audio_1.playSound)("victory");
        gameState.ui.showVictory = true;
        gameState.ui.currentScreen = "victory";
        gameState.world.gameCompleted = true;
    }
}
// Import ITEMS for reference
const state_2 = require("./state");
};

// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// Global game state and constants
Object.defineProperty(exports, "__esModule", { value: true });
exports.TOTAL_AREAS = exports.areaProgress = exports.gameTime = exports.ENEMY_TYPES = exports.ITEMS = exports.WEAPON_UPGRADES = exports.CANVAS_HEIGHT = exports.CANVAS_WIDTH = exports.TILE_SIZE = void 0;
exports.TILE_SIZE = 32;
exports.CANVAS_WIDTH = 800;
exports.CANVAS_HEIGHT = 600;
exports.WEAPON_UPGRADES = [
    {
        name: "Sharpened Blade",
        description: "A sharper edge for increased damage",
        damageBonus: 2,
        attackSpeedBonus: 0.2,
        rangeBonus: 4,
        specialAbility: "Faster attacks",
        price: 150,
        requiresItem: "Whetstone"
    },
    {
        name: "Flame Edge",
        description: "Enchanted with fire, burns enemies",
        damageBonus: 4,
        attackSpeedBonus: 0.4,
        rangeBonus: 8,
        specialAbility: "Fire damage over time",
        price: 300,
        requiresItem: "Fire Essence"
    }
];
exports.ITEMS = {
    "potion": { name: "Health Potion", description: "Restores 4 HP", type: "consumable" },
    "whetstone": { name: "Whetstone", description: "Required for first weapon upgrade", type: "key_item" },
    "fire_essence": { name: "Fire Essence", description: "Required for second weapon upgrade", type: "key_item" },
    "silver_ring": { name: "Silver Ring", description: "Accessory: +1 defense", type: "accessory" },
    "speed_boots": { name: "Speed Boots", description: "Accessory: +10% movement speed", type: "accessory" },
    "lucky_charm": { name: "Lucky Charm", description: "Accessory: +10% money gain", type: "accessory" },
    "iron_shield": { name: "Iron Shield", description: "Accessory: Blocks ranged attacks", type: "accessory" },
    "ancient_scroll": { name: "Ancient Scroll", description: "Key Item: Opens the shrine", type: "key_item" }
};
exports.ENEMY_TYPES = {
    "slime": { health: 4, damage: 1, speed: 0.5, money: 10, color: [76, 175, 80] },
    "bat": { health: 3, damage: 1, speed: 1.5, money: 8, color: [121, 85, 72] },
    "skeleton": { health: 6, damage: 2, speed: 0.8, money: 20, color: [236, 240, 241] },
    "guard": { health: 10, damage: 3, speed: 0.6, money: 35, color: [92, 107, 192] },
    "boss": { health: 30, damage: 5, speed: 0.4, money: 100, color: [183, 28, 28] }
};
// Game progress tracking
exports.gameTime = 0;
exports.areaProgress = 0;
exports.TOTAL_AREAS = 5;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// Audio system using Web Audio API for synthesized sounds
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAudio = initAudio;
exports.setEnabled = setEnabled;
exports.playSound = playSound;
let audioContext = null;
let enabled = true;
function initAudio() {
    if (!audioContext) {
        audioContext = new (window.AudioContext || window.webkitAudioContext)();
    }
}
function setEnabled(value) {
    enabled = value;
}
function playTone(frequency, duration, type, volume, attack = 0.01, decay = 0.1) {
    if (!enabled || !audioContext)
        return;
    const oscillator = audioContext.createOscillator();
    const gainNode = audioContext.createGain();
    oscillator.type = type;
    oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
    gainNode.gain.setValueAtTime(0, audioContext.currentTime);
    gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + attack);
    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration - decay);
    oscillator.connect(gainNode);
    gainNode.connect(audioContext.destination);
    oscillator.start(audioContext.currentTime);
    oscillator.stop(audioContext.currentTime + duration);
}
function playNoise(duration, volume) {
    if (!enabled || !audioContext)
        return;
    const bufferSize = audioContext.sampleRate * duration;
    const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) {
        data[i] = Math.random() * 2 - 1;
    }
    const noise = audioContext.createBufferSource();
    noise.buffer = buffer;
    const gainNode = audioContext.createGain();
    gainNode.gain.setValueAtTime(volume, audioContext.currentTime);
    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration);
    noise.connect(gainNode);
    gainNode.connect(audioContext.destination);
    noise.start();
}
function playSound(type) {
    switch (type) {
        case "step":
            playTone(80, 0.05, "triangle", 0.1);
            break;
        case "attack":
            playTone(300, 0.1, "sawtooth", 0.2, 0.01, 0.08);
            playTone(200, 0.1, "square", 0.15, 0.01, 0.08);
            break;
        case "hit":
            playNoise(0.15, 0.3);
            playTone(100, 0.1, "sawtooth", 0.25);
            break;
        case "enemy_hit":
            playTone(150, 0.1, "square", 0.3);
            playTone(100, 0.15, "sawtooth", 0.25);
            break;
        case "pickup":
            playTone(600, 0.1, "sine", 0.3);
            setTimeout(() => playTone(800, 0.15, "sine", 0.3), 50);
            break;
        case "coin":
            playTone(1200, 0.1, "sine", 0.2);
            setTimeout(() => playTone(1600, 0.15, "sine", 0.25), 40);
            setTimeout(() => playTone(2000, 0.2, "sine", 0.3), 80);
            break;
        case "heart":
            playTone(400, 0.15, "sine", 0.3);
            setTimeout(() => playTone(600, 0.2, "sine", 0.35), 100);
            break;
        case "upgrade":
            playTone(300, 0.1, "sine", 0.2);
            setTimeout(() => playTone(400, 0.1, "sine", 0.2), 80);
            setTimeout(() => playTone(500, 0.1, "sine", 0.2), 160);
            setTimeout(() => playTone(700, 0.3, "sine", 0.3), 240);
            break;
        case "shop_buy":
            playTone(800, 0.1, "sine", 0.25);
            setTimeout(() => playTone(1000, 0.15, "sine", 0.3), 60);
            break;
        case "save":
            playTone(400, 0.1, "sine", 0.2);
            setTimeout(() => playTone(500, 0.1, "sine", 0.2), 100);
            setTimeout(() => playTone(600, 0.1, "sine", 0.2), 200);
            setTimeout(() => playTone(800, 0.3, "sine", 0.3), 300);
            break;
        case "door_open":
            playNoise(0.2, 0.15);
            playTone(100, 0.2, "sine", 0.2);
            break;
        case "damage":
            playTone(150, 0.2, "sawtooth", 0.35);
            playTone(100, 0.25, "square", 0.3);
            break;
        case "ranged_attack":
            playTone(600, 0.15, "sine", 0.25);
            playTone(800, 0.1, "sine", 0.2);
            break;
        case "projectile_hit":
            playNoise(0.1, 0.25);
            playTone(400, 0.08, "sawtooth", 0.2);
            break;
        case "victory":
            for (let i = 0; i < 5; i++) {
                setTimeout(() => playTone(400 + i * 100, 0.2, "sine", 0.3), i * 100);
            }
            setTimeout(() => playTone(800, 0.4, "sine", 0.4), 500);
            break;
        case "gameover":
            playTone(300, 0.3, "sawtooth", 0.3);
            setTimeout(() => playTone(250, 0.3, "sawtooth", 0.3), 200);
            setTimeout(() => playTone(200, 0.4, "sawtooth", 0.3), 400);
            break;
    }
}
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createParticle = createParticle;
exports.createExplosion = createExplosion;
exports.updateParticles = updateParticles;
exports.drawParticles = drawParticles;
exports.clearParticles = clearParticles;
exports.getParticleCount = getParticleCount;
const particles = [];
function createParticle(x, y, type, vx = (Math.random() - 0.5) * 4, vy = (Math.random() - 0.5) * 4, life = 30, size = 4) {
    const colors = {
        "spark": [255, 235, 59],
        "dust": [189, 189, 189],
        "blood": [211, 47, 47],
        "magic": [156, 39, 176],
        "coin": [255, 215, 0],
        "heart": [233, 30, 99]
    };
    particles.push({
        x,
        y,
        vx,
        vy,
        life,
        maxLife: life,
        size,
        color: colors[type],
        type
    });
}
function createExplosion(x, y, type, count) {
    for (let i = 0; i < count; i++) {
        const angle = (Math.PI * 2 / count) * i;
        const speed = 2 + Math.random() * 2;
        createParticle(x, y, type, Math.cos(angle) * speed, Math.sin(angle) * speed, 20 + Math.random() * 15, 3 + Math.random() * 3);
    }
}
function updateParticles() {
    for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.life--;
        // Apply gravity to some types
        if (p.type === "dust" || p.type === "coin") {
            p.vy += 0.1;
        }
        // Friction
        p.vx *= 0.95;
        p.vy *= 0.95;
        if (p.life <= 0) {
            particles.splice(i, 1);
        }
    }
}
function drawParticles(p) {
    for (const particle of particles) {
        const alpha = particle.life / particle.maxLife;
        p.push();
        p.noStroke();
        p.fill(p.color[0], p.color[1], p.color[2], alpha * 255);
        if (particle.type === "heart") {
            drawHeart(p, particle.x, particle.y, particle.size * alpha);
        }
        else {
            p.circle(p.x, p.y, particle.size * alpha);
        }
        p.pop();
    }
}
function drawHeart(p, x, y, size) {
    p.push();
    p.translate(x, y);
    p.scale(size / 10);
    p.beginShape();
    p.vertex(0, -3);
    p.bezierVertex(-2, -6, -6, -3, 0, 5);
    p.bezierVertex(6, -3, 2, -6, 0, -3);
    p.endShape();
    p.pop();
}
function clearParticles() {
    particles.length = 0;
}
function getParticleCount() {
    return particles.length;
}
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TILE = void 0;
exports.generateArea = generateArea;
exports.getTileAt = getTileAt;
exports.checkCollision = checkCollision;
exports.drawWorld = drawWorld;
exports.handleDoorCollision = handleDoorCollision;
const state_1 = require("./state");
// Tile types
exports.TILE = {
    EMPTY: 0,
    WALL: 1,
    FLOOR: 2,
    WATER: 3,
    GRASS: 4,
    SAVE_POINT: 5,
    SHOP: 6,
    DOOR_NORTH: 7,
    DOOR_SOUTH: 8,
    DOOR_WEST: 9,
    DOOR_EAST: 10,
    CHEST: 11,
    BOSS_DOOR: 12
};
const AREAS = {
    "village": {
        name: "Peaceful Village",
        width: 25,
        height: 20,
        tileMap: [],
        enemies: [
            { type: "slime", x: 5, y: 5, health: 4, maxHealth: 4, damage: 1, speed: 0.5, patrolStartX: 5, patrolStartY: 5, patrolRadius: 3, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 10 },
            { type: "slime", x: 18, y: 8, health: 4, maxHealth: 4, damage: 1, speed: 0.5, patrolStartX: 18, patrolStartY: 8, patrolRadius: 4, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 10 }
        ],
        pickups: [
            { type: "money", x: 10, y: 10, value: 25, collected: false },
            { type: "item", x: 3, y: 15, itemName: "potion", collected: false }
        ],
        shops: [
            { x: 20, y: 5, items: [
                    { name: "Health Potion", description: "Restores 4 HP", price: 20, itemType: "potion" },
                    { name: "Silver Ring", description: "+1 defense", price: 100, itemType: "silver_ring" },
                    { name: "Speed Boots", description: "+10% movement", price: 150, itemType: "speed_boots" }
                ], open: true }
        ],
        savePoints: [{ x: 12, y: 3, saved: false, active: true }],
        doors: [
            { x: 12, y: 18, width: 2, height: 1, destination: "forest", destinationX: 12, destinationY: 2, locked: false }
        ],
        treasures: [
            { x: 2, y: 5, item: "whetstone", value: 50, collected: false, chestOpen: false }
        ]
    },
    "forest": {
        name: "Dark Forest",
        width: 30,
        height: 25,
        tileMap: [],
        enemies: [
            { type: "bat", x: 8, y: 8, health: 3, maxHealth: 3, damage: 1, speed: 1.5, patrolStartX: 8, patrolStartY: 8, patrolRadius: 5, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 8 },
            { type: "bat", x: 20, y: 12, health: 3, maxHealth: 3, damage: 1, speed: 1.5, patrolStartX: 20, patrolStartY: 12, patrolRadius: 4, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 8 },
            { type: "skeleton", x: 15, y: 18, health: 6, maxHealth: 6, damage: 2, speed: 0.8, patrolStartX: 15, patrolStartY: 18, patrolRadius: 6, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 20 }
        ],
        pickups: [
            { type: "money", x: 5, y: 5, value: 30, collected: false },
            { type: "money", x: 25, y: 20, value: 50, collected: false },
            { type: "item", x: 10, y: 22, itemName: "lucky_charm", collected: false }
        ],
        shops: [],
        savePoints: [{ x: 15, y: 5, saved: false, active: true }],
        doors: [
            { x: 15, y: 23, width: 2, height: 1, destination: "caves", destinationX: 15, destinationY: 2, locked: false },
            { x: 2, y: 12, width: 1, height: 2, destination: "village", destinationX: 23, destinationY: 12, locked: false }
        ],
        treasures: [
            { x: 28, y: 8, item: "fire_essence", value: 100, collected: false, chestOpen: false },
            { x: 5, y: 18, item: "iron_shield", value: 75, collected: false, chestOpen: false }
        ]
    },
    "caves": {
        name: "Mysterious Caves",
        width: 25,
        height: 20,
        tileMap: [],
        enemies: [
            { type: "skeleton", x: 6, y: 6, health: 6, maxHealth: 6, damage: 2, speed: 0.8, patrolStartX: 6, patrolStartY: 6, patrolRadius: 4, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 20 },
            { type: "skeleton", x: 18, y: 10, health: 6, maxHealth: 6, damage: 2, speed: 0.8, patrolStartX: 18, patrolStartY: 10, patrolRadius: 5, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 20 },
            { type: "guard", x: 12, y: 15, health: 10, maxHealth: 10, damage: 3, speed: 0.6, patrolStartX: 12, patrolStartY: 15, patrolRadius: 7, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 35 }
        ],
        pickups: [
            { type: "money", x: 8, y: 12, value: 40, collected: false },
            { type: "money", x: 16, y: 6, value: 60, collected: false },
            { type: "item", x: 20, y: 16, itemName: "ancient_scroll", collected: false }
        ],
        shops: [
            { x: 4, y: 4, items: [
                    { name: "Health Potion", description: "Restores 4 HP", price: 20, itemType: "potion" },
                    { name: "Lucky Charm", description: "+10% money gain", price: 200, itemType: "lucky_charm" },
                    { name: "Iron Shield", description: "Blocks ranged attacks", price: 180, itemType: "iron_shield" }
                ], open: true }
        ],
        savePoints: [{ x: 12, y: 3, saved: false, active: true }],
        doors: [
            { x: 12, y: 18, width: 2, height: 1, destination: "temple", destinationX: 12, destinationY: 2, requiredItem: "ancient_scroll", locked: true }
        ],
        treasures: [
            { x: 2, y: 10, item: "silver_ring", value: 100, collected: false, chestOpen: false }
        ]
    },
    "temple": {
        name: "Ancient Temple",
        width: 30,
        height: 25,
        tileMap: [],
        enemies: [
            { type: "guard", x: 8, y: 8, health: 10, maxHealth: 10, damage: 3, speed: 0.6, patrolStartX: 8, patrolStartY: 8, patrolRadius: 5, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 35 },
            { type: "guard", x: 20, y: 10, health: 10, maxHealth: 10, damage: 3, speed: 0.6, patrolStartX: 20, patrolStartY: 10, patrolRadius: 6, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 35 },
            { type: "guard", x: 15, y: 18, health: 10, maxHealth: 10, damage: 3, speed: 0.6, patrolStartX: 15, patrolStartY: 18, patrolRadius: 4, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 35 }
        ],
        pickups: [
            { type: "money", x: 10, y: 10, value: 75, collected: false },
            { type: "money", x: 20, y: 18, value: 100, collected: false },
            { type: "ranged_weapon", x: 15, y: 12, value: 0, collected: false }
        ],
        shops: [],
        savePoints: [{ x: 15, y: 4, saved: false, active: true }],
        doors: [
            { x: 15, y: 23, width: 2, height: 1, destination: "boss_room", destinationX: 15, destinationY: 3, locked: false }
        ],
        treasures: [
            { x: 25, y: 8, item: "speed_boots", value: 150, collected: false, chestOpen: false },
            { x: 5, y: 18, item: "heart_container", value: 200, collected: false, chestOpen: false }
        ]
    },
    "boss_room": {
        name: "Boss Arena",
        width: 20,
        height: 15,
        tileMap: [],
        enemies: [
            { type: "boss", x: 10, y: 8, health: 30, maxHealth: 30, damage: 5, speed: 0.4, patrolStartX: 10, patrolStartY: 8, patrolRadius: 8, direction: 0, attackCooldown: 0, isAggro: false, dropMoney: 100, dropItem: "heart_container" }
        ],
        pickups: [
            { type: "money", x: 5, y: 5, value: 100, collected: false },
            { type: "money", x: 15, y: 10, value: 100, collected: false },
            { type: "heart_container", x: 10, y: 11, value: 500, collected: false }
        ],
        shops: [],
        savePoints: [],
        doors: [
            { x: 9, y: 13, width: 2, height: 1, destination: "temple", destinationX: 9, destinationY: 12, locked: false }
        ],
        treasures: []
    }
};
function generateArea(areaName) {
    const config = AREAS[areaName];
    if (!config)
        throw new Error(`Unknown area: ${areaName}`);
    // Generate tile map with borders and some obstacles
    const tiles = [];
    for (let y = 0; y < config.height; y++) {
        const row = [];
        for (let x = 0; x < config.width; x++) {
            if (x === 0 || x === config.width - 1 || y === 0 || y === config.height - 1) {
                row.push(exports.TILE.WALL);
            }
            else {
                row.push(Math.random() < 0.1 ? exports.TILE.WALL : exports.TILE.FLOOR);
            }
        }
        tiles.push(row);
    }
    // Clear paths for doors
    for (const door of config.doors) {
        const dh = door.height || 1;
        const dw = door.width || 1;
        const dx0 = door.x || 0;
        const dy0 = door.y || 0;
        for (let dy = 0; dy < dh; dy++) {
            for (let dx = 0; dx < dw; dx++) {
                const tx = Math.floor(dx0 + dx);
                const ty = Math.floor(dy0 + dy);
                if (tx > 0 && tx < config.width - 1 && ty > 0 && ty < config.height - 1) {
                    tiles[ty][tx] = exports.TILE.FLOOR;
                }
            }
        }
    }
    const world = {
        tiles,
        enemies: config.enemies.map((e, i) => ({ ...e, id: `${areaName}_enemy_${i}`, type: e.type })),
        pickups: config.pickups.map((p, i) => ({ ...p, id: `${areaName}_pickup_${i}`, collected: false, type: p.type })),
        shops: config.shops.map((s, i) => ({ ...s, id: `${areaName}_shop_${i}`, x: s.x, y: s.y, items: s.items || [], open: s.open !== false })),
        savePoints: config.savePoints.map((s, i) => ({ ...s, id: `${areaName}_save_${i}`, saved: false, active: true, x: s.x, y: s.y })),
        doors: config.doors.map((d, i) => ({ ...d, id: `${areaName}_door_${i}`, x: d.x, y: d.y, width: d.width, height: d.height, destination: d.destination, destinationX: d.destinationX, destinationY: d.destinationY, locked: d.locked !== false })),
        treasures: config.treasures.map((t, i) => ({ ...t, id: `${areaName}_treasure_${i}`, collected: false, chestOpen: false, x: t.x, y: t.y, item: t.item, value: t.value })),
        currentArea: areaName,
        areasUnlocked: [areaName],
        midGameReached: false,
        gameCompleted: false
    };
    return world;
}
function getTileAt(world, x, y) {
    const tx = Math.floor(x / state_1.TILE_SIZE);
    const ty = Math.floor(y / state_1.TILE_SIZE);
    if (ty < 0 || ty >= world.tiles.length || tx < 0 || tx >= world.tiles[0].length) {
        return exports.TILE.WALL;
    }
    return world.tiles[ty][tx];
}
function checkCollision(world, x, y, radius = 8) {
    const points = [
        { x: x - radius, y: y - radius },
        { x: x + radius, y: y - radius },
        { x: x - radius, y: y + radius },
        { x: x + radius, y: y + radius }
    ];
    for (const p of points) {
        const tile = getTileAt(world, p.x, p.y);
        if (tile === exports.TILE.WALL || tile === exports.TILE.WATER) {
            return true;
        }
    }
    return false;
}
function drawWorld(p, world, cameraX, cameraY) {
    const startX = Math.floor(cameraX / state_1.TILE_SIZE);
    const startY = Math.floor(cameraY / state_1.TILE_SIZE);
    const endX = startX + Math.ceil(p.width / state_1.TILE_SIZE) + 1;
    const endY = startY + Math.ceil(p.height / state_1.TILE_SIZE) + 1;
    for (let y = Math.max(0, startY); y < Math.min(world.tiles.length, endY); y++) {
        for (let x = Math.max(0, startX); x < Math.min(world.tiles[0].length, endX); x++) {
            const tile = world.tiles[y][x];
            const screenX = x * state_1.TILE_SIZE - cameraX;
            const screenY = y * state_1.TILE_SIZE - cameraY;
            switch (tile) {
                case exports.TILE.EMPTY:
                    p.fill(30, 30, 40);
                    p.rect(screenX, screenY, state_1.TILE_SIZE, state_1.TILE_SIZE);
                    break;
                case exports.TILE.WALL:
                    p.fill(80, 80, 90);
                    p.rect(screenX, screenY, state_1.TILE_SIZE, state_1.TILE_SIZE);
                    p.fill(60, 60, 70);
                    p.rect(screenX + 2, screenY + 2, state_1.TILE_SIZE - 4, state_1.TILE_SIZE - 4);
                    break;
                case exports.TILE.FLOOR:
                    p.fill(50, 50, 60);
                    p.rect(screenX, screenY, state_1.TILE_SIZE, state_1.TILE_SIZE);
                    if ((x + y) % 3 === 0) {
                        p.fill(60, 60, 70);
                        p.rect(screenX + 8, screenY + 8, 4, 4);
                    }
                    break;
                case exports.TILE.WATER:
                    p.fill(52, 152, 219);
                    p.rect(screenX, screenY, state_1.TILE_SIZE, state_1.TILE_SIZE);
                    break;
                case exports.TILE.GRASS:
                    p.fill(76, 175, 80);
                    p.rect(screenX, screenY, state_1.TILE_SIZE, state_1.TILE_SIZE);
                    break;
            }
        }
    }
    // Draw save points
    for (const sp of world.savePoints) {
        const screenX = sp.x * state_1.TILE_SIZE - cameraX;
        const screenY = sp.y * state_1.TILE_SIZE - cameraY;
        p.fill(156, 39, 176);
        p.circle(screenX + state_1.TILE_SIZE / 2, screenY + state_1.TILE_SIZE / 2, 20);
        p.fill(255, 255, 255);
        p.textSize(12);
        p.text("S", screenX + state_1.TILE_SIZE / 2 - 4, screenY + state_1.TILE_SIZE / 2 + 4);
    }
    // Draw shops
    for (const shop of world.shops) {
        const screenX = shop.x * state_1.TILE_SIZE - cameraX;
        const screenY = shop.y * state_1.TILE_SIZE - cameraY;
        p.fill(255, 193, 7);
        p.rect(screenX + 4, screenY + 4, state_1.TILE_SIZE - 8, state_1.TILE_SIZE - 8);
        p.fill(0, 0, 0);
        p.textSize(12);
        p.text("$", screenX + state_1.TILE_SIZE / 2 - 4, screenY + state_1.TILE_SIZE / 2 + 4);
    }
    // Draw doors
    for (const door of world.doors) {
        const screenX = door.x * state_1.TILE_SIZE - cameraX;
        const screenY = door.y * state_1.TILE_SIZE - cameraY;
        p.fill(door.locked ? 128 : 64, 0, 64);
        p.rect(screenX, screenY, door.width * state_1.TILE_SIZE, door.height * state_1.TILE_SIZE);
        if (door.locked) {
            p.fill(255, 215, 0);
            p.textSize(10);
            p.text("L", screenX + 4, screenY + 16);
        }
    }
    // Draw treasures
    for (const treasure of world.treasures) {
        if (treasure.collected)
            continue;
        const screenX = treasure.x * state_1.TILE_SIZE - cameraX;
        const screenY = treasure.y * state_1.TILE_SIZE - cameraY;
        p.fill(treasure.chestOpen ? 139 : 184, 69, 19);
        p.rect(screenX + 4, screenY + 8, 24, 16);
        p.fill(139, 69, 19);
        p.rect(screenX + 6, screenY + 8, 20, 4);
    }
}
function handleDoorCollision(world, playerX, playerY) {
    for (const door of world.doors) {
        if (playerX >= door.x * state_1.TILE_SIZE &&
            playerX <= (door.x + door.width) * state_1.TILE_SIZE &&
            playerY >= door.y * state_1.TILE_SIZE &&
            playerY <= (door.y + door.height) * state_1.TILE_SIZE) {
            if (door.locked && door.requiredItem) {
                continue;
            }
            return {
                destination: door.destination,
                x: door.destinationX * state_1.TILE_SIZE + state_1.TILE_SIZE / 2,
                y: door.destinationY * state_1.TILE_SIZE + state_1.TILE_SIZE / 2
            };
        }
    }
    return undefined;
}
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.updatePlayer = updatePlayer;
exports.handleInput = handleInput;
exports.performAttack = performAttack;
exports.performRangedAttack = performRangedAttack;
exports.takeDamage = takeDamage;
exports.heal = heal;
exports.addHeartContainer = addHeartContainer;
exports.upgradeWeapon = upgradeWeapon;
exports.getAttackRange = getAttackRange;
exports.getAttackDamage = getAttackDamage;
exports.getAttackCooldown = getAttackCooldown;
exports.drawPlayer = drawPlayer;
exports.getWeaponStats = getWeaponStats;
const world_1 = require("./world");
const audio_1 = require("./audio");
const particles_1 = require("./particles");
let stepTimer = 0;
let lastStepSound = 0;
function createPlayer(x, y) {
    return {
        x,
        y,
        vx: 0,
        vy: 0,
        speed: 3,
        health: 6, // Start with 3 heart containers (2 HP each)
        maxHealth: 6,
        heartContainers: 3,
        weaponLevel: 0,
        hasRangedWeapon: false,
        ammo: 0,
        money: 0,
        direction: 0,
        isAttacking: false,
        attackCooldown: 0,
        invincibilityTimer: 0,
        animationFrame: 0,
        animTimer: 0,
        facing: Math.PI / 2 // Facing down
    };
}
function updatePlayer(player, dt) {
    // Update attack cooldown
    if (player.attackCooldown > 0) {
        player.attackCooldown -= dt;
    }
    // Update invincibility timer
    if (player.invincibilityTimer > 0) {
        player.invincibilityTimer -= dt;
    }
    // Animation
    player.animTimer += dt;
    if (player.animTimer > 10) {
        player.animTimer = 0;
        player.animationFrame = (player.animationFrame + 1) % 4;
    }
}
function handleInput(player, p) {
    const prevX = player.x;
    const prevY = player.y;
    player.vx = 0;
    player.vy = 0;
    if (p.keyIsDown(p.LEFT) || p.keyIsDown(65)) { // A key
        player.vx = -player.speed;
        player.facing = Math.PI;
        player.direction = 2;
    }
    if (p.keyIsDown(p.RIGHT) || p.keyIsDown(68)) { // D key
        player.vx = player.speed;
        player.facing = 0;
        player.direction = 3;
    }
    if (p.keyIsDown(p.UP) || p.keyIsDown(87)) { // W key
        player.vy = -player.speed;
        player.facing = -Math.PI / 2;
        player.direction = 1;
    }
    if (p.keyIsDown(p.DOWN) || p.keyIsDown(83)) { // S key
        player.vy = player.speed;
        player.facing = Math.PI / 2;
        player.direction = 0;
    }
    // Normalize diagonal movement
    if (player.vx !== 0 && player.vy !== 0) {
        player.vx *= 0.707;
        player.vy *= 0.707;
    }
    // Apply movement with collision detection
    const newX = player.x + player.vx;
    const newY = player.y + player.vy;
    // Check X collision
    if (!(0, world_1.checkCollision)({ tiles: [] }, newX, player.y, 10)) {
        player.x = newX;
    }
    // Check Y collision
    if (!(0, world_1.checkCollision)({ tiles: [] }, player.x, newY, 10)) {
        player.y = newY;
    }
    // Step sound when moving
    const moved = Math.abs(player.x - prevX) > 0.5 || Math.abs(player.y - prevY) > 0.5;
    if (moved) {
        stepTimer += Math.abs(player.vx) + Math.abs(player.vy);
        if (stepTimer > 15) {
            stepTimer = 0;
            const now = Date.now();
            if (now - lastStepSound > 200) {
                (0, audio_1.playSound)("step");
                lastStepSound = now;
            }
        }
    }
}
function performAttack(player) {
    if (player.attackCooldown <= 0 && !player.isAttacking) {
        player.isAttacking = true;
        player.attackCooldown = getAttackCooldown(player.weaponLevel);
        (0, audio_1.playSound)("attack");
        return true;
    }
    return false;
}
function performRangedAttack(player) {
    if (player.attackCooldown <= 0 && player.hasRangedWeapon && player.ammo > 0) {
        player.ammo--;
        player.attackCooldown = 20;
        (0, audio_1.playSound)("ranged_attack");
        return {
            x: player.x,
            y: player.y,
            vx: Math.cos(player.facing) * 6,
            vy: Math.sin(player.facing) * 6
        };
    }
    return null;
}
function takeDamage(player, damage) {
    if (player.invincibilityTimer > 0) {
        return false;
    }
    player.health -= damage;
    player.invincibilityTimer = 60; // 1 second at 60 FPS
    (0, audio_1.playSound)("damage");
    (0, particles_1.createExplosion)(player.x, player.y, "blood", 8);
    return true;
}
function heal(player, amount) {
    const oldHealth = player.health;
    player.health = Math.min(player.health + amount, player.maxHealth);
    const healed = player.health - oldHealth;
    if (healed > 0) {
        (0, audio_1.playSound)("heart");
        (0, particles_1.createExplosion)(player.x, player.y, "heart", 5);
    }
    return healed;
}
function addHeartContainer(player) {
    if (player.heartContainers < 5) {
        player.heartContainers++;
        player.maxHealth += 2;
        player.health = player.maxHealth;
        (0, audio_1.playSound)("upgrade");
        (0, particles_1.createExplosion)(player.x, player.y, "heart", 10);
    }
}
function upgradeWeapon(player) {
    if (player.weaponLevel < 2) {
        player.weaponLevel++;
        (0, audio_1.playSound)("upgrade");
        (0, particles_1.createExplosion)(player.x, player.y, "magic", 15);
        return true;
    }
    return false;
}
function getAttackRange(weaponLevel) {
    const baseRange = 24;
    const upgrades = [0, 4, 8];
    return baseRange + upgrades[weaponLevel];
}
function getAttackDamage(weaponLevel) {
    const baseDamage = 2;
    const upgrades = [0, 2, 4];
    return baseDamage + upgrades[weaponLevel];
}
function getAttackCooldown(weaponLevel) {
    const baseCooldown = 25;
    const upgrades = [0, -5, -10];
    return Math.max(10, baseCooldown + upgrades[weaponLevel]);
}
function drawPlayer(p, player, cameraX, cameraY) {
    const screenX = player.x - cameraX;
    const screenY = player.y - cameraY;
    p.push();
    p.translate(screenX, screenY);
    // Blink when invincible
    if (player.invincibilityTimer > 0 && Math.floor(player.invincibilityTimer / 5) % 2 === 0) {
        p.pop();
        return;
    }
    // Draw shadow
    p.fill(0, 0, 0, 100);
    p.ellipse(0, 12, 20, 10);
    // Draw body
    p.fill(52, 152, 219);
    p.rect(-10, -8, 20, 16, 4);
    // Draw head
    p.fill(255, 213, 174);
    p.circle(0, -12, 14);
    // Draw eyes based on direction
    p.fill(0, 0, 0);
    if (player.direction === 0) { // Down
        p.circle(-4, -12, 3);
        p.circle(4, -12, 3);
    }
    else if (player.direction === 1) { // Up
        p.circle(-4, -14, 3);
        p.circle(4, -14, 3);
    }
    else if (player.direction === 2) { // Left
        p.circle(-6, -13, 3);
    }
    else { // Right
        p.circle(6, -13, 3);
    }
    // Draw weapon
    if (player.isAttacking) {
        p.push();
        p.rotate(player.facing);
        // Weapon appearance changes with level
        if (player.weaponLevel === 0) {
            // Basic sword
            p.fill(189, 189, 189);
            p.rect(8, -2, 16, 4);
            p.fill(101, 160, 64);
            p.rect(8, -3, 4, 6);
        }
        else if (player.weaponLevel === 1) {
            // Sharpened blade with glow
            p.fill(255, 255, 255);
            p.rect(8, -2, 20, 4);
            p.fill(255, 235, 59);
            p.rect(8, -3, 4, 6);
            // Glow effect
            p.fill(255, 235, 59, 100);
            p.ellipse(18, 0, 12, 8);
        }
        else {
            // Flame edge
            p.fill(255, 255, 255);
            p.rect(8, -2, 24, 4);
            p.fill(255, 87, 34);
            p.rect(8, -3, 4, 6);
            // Fire effect
            p.fill(255, 152, 0, 150);
            p.ellipse(20, 0, 16, 10);
            p.fill(255, 235, 59, 100);
            p.ellipse(24, 0, 12, 8);
        }
        p.pop();
    }
    else {
        // Idle weapon position
        p.push();
        p.rotate(player.facing + 0.3);
        if (player.weaponLevel === 0) {
            p.fill(189, 189, 189);
            p.rect(8, -2, 12, 3);
        }
        else if (player.weaponLevel === 1) {
            p.fill(255, 255, 255);
            p.rect(8, -2, 14, 3);
        }
        else {
            p.fill(255, 255, 255);
            p.rect(8, -2, 16, 3);
            p.fill(255, 87, 34, 100);
            p.ellipse(16, 0, 8, 5);
        }
        p.pop();
    }
    // Ranged weapon indicator
    if (player.hasRangedWeapon) {
        p.fill(156, 39, 176);
        p.rect(-12, 8, 8, 4);
        p.fill(255, 255, 255);
        p.textSize(8);
        p.text(player.ammo, -8, 12);
    }
    p.pop();
}
function getWeaponStats(weaponLevel) {
    return {
        damage: getAttackDamage(weaponLevel),
        range: getAttackRange(weaponLevel),
        speed: 1 / (getAttackCooldown(weaponLevel) / 60)
    };
}
};

// ── module: src/combat.ts ──
__mods["src/combat.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addProjectile = addProjectile;
exports.updateProjectiles = updateProjectiles;
exports.clearProjectiles = clearProjectiles;
exports.updateEnemies = updateEnemies;
exports.handlePlayerAttack = handlePlayerAttack;
exports.checkEnemyDeath = checkEnemyDeath;
exports.drawEnemy = drawEnemy;
exports.getProjectileCount = getProjectileCount;
const state_1 = require("./state");
const player_1 = require("./player");
const state_2 = require("./state");
const audio_1 = require("./audio");
const particles_1 = require("./particles");
const projectiles = [];
function addProjectile(x, y, vx, vy, damage) {
    projectiles.push({
        x,
        y,
        vx,
        vy,
        damage,
        life: 60
    });
}
function updateProjectiles(world, onHit) {
    for (let i = projectiles.length - 1; i >= 0; i--) {
        const proj = projectiles[i];
        proj.x += proj.vx;
        proj.y += proj.vy;
        proj.life--;
        // Check collision with enemies
        let hit = false;
        for (const enemy of world.enemies) {
            const dx = proj.x - enemy.x;
            const dy = proj.y - enemy.y;
            const dist = Math.sqrt(dx * dx + dy * dy);
            if (dist < 15) {
                if (onHit(proj.x, proj.y, enemy)) {
                    (0, particles_1.createExplosion)(proj.x, proj.y, "spark", 5);
                    (0, audio_1.playSound)("projectile_hit");
                    hit = true;
                }
                break;
            }
        }
        if (hit || proj.life <= 0) {
            projectiles.splice(i, 1);
        }
    }
}
function clearProjectiles() {
    projectiles.length = 0;
}
function updateEnemies(world, playerX, playerY, dt) {
    for (const enemy of world.enemies) {
        const stats = state_2.ENEMY_TYPES[enemy.type];
        // Update attack cooldown
        if (enemy.attackCooldown > 0) {
            enemy.attackCooldown -= dt;
        }
        // Calculate distance to player
        const dx = playerX - enemy.x;
        const dy = playerY - enemy.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        // Aggro range
        const aggroRange = 80;
        if (dist < aggroRange && !enemy.isAggro) {
            enemy.isAggro = true;
        }
        if (enemy.isAggro) {
            // Move towards player
            const angle = Math.atan2(dy, dx);
            const moveSpeed = enemy.speed;
            const newX = enemy.x + Math.cos(angle) * moveSpeed;
            const newY = enemy.y + Math.sin(angle) * moveSpeed;
            // Simple collision check
            if (!isWallCollision(world, newX, enemy.y, 10)) {
                enemy.x = newX;
            }
            if (!isWallCollision(world, enemy.x, newY, 10)) {
                enemy.y = newY;
            }
            // Attack player if close enough
            const newDist = Math.sqrt((playerX - enemy.x) ** 2 + (playerY - enemy.y) ** 2);
            if (newDist < 20 && enemy.attackCooldown <= 0) {
                enemy.attackCooldown = 60;
                return { x: enemy.x, y: enemy.y, damage: enemy.damage };
            }
        }
        else {
            // Patrol behavior
            const patrolDx = enemy.patrolStartX - enemy.x;
            const patrolDy = enemy.patrolStartY - enemy.y;
            const patrolDist = Math.sqrt(patrolDx * patrolDx + patrolDy * patrolDy);
            if (patrolDist > enemy.patrolRadius * state_1.TILE_SIZE * 0.5) {
                const angle = Math.atan2(patrolDy, patrolDx);
                enemy.x += Math.cos(angle) * enemy.speed * 0.5;
                enemy.y += Math.sin(angle) * enemy.speed * 0.5;
            }
        }
    }
    return null;
}
function isWallCollision(world, x, y, radius = 8) {
    const points = [
        { x: x - radius, y: y - radius },
        { x: x + radius, y: y - radius },
        { x: x - radius, y: y + radius },
        { x: x + radius, y: y + radius }
    ];
    for (const p of points) {
        const tile = getTileAt(world, p.x, p.y);
        if (tile === 1 || tile === 3) { // WALL or WATER
            return true;
        }
    }
    return false;
}
function getTileAt(world, x, y) {
    const tx = Math.floor(x / state_1.TILE_SIZE);
    const ty = Math.floor(y / state_1.TILE_SIZE);
    if (ty < 0 || ty >= world.tiles.length || tx < 0 || tx >= world.tiles[0].length) {
        return 1; // WALL
    }
    return world.tiles[ty][tx];
}
function handlePlayerAttack(world, playerX, playerY, facing, weaponLevel) {
    const damage = (0, player_1.getAttackDamage)(weaponLevel);
    const range = (0, player_1.getAttackRange)(weaponLevel);
    const hitEnemies = [];
    // Create attack arc
    const arcWidth = Math.PI / 2;
    const startAngle = facing - arcWidth / 2;
    for (const enemy of world.enemies) {
        const dx = enemy.x - playerX;
        const dy = enemy.y - playerY;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist <= range + 10) {
            const angleToEnemy = Math.atan2(dy, dx);
            let angleDiff = angleToEnemy - facing;
            // Normalize angle
            while (angleDiff > Math.PI)
                angleDiff -= Math.PI * 2;
            while (angleDiff < -Math.PI)
                angleDiff += Math.PI * 2;
            if (Math.abs(angleDiff) < arcWidth / 2 + 0.3) {
                enemy.health -= damage;
                hitEnemies.push(enemy);
                // Knockback
                enemy.x += Math.cos(facing) * 10;
                enemy.y += Math.sin(facing) * 10;
                (0, particles_1.createExplosion)(enemy.x, enemy.y, "spark", 4);
                (0, audio_1.playSound)("enemy_hit");
            }
        }
    }
    return hitEnemies;
}
function checkEnemyDeath(enemy) {
    if (enemy.health <= 0) {
        (0, particles_1.createExplosion)(enemy.x, enemy.y, "spark", 10);
        (0, audio_1.playSound)("pickup");
        return {
            dead: true,
            dropMoney: enemy.dropMoney,
            dropItem: enemy.dropItem
        };
    }
    return { dead: false, dropMoney: 0 };
}
function drawEnemy(p, enemy, cameraX, cameraY) {
    const screenX = enemy.x - cameraX;
    const screenY = enemy.y - cameraY;
    const stats = state_2.ENEMY_TYPES[enemy.type];
    p.push();
    p.translate(screenX, screenY);
    // Draw shadow
    p.fill(0, 0, 0, 100);
    p.ellipse(0, 12, 20, 10);
    // Draw enemy based on type
    p.fill(stats.color[0], stats.color[1], stats.color[2]);
    switch (enemy.type) {
        case "slime":
            // Slime body
            p.beginShape();
            p.vertex(-12, 8);
            p.bezierVertex(-14, -4, 14, -4, 12, 8);
            p.bezierVertex(10, 12, -10, 12, -12, 8);
            p.endShape();
            // Eyes
            p.fill(0, 0, 0);
            p.circle(-5, 2, 4);
            p.circle(5, 2, 4);
            break;
        case "bat":
            // Bat wings
            p.fill(stats.color[0] + 20, stats.color[1], stats.color[2]);
            p.beginShape();
            p.vertex(-8, 0);
            p.bezierVertex(-20, -10, -25, 5, -10, 4);
            p.vertex(8, 0);
            p.bezierVertex(25, 5, 20, -10, 8, 0);
            p.endShape();
            // Body
            p.fill(stats.color[0], stats.color[1], stats.color[2]);
            p.circle(0, 2, 12);
            // Eyes
            p.fill(255, 0, 0);
            p.circle(-4, 0, 3);
            p.circle(4, 0, 3);
            break;
        case "skeleton":
            // Skeleton body
            p.fill(236, 240, 241);
            p.circle(0, -4, 14); // Head
            p.rect(-6, 6, 12, 12); // Body
            // Ribs
            p.stroke(100, 100, 100);
            p.strokeWeight(2);
            p.line(-4, 8, 4, 8);
            p.line(-4, 10, 4, 10);
            p.noStroke();
            // Eye sockets
            p.fill(0, 0, 0);
            p.circle(-4, -6, 4);
            p.circle(4, -6, 4);
            break;
        case "guard":
            // Armor
            p.fill(92, 107, 192);
            p.rect(-10, -6, 20, 20, 4);
            // Helmet
            p.fill(79, 107, 192);
            p.circle(0, -10, 14);
            // Visor
            p.fill(0, 0, 0);
            p.rect(-6, -12, 12, 4);
            // Red eyes
            p.fill(255, 0, 0);
            p.circle(-3, -11, 2);
            p.circle(3, -11, 2);
            break;
        case "boss":
            // Boss is bigger
            p.scale(1.5);
            p.fill(183, 28, 28);
            p.rect(-10, -8, 20, 24, 6);
            // Horns
            p.fill(100, 0, 0);
            p.triangle(-12, -12, -8, -20, -6, -10);
            p.triangle(12, -12, 8, -20, 6, -10);
            // Angry eyes
            p.fill(255, 255, 0);
            p.circle(-5, -2, 5);
            p.circle(5, -2, 5);
            // Mouth
            p.fill(0, 0, 0);
            p.rect(-6, 8, 12, 4);
            break;
    }
    // Health bar
    const healthPercent = enemy.health / enemy.maxHealth;
    p.fill(50, 50, 50);
    p.rect(-12, -20, 24, 4);
    p.fill(76, 175, 80);
    p.rect(-12, -20, 24 * healthPercent, 4);
    p.pop();
}
function getProjectileCount() {
    return projectiles.length;
}
};

// ── module: src/inventory.ts ──
__mods["src/inventory.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInventory = createInventory;
exports.addItem = addItem;
exports.removeItem = removeItem;
exports.hasItem = hasItem;
exports.getItemCount = getItemCount;
exports.useItem = useItem;
exports.equipAccessory = equipAccessory;
exports.unequipAccessory = unequipAccessory;
exports.getAccessoryEffects = getAccessoryEffects;
exports.getTotalItemCount = getTotalItemCount;
exports.isInventoryFull = isInventoryFull;
exports.drawInventory = drawInventory;
exports.drawShop = drawShop;
const state_1 = require("./state");
function createInventory() {
    return {
        items: new Map(),
        accessories: [],
        keyItems: [],
        maxSlots: 12
    };
}
function addItem(inventory, itemKey, quantity = 1) {
    const itemInfo = state_1.ITEMS[itemKey];
    if (!itemInfo)
        return false;
    if (itemInfo.type === "accessory") {
        // Accessories can only be equipped once
        if (!inventory.accessories.includes(itemKey)) {
            inventory.accessories.push(itemKey);
            return true;
        }
        return false;
    }
    if (itemInfo.type === "key_item") {
        if (!inventory.keyItems.includes(itemKey)) {
            inventory.keyItems.push(itemKey);
            return true;
        }
        return false;
    }
    // Consumables stack
    const current = inventory.items.get(itemKey) || 0;
    inventory.items.set(itemKey, current + quantity);
    return true;
}
function removeItem(inventory, itemKey, quantity = 1) {
    const current = inventory.items.get(itemKey) || 0;
    if (current >= quantity) {
        const newAmount = current - quantity;
        if (newAmount <= 0) {
            inventory.items.delete(itemKey);
        }
        else {
            inventory.items.set(itemKey, newAmount);
        }
        return true;
    }
    return false;
}
function hasItem(inventory, itemKey, quantity = 1) {
    const itemInfo = state_1.ITEMS[itemKey];
    if (!itemInfo)
        return false;
    if (itemInfo.type === "accessory") {
        return inventory.accessories.includes(itemKey);
    }
    if (itemInfo.type === "key_item") {
        return inventory.keyItems.includes(itemKey);
    }
    return (inventory.items.get(itemKey) || 0) >= quantity;
}
function getItemCount(inventory, itemKey) {
    return inventory.items.get(itemKey) || 0;
}
function useItem(inventory, itemKey) {
    const itemInfo = state_1.ITEMS[itemKey];
    if (!itemInfo)
        return { used: false };
    if (itemInfo.type !== "consumable") {
        return { used: false, effect: "Cannot use this item" };
    }
    if (!removeItem(inventory, itemKey, 1)) {
        return { used: false, effect: "Item not found" };
    }
    let effect = "";
    switch (itemKey) {
        case "potion":
            effect = "heal_4";
            break;
    }
    return { used: true, effect };
}
function equipAccessory(inventory, itemKey) {
    const itemInfo = state_1.ITEMS[itemKey];
    if (!itemInfo || itemInfo.type !== "accessory")
        return false;
    if (!inventory.accessories.includes(itemKey)) {
        inventory.accessories.push(itemKey);
        return true;
    }
    return false;
}
function unequipAccessory(inventory, itemKey) {
    const index = inventory.accessories.indexOf(itemKey);
    if (index > -1) {
        inventory.accessories.splice(index, 1);
        return true;
    }
    return false;
}
function getAccessoryEffects(inventory) {
    let defense = 0;
    let speed = 0;
    let moneyBonus = 0;
    for (const accessory of inventory.accessories) {
        switch (accessory) {
            case "silver_ring":
                defense += 1;
                break;
            case "speed_boots":
                speed += 0.1;
                break;
            case "lucky_charm":
                moneyBonus += 0.1;
                break;
            case "iron_shield":
                defense += 2;
                break;
        }
    }
    return { defense, speed, moneyBonus };
}
function getTotalItemCount(inventory) {
    let count = 0;
    for (const [_, qty] of inventory.items) {
        count += qty;
    }
    count += inventory.accessories.length;
    count += inventory.keyItems.length;
    return count;
}
function isInventoryFull(inventory) {
    return getTotalItemCount(inventory) >= inventory.maxSlots;
}
function drawInventory(p, inventory, x, y, width, height, selectedSlot) {
    // Background
    p.fill(30, 30, 40, 240);
    p.rect(x, y, width, height, 8);
    p.stroke(100, 100, 120);
    p.strokeWeight(2);
    p.noFill();
    p.rect(x, y, width, height, 8);
    p.noStroke();
    // Title
    p.fill(255, 255, 255);
    p.textSize(16);
    p.text("INVENTORY", x + width / 2, y + 20);
    // Slot size
    const slotSize = 40;
    const slotsPerRow = Math.floor((width - 40) / (slotSize + 10));
    let slotIndex = 0;
    // Draw consumable items
    p.textSize(12);
    for (const [itemKey, qty] of inventory.items) {
        const col = slotIndex % slotsPerRow;
        const row = Math.floor(slotIndex / slotsPerRow);
        const slotX = x + 20 + col * (slotSize + 10);
        const slotY = y + 35 + row * (slotSize + 10);
        // Slot background
        p.fill(50, 50, 60);
        if (slotIndex === selectedSlot) {
            p.fill(70, 70, 90);
            p.stroke(255, 215, 0);
            p.strokeWeight(2);
        }
        else {
            p.noStroke();
        }
        p.rect(slotX, slotY, slotSize, slotSize, 4);
        p.noStroke();
        // Item icon (simple representation)
        p.fill(100, 200, 100);
        p.circle(slotX + slotSize / 2, slotY + slotSize / 2 - 2, 12);
        // Quantity
        p.fill(255, 255, 255);
        p.textSize(10);
        p.text(qty.toString(), slotX + slotSize - 8, slotY + slotSize - 4);
        slotIndex++;
    }
    // Draw accessories
    p.textSize(12);
    p.fill(200, 200, 255);
    p.text("Accessories:", x + 20, y + 35 + Math.ceil(inventory.items.size / slotsPerRow) * (slotSize + 10) + 10);
    let accIndex = 0;
    for (const acc of inventory.accessories) {
        const col = accIndex % slotsPerRow;
        const row = Math.floor(accIndex / slotsPerRow);
        const slotX = x + 20 + col * (slotSize + 10);
        const slotY = y + 35 + Math.ceil(inventory.items.size / slotsPerRow) * (slotSize + 10) + 25 + row * (slotSize + 10);
        p.fill(50, 50, 80);
        p.rect(slotX, slotY, slotSize, slotSize, 4);
        // Accessory icon
        p.fill(150, 150, 255);
        p.rect(slotX + 10, slotY + 10, 20, 20, 2);
        accIndex++;
    }
    // Draw key items
    p.textSize(12);
    p.fill(255, 200, 100);
    const keyY = y + 35 + Math.ceil(inventory.items.size / slotsPerRow) * (slotSize + 10) + 25 + Math.ceil(inventory.accessories.length / slotsPerRow) * (slotSize + 10) + 10;
    p.text("Key Items:", x + 20, keyY);
    let keyIndex = 0;
    for (const key of inventory.keyItems) {
        const col = keyIndex % slotsPerRow;
        const row = Math.floor(keyIndex / slotsPerRow);
        const slotX = x + 20 + col * (slotSize + 10);
        const slotY = keyY + 15 + row * (slotSize + 10);
        p.fill(80, 60, 40);
        p.rect(slotX, slotY, slotSize, slotSize, 4);
        // Key icon
        p.fill(255, 215, 0);
        p.circle(slotX + slotSize / 2, slotY + slotSize / 2, 10);
        keyIndex++;
    }
    // Instructions
    p.fill(150, 150, 150);
    p.textSize(10);
    p.text("I - Close | Arrow keys - Select", x + 20, y + height - 10);
}
function drawShop(p, shopItems, x, y, width, height, playerMoney, selectedSlot) {
    // Background
    p.fill(30, 30, 40, 240);
    p.rect(x, y, width, height, 8);
    p.stroke(200, 180, 50);
    p.strokeWeight(2);
    p.noFill();
    p.rect(x, y, width, height, 8);
    p.noStroke();
    // Title
    p.fill(255, 215, 0);
    p.textSize(16);
    p.text("SHOP", x + width / 2, y + 20);
    // Money display
    p.fill(100, 255, 100);
    p.textSize(14);
    p.text(`Gold: ${playerMoney}`, x + 20, y + 40);
    // Shop items
    const slotSize = 35;
    const startY = y + 55;
    for (let i = 0; i < shopItems.length && i < 6; i++) {
        const item = shopItems[i];
        const itemY = startY + i * (slotSize + 8);
        // Slot background
        p.fill(50, 50, 60);
        if (i === selectedSlot) {
            p.fill(70, 70, 90);
            p.stroke(255, 215, 0);
            p.strokeWeight(2);
        }
        else {
            p.noStroke();
        }
        p.rect(x + 20, itemY, width - 40, slotSize, 4);
        p.noStroke();
        // Item name
        p.fill(255, 255, 255);
        p.textSize(12);
        p.text(item.name, x + 30, itemY + 14);
        // Price
        p.fill(255, 215, 0);
        p.text(`${item.price} G`, x + width - 50, itemY + 14);
        // Can afford indicator
        if (playerMoney >= item.price) {
            p.fill(100, 255, 100);
            p.text("✓", x + width - 80, itemY + 14);
        }
        else {
            p.fill(255, 100, 100);
            p.text("✗", x + width - 80, itemY + 14);
        }
    }
    // Instructions
    p.fill(150, 150, 150);
    p.textSize(10);
    p.text("I - Close | Enter - Buy | Arrows - Select", x + 20, y + height - 10);
}
};

// ── module: src/save.ts ──
__mods["src/save.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveGame = saveGame;
exports.loadGame = loadGame;
exports.hasSave = hasSave;
exports.deleteSave = deleteSave;
exports.activateSavePoint = activateSavePoint;
exports.isSavePointActive = isSavePointActive;
exports.getSaveTimestamp = getSaveTimestamp;
exports.formatSaveTime = formatSaveTime;
const SAVE_KEY = "rpg_save_data";
function saveGame(player, inventory, world) {
    const saveData = {
        player: {
            x: player.x,
            y: player.y,
            health: player.health,
            maxHealth: player.maxHealth,
            heartContainers: player.heartContainers,
            weaponLevel: player.weaponLevel,
            hasRangedWeapon: player.hasRangedWeapon,
            ammo: player.ammo,
            money: player.money
        },
        inventory: {
            items: Object.fromEntries(inventory.items),
            accessories: [...inventory.accessories],
            keyItems: [...inventory.keyItems]
        },
        world: {
            currentArea: world.currentArea,
            areasUnlocked: [...world.areasUnlocked],
            midGameReached: world.midGameReached,
            collectedPickups: world.pickups.filter(p => p.collected).map(p => p.id),
            openedTreasures: world.treasures.filter(t => t.collected).map(t => t.id),
            activatedSavePoints: world.savePoints.filter(sp => sp.saved).map(sp => sp.id)
        },
        timestamp: Date.now()
    };
    try {
        localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
        return saveData;
    }
    catch (e) {
        console.error("Failed to save game:", e);
        return saveData;
    }
}
function loadGame() {
    try {
        const saved = localStorage.getItem(SAVE_KEY);
        if (!saved)
            return null;
        const data = JSON.parse(saved);
        return data;
    }
    catch (e) {
        console.error("Failed to load game:", e);
        return null;
    }
}
function hasSave() {
    try {
        return localStorage.getItem(SAVE_KEY) !== null;
    }
    catch {
        return false;
    }
}
function deleteSave() {
    try {
        localStorage.removeItem(SAVE_KEY);
    }
    catch (e) {
        console.error("Failed to delete save:", e);
    }
}
function activateSavePoint(world, savePointId) {
    for (const sp of world.savePoints) {
        if (sp.id === savePointId) {
            sp.saved = true;
            sp.active = true;
            break;
        }
    }
}
function isSavePointActive(world, savePointId) {
    for (const sp of world.savePoints) {
        if (sp.id === savePointId) {
            return sp.active && sp.saved;
        }
    }
    return false;
}
function getSaveTimestamp() {
    try {
        const saved = localStorage.getItem(SAVE_KEY);
        if (!saved)
            return null;
        const data = JSON.parse(saved);
        return data.timestamp;
    }
    catch {
        return null;
    }
}
function formatSaveTime(timestamp) {
    const date = new Date(timestamp);
    const hours = date.getHours().toString().padStart(2, '0');
    const minutes = date.getMinutes().toString().padStart(2, '0');
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    return `${month}/${day} ${hours}:${minutes}`;
}
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.drawHealthBar = drawHealthBar;
exports.drawMoney = drawMoney;
exports.drawWeaponIndicator = drawWeaponIndicator;
exports.drawMessage = drawMessage;
exports.drawPauseMenu = drawPauseMenu;
exports.drawGameOver = drawGameOver;
exports.drawVictory = drawVictory;
exports.drawTutorial = drawTutorial;
exports.drawAreaName = drawAreaName;
exports.drawInteractionPrompt = drawInteractionPrompt;
function drawHealthBar(p, player, x, y) {
    const heartSize = 24;
    const spacing = 4;
    p.push();
    p.translate(x, y);
    // Draw heart containers
    for (let i = 0; i < 5; i++) {
        const heartX = i * (heartSize + spacing);
        const isFullContainer = i < Math.floor(player.heartContainers);
        const isInHalf = player.heartContainers % 1 !== 0 && i === Math.floor(player.heartContainers);
        p.push();
        p.translate(heartX, 0);
        if (i < player.heartContainers) {
            // Full or partial heart
            const healthInThisContainer = player.health - i * 2;
            if (healthInThisContainer >= 2) {
                // Full heart
                drawFullHeart(p, 0, 0, heartSize);
            }
            else if (healthInThisContainer > 0) {
                // Half heart
                drawHalfHeart(p, 0, 0, heartSize);
            }
            else {
                // Empty (shouldn't happen but just in case)
                drawEmptyHeart(p, 0, 0, heartSize);
            }
        }
        else {
            // Empty heart container outline
            drawEmptyHeart(p, 0, 0, heartSize);
        }
        p.pop();
    }
    p.pop();
}
function drawFullHeart(p, x, y, size) {
    p.push();
    p.translate(x, y);
    p.scale(size / 20);
    p.fill(233, 30, 99);
    p.noStroke();
    p.beginShape();
    p.vertex(0, -6);
    p.bezierVertex(-4, -12, -12, -6, 0, 10);
    p.bezierVertex(12, -6, 4, -12, 0, -6);
    p.endShape();
    p.pop();
}
function drawHalfHeart(p, x, y, size) {
    p.push();
    p.translate(x, y);
    p.scale(size / 20);
    // Left half (filled)
    p.fill(233, 30, 99);
    p.noStroke();
    p.beginShape();
    p.vertex(0, -6);
    p.bezierVertex(-4, -12, -12, -6, 0, 10);
    p.vertex(0, 10);
    p.vertex(0, -6);
    p.endShape();
    // Right half (empty outline)
    p.stroke(150, 50, 70);
    p.strokeWeight(1);
    p.noFill();
    p.beginShape();
    p.vertex(0, -6);
    p.bezierVertex(4, -12, 12, -6, 0, 10);
    p.endShape();
    p.pop();
}
function drawEmptyHeart(p, x, y, size) {
    p.push();
    p.translate(x, y);
    p.scale(size / 20);
    p.noFill();
    p.stroke(150, 50, 70);
    p.strokeWeight(1.5);
    p.beginShape();
    p.vertex(0, -6);
    p.bezierVertex(-4, -12, -12, -6, 0, 10);
    p.bezierVertex(12, -6, 4, -12, 0, -6);
    p.endShape();
    p.pop();
}
function drawMoney(p, money, x, y) {
    p.push();
    p.translate(x, y);
    p.fill(255, 215, 0);
    p.textSize(14);
    p.text(`G ${money}`, 0, 0);
    // Gold coin icon
    p.fill(255, 215, 0);
    p.circle(-28, 0, 16);
    p.fill(255, 235, 59);
    p.circle(-28, 0, 12);
    p.fill(255, 215, 0);
    p.textSize(10);
    p.text("$", -31, 4);
    p.pop();
}
function drawWeaponIndicator(p, weaponLevel, hasRanged, ammo, x, y) {
    p.push();
    p.translate(x, y);
    // Weapon level indicator
    p.fill(150, 150, 150);
    p.rect(0, 0, 60, 20, 4);
    p.fill(100, 200, 100);
    for (let i = 0; i < weaponLevel; i++) {
        p.rect(4 + i * 18, 3, 14, 14, 2);
        p.fill(255, 255, 0);
        p.textSize(10);
        p.text("↑", 8 + i * 18, 14);
        p.fill(100, 200, 100);
    }
    // Ranged weapon indicator
    if (hasRanged) {
        p.fill(156, 39, 176);
        p.rect(70, 0, 50, 20, 4);
        p.fill(255, 255, 255);
        p.textSize(12);
        p.text(`🏹 ${ammo}`, 75, 14);
    }
    p.pop();
}
function drawMessage(_p, _message, _timer, _maxWidth) {
    if (_timer <= 0 || !_message)
        return;
    const alpha = Math.min(255, _timer * 5);
    _p.push();
    _p.fill(0, 0, 0, alpha);
    _p.rectMode(_p.CENTER);
    _p.rect(_p.width / 2, _p.height - 60, _maxWidth + 20, 30, 8);
    _p.fill(255, 255, 255, alpha);
    _p.textAlign(_p.CENTER);
    _p.textSize(14);
    _p.text(_message, _p.width / 2, _p.height - 55);
    _p.rectMode(_p.CORNER);
    _p.pop();
}
function drawPauseMenu(p, x, y, width, height, selectedOption) {
    // Background overlay
    p.fill(0, 0, 0, 180);
    p.rect(0, 0, p.width, p.height);
    // Menu box
    p.fill(40, 40, 50, 240);
    p.rect(x, y, width, height, 10);
    p.stroke(100, 100, 120);
    p.strokeWeight(2);
    p.noFill();
    p.rect(x, y, width, height, 10);
    p.noStroke();
    // Title
    p.fill(255, 255, 255);
    p.textAlign(p.CENTER);
    p.textSize(20);
    p.text("PAUSED", x + width / 2, y + 35);
    // Options
    const options = ["Resume", "Save Game", "Options", "Quit"];
    p.textAlign(p.LEFT);
    p.textSize(16);
    for (let i = 0; i < options.length; i++) {
        const optionY = y + 60 + i * 35;
        if (i === selectedOption) {
            p.fill(100, 200, 100);
            p.text("> " + options[i], x + 30, optionY);
        }
        else {
            p.fill(200, 200, 200);
            p.text(options[i], x + 40, optionY);
        }
    }
    p.textAlign(p.LEFT);
}
function drawGameOver(p) {
    // Dark overlay
    p.fill(0, 0, 0, 200);
    p.rect(0, 0, p.width, p.height);
    // Game Over text
    p.fill(200, 50, 50);
    p.textAlign(p.CENTER);
    p.textSize(48);
    p.text("GAME OVER", p.width / 2, p.height / 2 - 30);
    p.fill(200, 200, 200);
    p.textSize(18);
    p.text("Press R to restart", p.width / 2, p.height / 2 + 20);
    p.textAlign(p.LEFT);
}
function drawVictory(p, player) {
    // Golden overlay
    p.fill(50, 40, 20, 200);
    p.rect(0, 0, p.width, p.height);
    // Victory text
    p.fill(255, 215, 0);
    p.textAlign(p.CENTER);
    p.textSize(48);
    p.text("VICTORY!", p.width / 2, p.height / 2 - 60);
    p.fill(255, 255, 255);
    p.textSize(20);
    p.text("You have conquered the realm!", p.width / 2, p.height / 2 - 10);
    p.textSize(16);
    p.text(`Final Gold: ${player.money}`, p.width / 2, p.height / 2 + 30);
    p.text("Press R to play again", p.width / 2, p.height / 2 + 60);
    p.textAlign(p.LEFT);
}
function drawTutorial(p, visible) {
    if (!visible)
        return;
    p.push();
    p.fill(0, 0, 0, 150);
    p.rect(10, p.height - 120, 200, 110, 8);
    p.fill(255, 255, 255);
    p.textSize(12);
    p.text("WASD/Arrows - Move", 20, p.height - 100);
    p.text("SPACE - Attack", 20, p.height - 85);
    p.text("I - Inventory", 20, p.height - 70);
    p.text("E - Interact", 20, p.height - 55);
    p.text("ESC - Pause", 20, p.height - 40);
    p.text("P - Toggle Sound", 20, p.height - 25);
    p.pop();
}
function drawAreaName(p, areaName, timer) {
    if (timer <= 0)
        return;
    const alpha = Math.min(255, timer * 8);
    p.push();
    p.fill(0, 0, 0, alpha);
    p.rectMode(p.CENTER);
    p.rect(p.width / 2, 50, 200, 40, 8);
    p.fill(255, 255, 255, alpha);
    p.textAlign(p.CENTER);
    p.textSize(16);
    p.text(areaName, p.width / 2, 55);
    p.rectMode(p.CORNER);
    p.pop();
}
function drawInteractionPrompt(p, prompt, x, y) {
    p.push();
    p.fill(0, 0, 0, 180);
    p.rect(x, y, 80, 25, 4);
    p.fill(255, 255, 255);
    p.textAlign(p.CENTER);
    p.textSize(12);
    p.text(prompt, x + 40, y + 17);
    p.textAlign(p.LEFT);
    p.pop();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.5-122b-a10b","provider":"openrouter","persona":"typescript","sessionId":"9149beba-e5dc-4911-a17d-2f6782772ff4","tokensIn":2709224,"tokensOut":44643,"tokensTotal":2753867,"cost":0.79725568,"turns":54,"toolCalls":53,"failedToolCalls":2,"timestamp":"2026-08-05T05:37:33.135Z"} -->