PekAblo - Move Mechanics Problems

Words
171
Reading
1 min
Listen
Play
5M

If anyone can help me out, and point me in the right direction please. I can make the character move with the keyboard, but that doesn't animate. Mouse Clicks do nothing, if you know and can help, let me know.

gaulent.js

https://github.com/PaulMoon410/pekablo

Play the errors here

https://geocities.ws/peakecoin/games/pekablo/


(function() {
// --- ARROW KEY & MOBILE CONTROL ---
function handleMoveKey(key) {
    if (game.state !== 'playing') return;
    let dx = 0, dy = 0;
    const step = s * 0.5;
    if (key === 'ArrowUp' || key === 'w') dy = -step;
    else if (key === 'ArrowDown' || key === 's') dy = step;
    else if (key === 'ArrowLeft' || key === 'a') dx = -step;
    else if (key === 'ArrowRight' || key === 'd') dx = step;
    else return;
    let nx = hero.x + dx;
    let ny = hero.y + dy;
    if (!isWayWall(nx, ny)) {
        hero.x = nx;
        hero.y = ny;
        hero.to_x = nx;
        hero.to_y = ny;
        if (typeof hero.setState === 'function' && hero.run) hero.setState(hero.run);
        if (typeof hero.currentState === 'object' && typeof hero.step === 'number' && hero.currentState.steps) {
            hero.step = (hero.step + 1) % hero.currentState.steps;
            hero.sprite = hero.currentState;
        }
    } else {
        if (typeof hero.setState === 'function' && hero.stay) hero.setState(hero.stay);
    }
}

function handleAttackKey() {
    if (game.state !== 'playing') return;
    if (typeof hero.tryAttack === 'function') hero.tryAttack();
}

document.addEventListener('keydown', function(e) {
    // Prevent default scrolling on mobile for arrow keys and space
    if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"," ","w","a","s","d"].includes(e.key)) {
        e.preventDefault && e.preventDefault();
    }
    if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","w","a","s","d"].includes(e.key)) {
        handleMoveKey(e.key);
    } else if (e.key === ' ') {
        handleAttackKey();
    }
});

// Support for on-screen controls via custom events
window.handleMoveKey = handleMoveKey;
window.handleAttackKey = handleAttackKey;

var imageCount = 0;
function loadImage(url, angles, steps, offsetX) {
    imageCount++;
    var image = new Image();
    image.onload = function() {
        imageCount--;
        image.offsetX = offsetX ? ((image.height / angles) >> 2) : 0;
    };
    image.src = url;
    if (typeof angles !== "undefined" && typeof steps !== "undefined") {
        image.angles = angles;
        image.steps = steps;
    }
    return image;
}

function load(img, callback) {
    if (img.complete) callback();
    else img.addEventListener("load", callback, false);
}

var level = {
    floor: {
        prefix: "dttool/output/1/",
        map: [
            // 30x30 large level, mostly open with some obstacles
            ...Array(30).fill().map((_, y) => Array(30).fill(756)),
        ],
        header: {
            372: false,
            660: false,
            756: false,
            1140: false,
            1908: false
        }
    },
    wall: {
        prefix: "dttool/output/0/",
        map: [
            // 30x30, border walls and some internal obstacles
            ...Array(30).fill().map((_, y) => Array(30).fill(0)),
        ],
        header: {
            276: {orientation: 8, main_index: 5, sub_index: 2, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
            372: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
            468: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            564: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
            660: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
            756: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            852: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            948: {orientation: 3, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
            1044: {orientation: 4, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
            1140: {orientation: 7, main_index: 5, sub_index: 0, direction: 4, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            1236: {orientation: 9, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1]},
            1332: {orientation: 9, main_index: 5, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
            1428: {orientation: 8, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
            1524: {orientation: 8, main_index: 5, sub_index: 1, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            1620: {orientation: 9, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1]},
            1716: {orientation: 9, main_index: 5, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
            1812: {orientation: 8, main_index: 5, sub_index: 1, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            1908: {orientation: 8, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
            2004: {orientation: 6, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1]},
            2100: {orientation: 5, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            2196: {orientation: 6, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1]},
            2292: {orientation: 5, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            2388: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
            2484: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            2580: {orientation: 3, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
            2676: {orientation: 4, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
            2772: {orientation: 12, main_index: 5, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]}
        }
    },
    object: {
        prefix: "dttool/output/2/",
        map: [
            // 30x30, sparse objects
            ...Array(30).fill().map(() => Array(30).fill(0)),
        ],
        header: {
            276: {orientation: 2, main_index: 9, sub_index: 12, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0]},
            372: {orientation: 2, main_index: 9, sub_index: 11, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1]},
            468: {orientation: 12, main_index: 50, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0]},
            564: {orientation: 12, main_index: 9, sub_index: 33, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0]},
            660: {orientation: 1, main_index: 9, sub_index: 33, direction: 1, walk: [1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0]},
            756: {orientation: 7, main_index: 9, sub_index: 33, direction: 4, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1,0]},
            852: {orientation: 12, main_index: 9, sub_index: 32, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            948: {orientation: 12, main_index: 9, sub_index: 31, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,0,0]},
            1044: {orientation: 1, main_index: 9, sub_index: 10, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            1140: {orientation: 1, main_index: 9, sub_index: 9, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            1236: {orientation: 1, main_index: 9, sub_index: 8, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            1332: {orientation: 12, main_index: 9, sub_index: 11, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
            1428: {orientation: 12, main_index: 9, sub_index: 10, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
            1524: {orientation: 12, main_index: 9, sub_index: 9, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
            1620: {orientation: 12, main_index: 9, sub_index: 8, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
            1716: {orientation: 12, main_index: 9, sub_index: 7, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            1812: {orientation: 12, main_index: 9, sub_index: 6, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            1908: {orientation: 12, main_index: 9, sub_index: 5, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2004: {orientation: 12, main_index: 9, sub_index: 4, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2100: {orientation: 12, main_index: 9, sub_index: 3, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2196: {orientation: 12, main_index: 9, sub_index: 2, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2292: {orientation: 12, main_index: 9, sub_index: 1, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2388: {orientation: 12, main_index: 9, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
            2484: {orientation: 2, main_index: 9, sub_index: 10, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
            2580: {orientation: 2, main_index: 9, sub_index: 9, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1]},
            2676: {orientation: 2, main_index: 9, sub_index: 8, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},
            2772: {orientation: 2, main_index: 9, sub_index: 7, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
            2868: {orientation: 2, main_index: 9, sub_index: 6, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
            2964: {orientation: 1, main_index: 9, sub_index: 7, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            3060: {orientation: 1, main_index: 9, sub_index: 6, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            3156: {orientation: 2, main_index: 9, sub_index: 5, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
            3252: {orientation: 2, main_index: 9, sub_index: 4, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,0]},
            3348: {orientation: 1, main_index: 9, sub_index: 5, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            3444: {orientation: 1, main_index: 9, sub_index: 4, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            3540: {orientation: 12, main_index: 9, sub_index: 30, direction: 3, walk: [0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0]},
            3636: {orientation: 12, main_index: 9, sub_index: 29, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0]},
            3732: {orientation: 1, main_index: 9, sub_index: 3, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            3828: {orientation: 1, main_index: 9, sub_index: 2, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            3924: {orientation: 2, main_index: 9, sub_index: 3, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1]},
            4020: {orientation: 2, main_index: 9, sub_index: 2, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
            4116: {orientation: 2, main_index: 9, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
            4212: {orientation: 2, main_index: 9, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
            4308: {orientation: 1, main_index: 9, sub_index: 1, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            4404: {orientation: 1, main_index: 9, sub_index: 0, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            4500: {orientation: 12, main_index: 9, sub_index: 28, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
            4596: {orientation: 12, main_index: 9, sub_index: 27, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0]},
            4692: {orientation: 12, main_index: 9, sub_index: 24, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
            4788: {orientation: 12, main_index: 9, sub_index: 23, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
            4884: {orientation: 12, main_index: 9, sub_index: 22, direction: 3, walk: [0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0]},
            4980: {orientation: 12, main_index: 9, sub_index: 21, direction: 3, walk: [0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0]},
            5076: {orientation: 12, main_index: 9, sub_index: 20, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
            5172: {orientation: 12, main_index: 9, sub_index: 17, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            5268: {orientation: 12, main_index: 9, sub_index: 18, direction: 3, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            5364: {orientation: 12, main_index: 9, sub_index: 19, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0]},
            5460: {orientation: 12, main_index: 9, sub_index: 16, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
            5556: {orientation: 12, main_index: 9, sub_index: 15, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0]},
            5652: {orientation: 12, main_index: 9, sub_index: 13, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0]},
            5748: {orientation: 12, main_index: 9, sub_index: 12, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0]},
            5844: {orientation: 12, main_index: 9, sub_index: 14, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,0]}
        }
    }
};

for (var layer in level) {
    level[layer].tiles = {};
    for (var tileId in level[layer].header) {
        if (!level[layer].tiles[tileId]) {
            level[layer].tiles[tileId] = loadImage(level[layer].prefix + tileId + ".png");
        }
    }
}

var floor = document.getElementById("floor").getContext("2d");
floor.w = floor.canvas.width;
floor.h = floor.canvas.height;
var tw = 160;
var th = tw / 2;
var s = tw * 0.705;
var a = Math.PI / 4;
var visible = 12; // show more tiles on large map
var asin = Math.sin(a);
var acos = Math.sin(a);

var barrelSprite = loadImage("sprite/barrel64.png");
var coinSprite = loadImage("sprite/coins10.png");
var potionSprite = loadImage("sprite/potions.png");
var gateSprite = loadImage("sprite/house.png");

var monsterMap = {
    SK: {
        A1: loadImage("monsters/SK/A1/map.png", 8, 16, true),
        NU: loadImage("monsters/SK/NU/map.png", 8, 8, true),
        WL: loadImage("monsters/SK/WL/map.png", 8, 8, true),
        DD: loadImage("monsters/SK/DD/map.png", 8, 1),
        attackOffset: 10
    },
    FS: {
        A1: loadImage("monsters/FS/A1/map.png", 8, 17, true),
        NU: loadImage("monsters/FS/NU/map.png", 8, 12, true),
        WL: loadImage("monsters/FS/WL/map.png", 8, 14, true),
        DD: loadImage("monsters/FS/DD/map.png", 8, 1)
    },
    SI: {
        A1: loadImage("monsters/SI/A1/map.png", 8, 16, true),
        NU: loadImage("monsters/SI/NU/map.png", 8, 8, true),
        WL: loadImage("monsters/SI/WL/map.png", 8, 9, true),
        DD: loadImage("monsters/SI/DD/map.png", 8, 1)
    },
    BA: {
        A1: loadImage("monsters/BA/A1/map.png", 16, 9, true),
        NU: loadImage("monsters/BA/NU/map.png", 16, 8, true),
        WL: loadImage("monsters/BA/WL/map.png", 16, 8, true)
    }
};

var STAGES = [
    {
        name: "The Gatehouse",
        intro: "Clear the scavengers haunting the gatehouse.",
        monsters: [
            {type: "SK", count: 3, health: 700, damage: 22},
            {type: "FS", count: 2, health: 900, damage: 28}
        ],
        potionCount: 2,
        barrelCount: 3,
        healOnStart: 0,
        gateLabel: "Descend"
    },
    {
        name: "The Bone Hall",
        intro: "A deeper chamber wakes. Survive the second trial.",
        monsters: [
            {type: "SK", count: 3, health: 900, damage: 28},
            {type: "FS", count: 3, health: 1200, damage: 32},
            {type: "SI", count: 2, health: 1000, damage: 36}
        ],
        potionCount: 2,
        barrelCount: 4,
        healOnStart: 250,
        gateLabel: "Challenge the Warden"
    },
    {
        name: "Warden's Ring",
        intro: "Defeat the crypt warden and claim the relic.",
        monsters: [
            {type: "SI", count: 2, health: 1200, damage: 38},
            {type: "FS", count: 2, health: 1300, damage: 40},
            {type: "SK", count: 1, health: 1600, damage: 45, title: "Bone Captain"},
            {type: "SI", count: 1, health: 3200, damage: 65, resistance: 180, speed: 10, title: "Crypt Warden", lootCoins: 900}
        ],
        potionCount: 3,
        barrelCount: 4,
        healOnStart: 300,
        gateLabel: "Claim Victory"
    }
];

var monsters = [];
var deathmobs = [];
var barrels = [];
var coins = [];
var potions = [];
var walls = [];
var interactives = [];
var stageKills = 0;

var game = {
    state: "loading",
    stageIndex: -1,
    message: "Loading assets...",
    messageUntil: 0,
    showMap: false,
    questText: "",
    gate: null
};

function now() {
    return new Date().getTime();
}

// DEBUG: Draw walkability overlay
function drawWalkabilityOverlay(ctx) {
    if (!window.hero) return;
    ctx.save();
    for (let y = 0; y < level.floor.map.length; y++) {
        for (let x = 0; x < level.floor.map[0].length; x++) {
            let wx = x * s;
            let wy = y * s;
            if (isWayWall(wx + s/2, wy + s/2)) {
                ctx.fillStyle = 'rgba(255,0,0,0.2)';
                ctx.fillRect(wx, wy, s, s);
            }
        }
    }
    // Draw hero position
    ctx.fillStyle = 'lime';
    ctx.beginPath();
    ctx.arc(hero.x, hero.y, 6, 0, 2 * Math.PI);
    ctx.fill();
    ctx.restore();
}

function setMessage(text, duration) {
    game.message = text;
    game.messageUntil = now() + (duration || 2200);
}

function clearMessage() {
    if (game.messageUntil && now() > game.messageUntil) {
        game.message = "";
        game.messageUntil = 0;
    }
}

function remove(arr, value) {
    var index = arr.indexOf(value);
    if (index >= 0) arr.splice(index, 1);
}

function distance(ax, ay, bx, by) {
    var dx = ax - bx;
    var dy = ay - by;
    return Math.sqrt(dx * dx + dy * dy);
}

function isWayWall(x, y) {
    var block_x = Math.floor(x / s);
    var block_y = Math.floor(y / s);
    if (block_x < 0 || block_y < 0 || block_x >= level.wall.map[0].length || block_y >= level.wall.map.length) return true;
    // Border walls
    if (block_x === 0 || block_y === 0 || block_x === level.wall.map[0].length - 1 || block_y === level.wall.map.length - 1) return true;
    // Internal walls
    if (level.wall.map[block_y][block_x] !== 0) return true;
    // Objects block movement
    if (level.object.map[block_y][block_x] !== 0) return true;
    return false;
}

function getFloorTile(x, y) {
    if (!level.floor.map[y]) return null;
    if (!level.floor.map[y][x]) return null;
    var f = level.floor.map[y][x];
    return level.floor.tiles[f];
}

function randomFreePoint(minDistance) {
    for (var attempt = 0; attempt < 500; attempt++) {
        var point = {
            x: s * (1 + Math.random() * (level.floor.map[0].length - 2)),
            y: s * (1 + Math.random() * (level.floor.map.length - 2))
        };
        if (!isWayWall(point.x, point.y)) {
            if (!minDistance || distance(point.x, point.y, hero.x, hero.y) >= minDistance) return point;
        }
    }
    return {x: s * 3, y: s * 3};
}

function Shape(sprite, x, y) {
    this.x = x;
    this.y = y;
    this.offset_x = 0;
    this.offset_y = 0;
    this.sprite = sprite;
    this.isAboveHero = function() {
        var maxlen = tw * visible / 2;
        return Math.abs(this.x - hero.x) <= maxlen && Math.abs(this.y - hero.y) <= maxlen;
    };
}

function BaseWall(sprite, header, x, y) {
    Shape.call(this, sprite, x, y);
    this.header = header;
    this.isAboveHero = function() { return true; };
    this.offset_x -= 14;
    this.offset_y += 82;
}

function Wall(index, x, y) {
    BaseWall.call(this, level.wall.tiles[index], level.wall.header[index], x, y);
    switch (this.header.orientation) {
        case 2:
        case 6:
            this.offset_x += 16;
            break;
        case 5:
            this.offset_x -= 16;
            break;
        case 3:
            for (var inx in level.wall.header) {
                var h = level.wall.header[inx];
                if (h.orientation === 4 && h.main_index === this.header.main_index && h.sub_index === this.header.sub_index) {
                    walls.push(new Wall(inx, x, y));
                    break;
                }
            }
            this.offset_x += 16;
            break;
        case 4:
            this.offset_x -= 16;
            break;
    }
}

function WallObject(index, x, y) {
    BaseWall.call(this, level.object.tiles[index], level.object.header[index], x, y);
    this.offset_x += 16;
    var self = this;
    load(this.sprite, function() {
        if (self.sprite.width < 160) self.offset_x -= (160 - self.sprite.width) / 2;
    });
}

function DeathMob(mob) {
    Shape.call(this, mob.death, mob.x, mob.y);
    this.step = 0;
    this.angle = mob.angle;
    this.used = false;
    this.use = function() {
        if (this.used) return;
        this.used = true;
        var bonusCoins = mob.lootCoins || Math.floor(120 + Math.random() * 180);
        coins.push(new Coin(this.x + 28, this.y + 20, bonusCoins));
        if (Math.random() > 0.45) potions.push(new PotionHealth(this.x + 54, this.y));
        if (Math.random() > 0.75) coins.push(new Coin(this.x - 22, this.y + 8, Math.floor(bonusCoins / 2)));
        setMessage("Loot gathered from " + (mob.title || "the fallen") + ".", 1800);
    };
}

function Barrel(x, y) {
    Shape.call(this, barrelSprite, x, y);
    this.use = function(mob) {
        if (mob.doAttack) mob.doAttack(this);
    };
    this.damage = function() {
        remove(barrels, this);
        if (Math.random() > 0.35) coins.push(new Coin(this.x, this.y, Math.floor(45 + Math.random() * 60)));
        if (Math.random() > 0.8) potions.push(new PotionHealth(this.x + 12, this.y));
    };
}

function Coin(x, y, amount) {
    Shape.call(this, coinSprite, x, y);
    this.coins = amount || Math.floor(Math.random() * 100 + 50);
    this.use = function(mob) {
        remove(coins, this);
        mob.coins += this.coins;
        setMessage("Collected " + this.coins + " gold.", 1200);
    };
}

function Potion(x, y) {
    Shape.call(this, potionSprite, x, y);
    this.sprite.steps = 6;
    this.sprite.angles = 4;
    this.use = function(mob) {
        if (mob.addToBelt(this)) {
            remove(potions, this);
            setMessage("Potion stored on your belt.", 1200);
        }
    };
}

function PotionHealth(x, y) {
    Potion.call(this, x, y);
    this.step = 0;
    this.angle = 0;
    this.health = 850;
    this.drink = function(mob) {
        mob.health = Math.min(mob.origin_health, mob.health + this.health);
        setMessage("You recover " + this.health + " vitality.", 1200);
    };
}

function AncientGate(x, y) {
    Shape.call(this, gateSprite, x, y);
    this.offset_y += 24;
    this.active = false;
    this.label = "Ward Locked";
    this.use = function() {
        if (game.state !== "playing") return;
        if (!this.active) {
            setMessage("The gate remains sealed. Finish the trial first.", 1800);
            return;
        }
        if (game.stageIndex === STAGES.length - 1) {
            game.state = "won";
            setMessage("The relic is yours. The gauntlet is broken.", 4000);
            return;
        }
        advanceStage();
    };
}

function Mob(x, y, name) {
    this.to_x = x;
    this.to_y = y;
    this.name = name;
    this.title = name;
    this.stay = monsterMap[name].NU;
    this.run = monsterMap[name].WL;
    this.death = monsterMap[name].DD;
    this.currentState = this.stay;
    this.step = 0;
    this.angle = 0;
    this.st = 8;
    this.lootCoins = 150;
    Shape.call(this, this.currentState, x, y);
    this.rotate = function(sx, sy) {
        var l = this.currentState.angles;
        this.angle = Math.round((Math.atan2(sy, sx) / Math.PI + 2.75) * l / 2 + l / 2) % l;
    };
    this.rotateTo = function(point) {
        this.rotate(point.x - this.x, point.y - this.y);
    };
    this.setState = function(state) {
        if (this.currentState !== state) {
            this.currentState = state;
            this.step = -1;
        }
    };
    this.nextStep = function() {
        var dx = this.to_x - this.x;
        var dy = this.to_y - this.y;
        var dist = Math.sqrt(dx * dx + dy * dy);
        if (dist > 1) {
            var maxStep = this.st;
            var tx = 0, ty = 0;
            for (var st = 0; st <= maxStep; st += 1) {
                var sx = st * dx / dist;
                var sy = st * dy / dist;
                if (isWayWall(this.x + sx, this.y + sy)) {
                    tx = sx;
                    ty = sy;
                } else break;
            }
            this.rotate(tx, ty);
            if (Math.abs(tx) > 0.5 || Math.abs(ty) > 0.5) {
                this.x += tx;
                this.y += ty;
                this.setState(this.run);
            } else {
                this.setState(this.stay);
                this.x += tx;
                this.y += ty;
                this.to_x = this.x;
                this.to_y = this.y;
            }
        } else {
            this.setState(this.stay);
            this.to_x = this.x;
            this.to_y = this.y;
        }
        this.step = (this.step + 1) % this.currentState.steps;
        this.sprite = this.currentState;
    };
    this.origin_health = this.health = 1000;
    this.resistance = 10;
    this.use = function(mob) {
        if (mob.doAttack) mob.doAttack(this);
    };
    this.damage = function(damage) {
        var nextHealth = this.health - damage * 1000 / (1000 - this.resistance);
        if (nextHealth <= 0) {
            this.health = 0;
            remove(monsters, this);
            if (this.death) deathmobs.push(new DeathMob(this));
            stageKills++;
            if (monsters.length === 0 && game.gate) {
                game.gate.active = true;
                game.gate.label = STAGES[game.stageIndex].gateLabel;
                hero.health = Math.min(hero.origin_health, hero.health + 180);
                setMessage("The seal gives way. Approach the gate.", 2600);
            }
        } else {
            this.health = nextHealth;
        }
    };
}

function AgressiveMob(x, y, name) {
    Mob.call(this, x, y, name);
    this.attack = monsterMap[name].A1;
    this.attackOffset = monsterMap[name].attackOffset || 0;
    this.normalOffset = 0;
    this._nextStep = this.nextStep;
    this.nextStep = function() {
        if (this.currentState === this.attack) {
            if (this.step === this.attack.steps - 1) {
                this.currentState = this.stay;
                this.step = -1;
                if (this.attacked) {
                    this.attacked.damage(this.getDamage());
                    this.attacked = null;
                }
            }
            this.step = (this.step + 1) % this.currentState.steps;
            this.sprite = this.currentState;
        } else this._nextStep();
        this.offset_y = this.currentState === this.attack ? this.attackOffset : this.normalOffset;
    };
    this.currentDamage = 30;
    this.getDamage = function() {
        return this.currentDamage;
    };
    this.attacked = null;
    this.doAttack = function(mob) {
        if (this.attacked !== mob) {
            this.rotateTo(mob);
            this.setState(this.attack);
            this.attacked = mob;
        }
    };
}

function HeroBarbarian(x, y) {
    AgressiveMob.call(this, x, y, "BA");
    this.attackOffset = 40;
    this.normalOffset = 10;
    this.health = this.origin_health = 1500;
    this.belt = {items: [], size: 10};
    this.st = 16;
    this.coins = 0;
    this.resistance = 140;
    this.title = "The Wardenbreaker";
    this.addToBelt = function(potion) {
        for (var i = 0; i < this.belt.size; i++) {
            if (typeof this.belt.items[i] === "undefined") {
                this.belt.items[i] = potion;
                return true;
            }
        }
        setMessage("Your belt is full.", 1200);
        return false;
    };
    this.criticalDamage = 0.35;
    this.currentDamage = 130;
    this.getDamage = function() {
        return this.currentDamage * (Math.random() <= this.criticalDamage ? 3.5 : 1);
    };
    // Override nextStep to always move toward to_x, to_y
    // Save AgressiveMob's nextStep as _superNextStep
    this._superNextStep = this.nextStep;
    this.nextStep = function() {
        var dx = this.to_x - this.x;
        var dy = this.to_y - this.y;
        if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
            this._superNextStep();
        } else {
            this.setState(this.stay);
            this.to_x = this.x;
            this.to_y = this.y;
        }
        this.step = (this.step + 1) % this.currentState.steps;
        this.sprite = this.currentState;
    };
    this.damage = function(damage) {
        var nextHealth = this.health - damage * 1000 / (1000 - this.resistance);
        this.health = Math.max(0, nextHealth);
        if (this.health <= 0 && game.state === "playing") {
            this.to_x = this.x;
            this.to_y = this.y;
            game.state = "lost";
            setMessage("You fall beneath the gatehouse stones. Press R to try again.", 5000);
        }
    };
    this.resetAdventure = function() {
        this.x = s * 3;
        this.y = s * 3;
        this.to_x = this.x;
        this.to_y = this.y;
        this.health = this.origin_health;
        this.coins = 0;
        this.belt.items = [];
        this.currentDamage = 130;
    };
}

var hero = new HeroBarbarian(s * 3, s * 3);

function buildWalls() {
    walls.length = 0;
    // Border walls
    for (var y = 0; y < level.wall.map.length; y++) {
        for (var x = 0; x < level.wall.map[y].length; x++) {
            if (x === 0 || y === 0 || x === level.wall.map[y].length - 1 || y === level.wall.map.length - 1) {
                level.wall.map[y][x] = 948; // Use a wall tile
            } else if ((x % 7 === 0 && y % 7 > 1 && y % 7 < 6) || (y % 9 === 0 && x % 9 > 1 && x % 9 < 7)) {
                level.wall.map[y][x] = 372; // Internal obstacles
            } else {
                level.wall.map[y][x] = 0;
            }
            if (level.wall.map[y][x] > 0) walls.push(new Wall(level.wall.map[y][x], x * s, y * s));
        }
    }
    // Place a few objects
    for (var i = 0; i < 20; i++) {
        var ox = Math.floor(2 + Math.random() * (level.object.map[0].length - 4));
        var oy = Math.floor(2 + Math.random() * (level.object.map.length - 4));
        level.object.map[oy][ox] = 5844;
        walls.push(new WallObject(5844, ox * s, oy * s));
    }
}

function spawnMonster(config) {
    var point = randomFreePoint(s * 1.8);
    var mob = new AgressiveMob(point.x, point.y, config.type);
    mob.title = config.title || config.type;
    mob.origin_health = mob.health = config.health || mob.health;
    mob.currentDamage = config.damage || mob.currentDamage;
    mob.resistance = typeof config.resistance === "number" ? config.resistance : mob.resistance;
    mob.st = config.speed || mob.st;
    mob.lootCoins = config.lootCoins || Math.floor(130 + Math.random() * 170);
    monsters.push(mob);
    return mob;
}

function spawnBarrels(count) {
    for (var i = 0; i < count; i++) {
        var point = randomFreePoint(s * 1.2);
        barrels.push(new Barrel(point.x, point.y));
    }
}

function spawnPotions(count) {
    for (var i = 0; i < count; i++) {
        var point = randomFreePoint(s * 1.1);
        potions.push(new PotionHealth(point.x, point.y));
    }
}

function setupStage(index) {
    var stage = STAGES[index];
    monsters.length = 0;
    deathmobs.length = 0;
    barrels.length = 0;
    coins.length = 0;
    potions.length = 0;
    interactives.length = 0;
    stageKills = 0;

    hero.to_x = hero.x;
    hero.to_y = hero.y;
    hero.health = Math.min(hero.origin_health, hero.health + stage.healOnStart);
    hero.x = s * 3;
    hero.y = s * 3;
    hero.to_x = hero.x;
    hero.to_y = hero.y;

    for (var groupIndex = 0; groupIndex < stage.monsters.length; groupIndex++) {
        var group = stage.monsters[groupIndex];
        for (var count = 0; count < group.count; count++) spawnMonster(group);
    }

    spawnBarrels(stage.barrelCount);
    spawnPotions(stage.potionCount);

    game.gate = new AncientGate(s * 8.3, s * 2.3);
    game.gate.label = "Ward Locked";
    interactives.push(game.gate);
    game.questText = stage.intro;
    setMessage(stage.name + ": " + stage.intro, 3000);
}

function startGame() {
    hero.resetAdventure();
    game.state = "playing";
    game.stageIndex = 0;
    setupStage(0);
}

function advanceStage() {
    game.stageIndex++;
    if (game.stageIndex >= STAGES.length) {
        game.state = "won";
        setMessage("The relic is yours. The gauntlet is broken.", 4000);
        return;
    }
    setupStage(game.stageIndex);
}

function loadZb(order, click) {
    var tmp_zb = [];
    var zb = [];
    var all = [monsters, potions, barrels, interactives, click ? [] : [hero], click ? [] : walls];
    for (var t in all) {
        for (var m in all[t]) {
            if (all[t][m].isAboveHero()) tmp_zb.push(all[t][m]);
        }
    }
    tmp_zb.sort(function(objA, objB) {
        var compare = (objB.x + objB.offset_x) + (objB.y + objB.offset_y) - (objA.x + objA.offset_x) - (objA.y + objA.offset_y);
        return order ? compare : -compare;
    });
    var ordered = [coins, deathmobs, tmp_zb];
    for (var i in ordered) for (var j in ordered[i]) zb.push(ordered[i][j]);
    return zb;
}

function processClick() {
    var zb = loadZb(true, true);
    var cx = (floor.click_x - floor.click_y) * acos;
    var cy = (floor.click_x + floor.click_y) / 2 * asin;
    for (var i in zb) {
        var entity = zb[i];
        var sprite = entity.sprite;
        var sx = (entity.x - entity.y) * acos + entity.offset_x;
        var sy = (entity.x + entity.y) / 2 * asin + entity.offset_y;

        var spriteWidth = sprite.angles ? sprite.width / sprite.angles : sprite.width;
        var spriteHeight = sprite.steps ? sprite.height / sprite.steps : sprite.height;
        if (cx >= sx - spriteWidth / 2 && cx <= sx + spriteWidth / 2 && cy >= sy - spriteHeight && cy <= sy) {
            entity.use(hero);
            return true;
        }
    }
    return false;
}

floor.canvas.onclick = function(e) {
        console.log('Canvas clicked');
    if (game.state === "won" || game.state === "lost") {
        startGame();
        return;
    }
    if (game.state !== "playing") return;

    // Get canvas bounding rect for correct mouse position
    var rect = floor.canvas.getBoundingClientRect();
    var mx = (e.clientX - rect.left) - floor.w / 2 + th;
    var my = (e.clientY - rect.top) - floor.h / 2;

    // Inverse isometric transform to get world coordinates
    var mrx = hero.x * acos - hero.y * asin;
    var mry = (hero.x * asin + hero.y * acos) / 2;
    var wx = (mx + mrx) / acos;
    var wy = (2 * my + mry * 2 - mx - mrx) / (2 * asin);

    floor.click_x = wx;
    floor.click_y = wy;
    console.log('[CLICK]', {
        hero_x: hero.x,
        hero_y: hero.y,
        click_x: wx,
        click_y: wy,
        to_x: hero.to_x,
        to_y: hero.to_y
    });
    processClick(); // still allow interaction, but always set move target
    hero.to_x = floor.click_x;
    hero.to_y = floor.click_y;
    if (typeof hero.setState === 'function' && hero.run) hero.setState(hero.run);
    console.log('[MOVE TARGET]', {to_x: hero.to_x, to_y: hero.to_y});
};

window.onkeydown = function(e) {
    var beltKeys = [49,50,51,52,53,54,55,56,57,48];
    var beltIndex = beltKeys.indexOf(e.keyCode);
    if (beltIndex >= 0) {
        if (hero.belt.items[beltIndex] instanceof PotionHealth) {
            hero.belt.items[beltIndex].drink(hero);
            delete hero.belt.items[beltIndex];
        }
        return false;
    }
    if (e.keyCode === 9 || e.keyCode === 77) {
        game.showMap = !game.showMap;
        return false;
    }
    if (e.keyCode === 82) {
        startGame();
        return false;
    }
};

function renderObjects() {
    var zb = loadZb(false);
    for (var z in zb) {
        var entity = zb[z];
        floor.save();
        var sx = (entity.x - entity.y) * acos + entity.offset_x;
        var sy = (entity.x + entity.y) / 2 * asin + entity.offset_y;
        var tile = entity.sprite;
        var renderW = tile.width;
        var renderH = tile.height;
        if (tile.steps && tile.angles) {
            renderW /= tile.steps;
            renderH /= tile.angles;
            floor.drawImage(tile, renderW * entity.step, renderH * entity.angle, renderW, renderH, Math.round(sx - renderW / 2 - tile.offsetX), Math.round(sy - renderH), renderW, renderH);
        } else {
            if (entity instanceof AncientGate && !entity.active) floor.globalAlpha = 0.45;
            floor.drawImage(tile, Math.round(sx - tile.width / 2) + 1, Math.round(sy - tile.height) + 1);
        }
        floor.restore();

        if (entity.health && entity.origin_health && entity !== hero) {
            floor.save();
            floor.globalAlpha = 0.75;
            sy -= 92;
            var maxLine = Math.floor(entity.origin_health / 20);
            var currentLine = Math.max(0, Math.floor(entity.health / 20));
            floor.fillStyle = "black";
            floor.fillRect(sx - maxLine / 2 - 1, sy, maxLine + 2, 6);
            floor.fillStyle = entity.title === "Crypt Warden" ? "#d9c26e" : "#b22222";
            floor.fillRect(sx - maxLine / 2, sy + 1, currentLine, 4);
            floor.restore();
        }

        if (entity instanceof AncientGate || entity.title === "Crypt Warden" || entity.title === "Bone Captain") {
            floor.save();
            floor.textAlign = "center";
            floor.fillStyle = "#d8c392";
            floor.font = "14px Verdana";
            floor.fillText(entity.label || entity.title, sx, sy - 120);
            floor.restore();
        }
    }
}

function renderFloor() {
    floor.save();
    floor.translate(floor.w / 2 - th, floor.h / 2);
    var fdx = Math.floor(hero.x / s);
    var fdy = Math.floor(hero.y / s);
    var miny = Math.max(0, fdy - visible);
    var maxy = Math.min(level.floor.map.length - 1, fdy + visible);
    var minx = Math.max(0, fdx - visible);
    var maxx = Math.min(level.floor.map[0].length - 1, fdx + visible);
    var mrx = hero.x * acos - hero.y * asin;
    var mry = hero.x * asin + hero.y * acos;
    mry = mry / 2;
    floor.translate(-mrx, -mry);
    for (var y = miny; y <= maxy; y++) {
        for (var x = minx; x <= maxx; x++) {
            var tile = getFloorTile(x, y);
            if (tile) {
                var tx = (x - y) * th;
                var ty = (x + y) * th / 2;
                floor.drawImage(tile, tx, ty, tile.width + 0.707, tile.height + 0.707);
            }
        }
    }
    floor.translate(th, 0);
    // DEBUG: draw walkability overlay
    drawWalkabilityOverlay(floor);
    renderObjects();
    floor.restore();
}

function renderMap() {
    floor.save();
    floor.translate(floor.w - 180, 160);
    var sc = 0.25;
    floor.scale(1 * sc, 0.5 * sc);
    floor.rotate(Math.PI * 0.25);
    floor.translate(-hero.x, -hero.y);
    floor.fillStyle = "rgba(0,0,0,0.55)";
    var wallOffset = [];
    for (var y = 4; y >= 0; y--) for (var x = 0; x <= 4; x++) wallOffset.push({x: x * s / 5, y: y * s / 5});
    for (var i in walls) {
        var wall = walls[i];
        var walk = wall.header.walk;
        if (wall.header.orientation === 4) continue;
        for (var j = 0; j < 25; j++) if (walk[j] === 1) floor.fillRect(wall.x + wallOffset[j].x, wall.y + wallOffset[j].y, s / 5, s / 5);
    }
    floor.fillStyle = "rgba(216,195,146,0.95)";
    floor.fillRect(hero.x, hero.y, s / 5, s / 5);
    if (game.gate) {
        floor.fillStyle = game.gate.active ? "rgba(70,200,110,0.85)" : "rgba(210,90,70,0.75)";
        floor.fillRect(game.gate.x, game.gate.y, s / 5, s / 5);
    }
    floor.restore();
}

function renderHeroHealth() {
    var radius = 68;
    var padding = 22;
    floor.save();
    floor.globalAlpha = 0.55;
    floor.fillStyle = "black";
    floor.beginPath();
    floor.arc(radius + padding, floor.h - radius - padding, radius + 5, 0, Math.PI * 2);
    floor.closePath();
    floor.fill();
    floor.fillStyle = "#851414";
    var percent = hero.health / hero.origin_health;
    var angleFrom = Math.PI * (0.5 - percent);
    var angleTo = Math.PI * (0.5 + percent);
    floor.beginPath();
    floor.arc(radius + padding, floor.h - radius - padding, radius, angleFrom, angleTo);
    floor.closePath();
    floor.fill();
    floor.restore();

    floor.save();
    floor.fillStyle = "#d8c392";
    floor.font = "bold 14px Verdana";
    floor.fillText("HP", 18, floor.h - 26);
    floor.fillText(Math.floor(hero.health) + " / " + hero.origin_health, 62, floor.h - 26);
    floor.restore();
}

function renderHeroBelt() {
    floor.save();
    var tile = potionSprite;
    var slotW = tile.width / tile.steps;
    var slotH = tile.height / tile.angles;
    for (var i = 0; i < hero.belt.size; i++) {
        floor.globalAlpha = 0.8;
        floor.drawImage(tile, slotW * 2, slotH * 3, slotW, slotH, 188 + slotW * i, 606, slotW, slotH);
        floor.globalAlpha = 1;
        var potion = hero.belt.items[i];
        if (potion) floor.drawImage(tile, slotW * potion.step, slotH * potion.angle, slotW, slotH, 188 + slotW * i, 606, slotW, slotH);
        floor.fillStyle = "#d8c392";
        floor.font = "11px Verdana";
        floor.fillText((i + 1) % 10, 200 + slotW * i, 655);
    }
    floor.restore();
}

function renderHud() {
    floor.save();
    floor.fillStyle = "rgba(0,0,0,0.55)";
    floor.fillRect(18, 16, 310, 128);
    floor.strokeStyle = "rgba(216,195,146,0.5)";
    floor.strokeRect(18, 16, 310, 128);
    floor.fillStyle = "#d8c392";
    floor.font = "bold 22px Verdana";
    floor.fillText("PeakeCoin Gauntlet", 34, 44);
    floor.font = "14px Verdana";
    var stage = STAGES[game.stageIndex] || {name: "Preparing..."};
    floor.fillText("Stage: " + stage.name, 34, 70);
    floor.fillText("Enemies left: " + monsters.length, 34, 92);
    floor.fillText("Gold: " + hero.coins, 34, 114);
    floor.fillText("Objective: " + (game.gate && game.gate.active ? game.gate.label : "Defeat every foe"), 34, 136);
    floor.restore();
}

function renderMessage() {
    if (!game.message) return;
    floor.save();
    floor.fillStyle = "rgba(0,0,0,0.65)";
    floor.fillRect(210, 18, 560, 38);
    floor.strokeStyle = "rgba(216,195,146,0.5)";
    floor.strokeRect(210, 18, 560, 38);
    floor.fillStyle = "#f0e0b6";
    floor.font = "15px Verdana";
    floor.textAlign = "center";
    floor.fillText(game.message, 490, 43);
    floor.restore();
}

function renderHelp() {
    floor.save();
    floor.fillStyle = "rgba(0,0,0,0.45)";
    floor.fillRect(680, 530, 282, 108);
    floor.strokeStyle = "rgba(216,195,146,0.4)";
    floor.strokeRect(680, 530, 282, 108);
    floor.fillStyle = "#d8c392";
    floor.font = "13px Verdana";
    floor.fillText("Click to move. Click nearby targets to attack or loot.", 694, 555);
    floor.fillText("1-0 drink belt potions. M or Tab toggles the map.", 694, 578);
    floor.fillText("R restarts the run at any time.", 694, 601);
    floor.fillText("Clear each trial, then touch the gate to continue.", 694, 624);
    floor.restore();
}

function renderOverlay() {
    if (game.state === "playing") return;
    floor.save();
    floor.fillStyle = "rgba(0,0,0,0.55)";
    floor.fillRect(0, 0, floor.w, floor.h);
    floor.fillStyle = "#d8c392";
    floor.textAlign = "center";
    floor.font = "bold 42px Verdana";
    var title = game.state === "won" ? "Victory" : (game.state === "lost" ? "Defeat" : "Loading");
    floor.fillText(title, floor.w / 2, 220);
    floor.font = "18px Verdana";
    if (game.state === "won") {
        floor.fillText("You emerged with " + hero.coins + " gold and shattered the gauntlet.", floor.w / 2, 270);
        floor.fillText("Click or press R to begin another run.", floor.w / 2, 305);
    } else if (game.state === "lost") {
        floor.fillText("The wardens hold the chamber. Click or press R to try again.", floor.w / 2, 270);
    } else {
        floor.fillText("Gathering sprites and stonework...", floor.w / 2, 270);
    }
    floor.restore();
}

function updateMonsters() {
    if (game.state !== "playing") return;
    for (var i = 0; i < monsters.length; i++) monsters[i].nextStep();
}

setInterval(function() {
    if (game.state !== "playing") return;
    if (monsters.length === 0) return;
    var randomMob = monsters[Math.ceil(Math.random() * (monsters.length - 1))];
    if (randomMob && typeof randomMob.attacked !== "object") {
        randomMob.to_x = randomMob.x + (Math.random() * s - s / 2);
        randomMob.to_y = randomMob.y + (Math.random() * s - s / 2);
    }
    for (var i = 0; i < monsters.length; i++) {
        var mob = monsters[i];
        var attackDist = mob.title === "Crypt Warden" ? 128 : 104;
        if (mob.attack && mob.isAboveHero()) {
            if (Math.abs(hero.x - mob.x) < attackDist && Math.abs(hero.y - mob.y) < attackDist) {
                mob.doAttack(hero);
                mob.to_x = mob.x;
                mob.to_y = mob.y;
            } else {
                mob.to_x = hero.x;
                mob.to_y = hero.y;
            }
        }
    }
}, 220);

setInterval(function() {
    if (game.state === "playing") hero.health = Math.min(hero.origin_health, hero.health + 18);
}, 2500);

setInterval(function() {
    clearMessage();
    if (imageCount > 0) {
        floor.fillStyle = "black";
        floor.fillRect(0, 0, floor.w, floor.h);
        renderOverlay();
        return;
    }
    if (game.state === "loading") startGame();
    if (game.state === "playing") hero.nextStep();
    updateMonsters();
    floor.fillStyle = "black";
    floor.fillRect(0, 0, floor.w, floor.h);
    renderFloor();
    renderHeroHealth();
    renderHeroBelt();
    renderHud();
    renderHelp();
    renderMessage();
    if (game.showMap) renderMap();
    renderOverlay();
}, 66);

buildWalls();
})();



🪙 PeakeCoin Ecosystem


⚙️ HiveP.I.M.P. — Market Stabilization Layer

::contentReference[oaicite:0]{index=0}

Operated by hivepimp@hivepimp, P.I.M.P. stabilizes PEK markets and supports liquidity on Hive Engine.
Community participation strengthens long-term market health.
👉 https://peakecoin.info


🤖 PeakeBot — Autonomous Trading System (RC-AWARE)

::contentReference[oaicite:1]{index=1}

Independent multi-token trading bot featuring:

  • RC-aware execution
  • Adaptive delay logic
  • Self-regulating trade cycles

📊 Trading bot details:
👉 https://geocities.ws/p/e/peakecoin/trading-bot/peakebot_v0_01.html
💻 Open-source repositories:
👉 https://github.com/paulmoon410


🎰 PeakeSino — The PeakeCoin Casino (Beta)

::contentReference[oaicite:2]{index=2}

Blockchain-powered games using PEK as the native in-game currency.
Built on Hive with provable fairness and community-driven growth.

🃏 Play the beta games here:
👉 https://geocities.ws/peakecoin/pek_casino/beta_games/index.html


🙏 Acknowledgements

Thanks to and please follow:
enginewitty@enginewitty ecoinstant@ecoinstant neoxian@neoxian txracer@txracer thecrazygm@thecrazygm holdonia@holdonia aggroed@aggroed

For their continued support, guidance, and help expanding the PeakeCoin ecosystem.


PekAblo - Move Mechanics Problems | Ecency