
//
// All awful code rushed out in less than 40 hours is copyright (c) 2009 Zachary Johnson
//

// Array containing the player's path through the quest maps so I can look at them for my enjoyment later.
var movements = [];

window.onunload = function ()
{
    $.get('/lab/equip-pants/log.php', {'movements': movements.toString()});
};

// Directional Bit Constants
var NORTH = 1;
var SOUTH = 2;
var EAST = 4;
var WEST = 8;

// Bitfields
var keyDir = 0;
var moveDir = 0;

// # of tiles to move delta
var mouseDirs = {tx: 0, ty: 0};

// Timer reference
//var moveTimer;

// The center of your guy in relation to the map's edge (pixels; constant)
var youLoc = {x: VIEWPORTWIDTH / 2, y: VIEWPORTHEIGHT / 2};

// The offset of the map from the browser's client area top left corner. (pixels; constant set at run time)
var mapLoc = {x: null, y: null};

// Map Data
var map = { // Array item is an array of chunk tiles... not sure why this is preset to 100?
            chunkTiles: new Array(100),
            
            // Size (pixels)
            width: 0,
            height: 0
            };

// The map sprites div (constant)
var spritesDiv = null;

// Your top,left coordinates in the map's coordinate system (pixels)
var mcoords = {x: 0, y: 0};

// The tiles are in chunks that move across the screen
var mapChunks = {
                    // Array item is a Chunk object
                    chunkObjs: new Array(NUMCHUNKS),
                
                    // The indices into the chunks array of Chunks which are free (not currently on screen)
                    freeIndices: new Array(),
                
                    init: function() {
                        // Get all chunk divs
                        var chunkDivs = $('#mapViewport div.chunk').get();

                        //alert(mcoords.x + ',' + mcoords.y);
                        
                        // Starting x,y in pixels of first Chunk
                        var x = -1 * ((CHUNKWIDTH - (youLoc.x - ((TILEWIDTH * 0.5) + (TILEWIDTH * ((mcoords.x / TILEWIDTH) % (CHUNKWIDTH / TILEWIDTH)))))) % CHUNKWIDTH);
                        var y = -1 * ((CHUNKHEIGHT - (youLoc.y - ((TILEHEIGHT * 0.5) + (TILEHEIGHT * ((mcoords.y / TILEHEIGHT) % (CHUNKHEIGHT / TILEHEIGHT)))))) % CHUNKHEIGHT);
                        
                        var i;
                        for (i = 0; i < NUMCHUNKS; i++) {
                            // Create a new Chunk object for each chunk div
                            this.chunkObjs[i] = new Chunk(chunkDivs[i]);

                            // Chunks outside of the field of view are free
                            if (y >= VIEWPORTHEIGHT) {
                                this.freeChunk(i);

                            // Chunks in view need map tiles
                            } else {
                                this.chunkObjs[i].free = false;
                                this.chunkObjs[i].setTilesFromMap(mcoords.x + x, mcoords.y + y);
                            }

                            // Move the chunk div to the appropriate x,y pixel coordinates in the map viewport
                            this.chunkObjs[i].setXY(x, y);

                            this.chunkObjs[i].justAdded = false;

                            x += CHUNKWIDTH;
                            if (x >= VIEWPORTWIDTH) {
                                x = -1 * ((CHUNKWIDTH - (youLoc.x - ((TILEWIDTH * 0.5) + (TILEWIDTH * ((mcoords.x / TILEWIDTH) % (CHUNKWIDTH / TILEWIDTH)))))) % CHUNKWIDTH);
                                y += CHUNKHEIGHT;
                            }
                        }
                    },
                
                    // gain a chunk placed at pixel x,y
                    gainChunk: function (x, y) {
                        var i = this.freeIndices.pop();

                        this.chunkObjs[i].setXY(x, y);

                        this.chunkObjs[i].setTilesFromMap(mcoords.x + x, mcoords.y + y);

                        this.chunkObjs[i].free = false;
                        this.chunkObjs[i].justAdded = true;
                    },

                    // free chunk i of course
                    freeChunk: function (i) {
                        this.chunkObjs[i].free = true;
                        this.freeIndices.push(i);
                    },
                    
                    move: function () {
                        // This variable marks where we've added chunks to the screen so that
                        // the code can handle situations where a horizontal and vertical
                        // chunk are added at the same time, and add the needed diagnol chunk
                        var whereAdded;

                        // Move the chunks
                        var x;
                        var y;
                        var i;
                        for (i = 0; i < NUMCHUNKS; i++) {
                            // The whole justAdded thing keeps us from moving chunks we've
                            // just placed back into the not-free pool after moving some
                            // other chunk.
                            if (!this.chunkObjs[i].free && !this.chunkObjs[i].justAdded) {
                                x = this.chunkObjs[i].x;
                                y = this.chunkObjs[i].y;

                                if (moveDir & WEST) {
                                    x += MAPVELOCITY;
                                } else if (moveDir & EAST) {
                                    x -= MAPVELOCITY;
                                }

                                if (moveDir & NORTH) {
                                    y += MAPVELOCITY;
                                } else if (moveDir & SOUTH) {
                                    y -= MAPVELOCITY;
                                }

                                whereAdded = 0;

                                // Add new chunk on right side
                                if (this.chunkObjs[i].x >= 0 && x < 0) {
                                    this.gainChunk(x + VIEWPORTWIDTH, y);
                                    whereAdded |= EAST;

                                // Add new chunk on the left side
                                } else if (this.chunkObjs[i].x + CHUNKWIDTH <= VIEWPORTWIDTH && x + CHUNKWIDTH > VIEWPORTWIDTH) {
                                    this.gainChunk(x - VIEWPORTWIDTH, y);
                                    whereAdded |= WEST;
                                }

                                // Add new chunk on bottom
                                if (this.chunkObjs[i].y >= 0 && y < 0) {
                                    this.gainChunk(x, y + VIEWPORTHEIGHT);
                                    whereAdded |= SOUTH;

                                // Add new chunk on the top
                                } else if (this.chunkObjs[i].y + CHUNKHEIGHT <= VIEWPORTHEIGHT && y + CHUNKHEIGHT > VIEWPORTHEIGHT) {
                                    this.gainChunk(x, y - VIEWPORTHEIGHT);
                                    whereAdded |= NORTH;
                                }

                                /* Needed when able to move multiple directions /*
                                // Add Top Right
                                if (whereAdded & NORTH && whereAdded & EAST) {
                                    this.gainChunk(x + 2 * CHUNKWIDTH, y - 2 * CHUNKHEIGHT);

                                // Add Bottom Right
                                } else if (whereAdded & SOUTH && whereAdded & EAST) {
                                    this.gainChunk(x + 2 * CHUNKWIDTH, y + 2 * CHUNKHEIGHT);

                                // Add Top Left
                                } else if (whereAdded & NORTH && whereAdded & WEST) {
                                    this.gainChunk(x - 2 * CHUNKWIDTH, y - 2 * CHUNKHEIGHT);

                                // Add Bottom Left
                                } else if (whereAdded & SOUTH && whereAdded & WEST) {
                                    this.gainChunk(x - 2 * CHUNKWIDTH, y + 2 * CHUNKHEIGHT);
                                }
                                */

                                // Free a no longer visible chunk
                                if (x >= VIEWPORTWIDTH || x + CHUNKWIDTH <= 0 || y >= VIEWPORTHEIGHT || y + CHUNKHEIGHT <= 0) {
                                    this.freeChunk(i);
                                }

                                this.chunkObjs[i].setXY(x, y);
                                
                                //debug(i + ': ' + this.chunkObjs[i].x + ',' + this.chunkObjs[i].y);
                                //debug(i+': '+(this.chunkObjs[i].x-youLoc.x)+','+(this.chunkObjs[i].y-youLoc.y));
                            }
                        }

                        for (i = 0; i < NUMCHUNKS; i++) {
                            this.chunkObjs[i].justAdded = false;
                        }
                    }
                }; // mapChunks


function Chunk(div) {
    this.x = 0;
    this.y = 0;
    this.div = div;
    this.tiles = new Array(TILESPERCHUNK); // array item is type of tile (expressed as int/char/whatever)
    this.free = true;
    this.justAdded = false;
}

// Set screen x,y coordinates (in pixels) of this chunk... relative to map viewport (div#mapViewport) corner
Chunk.prototype.setXY = function (x, y) {
    this.x = x;
    $(this.div).css('left', x + 'px');

    this.y = y;
    $(this.div).css('top', y + 'px');
}

// Apply map tiles to chunk based on location of top,left corner of chunk in map pixel coordinates mx, my
Chunk.prototype.setTilesFromMap = function (mx, my) {
    //var c = (((my + (TILEWIDTH * 0.5)) / CHUNKHEIGHT) * (map.width / CHUNKWIDTH)) + ((mx + (TILEWIDTH * 0.5)) / CHUNKWIDTH);
    //var c = ((((my + (TILEWIDTH * 0.5)) / CHUNKHEIGHT) * map.width) + (mx + (TILEWIDTH * 0.5))) / CHUNKWIDTH; // should be == above, but with less divides
    
    //mx += (TILEWIDTH * 0.5) - youLoc.x;
    //my += (TILEHEIGHT * 0.5) - youLoc.y;
    
    //alert(mx + ',' + my);
    
    // Offset into map chunks array
    // Same formula as in pngToMap
    var c = Math.floor(my / CHUNKHEIGHT) * (map.width / CHUNKWIDTH) + Math.floor(mx / CHUNKWIDTH);
    
    // bg position
    var p;
    
    // offset into tile list
    var t;
    
    // Copy map's chunk tile list to active chunk tile list
    this.tiles = map.chunkTiles[c];
    
    for (t = 0; t < this.tiles.length; t++) {
        switch (this.tiles[t]) {
            case 0:
            case 254:
                p = '-100px 0';
                bgc = '#000';
                break;
            
            case 1:
                p = '0 0';
                bgc = '#00c';
                break;
            
            case 3:
                p = '0 -150px';
                break;
            
            case 6:
                p = '-100px -50px';
                bgc = '#666';
                break;
        
            case 9:
                p = '-50px -100px';
                bgc = '#666';
                break;
            
            case 10:
                p = '-100px -100px';
                break;
            
            case 128:
                p = '-50px 0';
                bgc = '#0c0';
                break;
            
            case 2:
            case 129:
                p = '0 -50px';
                bgc = '#060';
                break;
            
            case 130:
                p = '-50px -50px';
                bgc = '#cc0';
                break;
            
            case 131:
                p = '-150px 0';
                bgc = '#900';
                break;
            
            case 254:
                p = '-150px -50px';
                break;
            
            case 255:
                p = '0 -100px';
                bgc = '#fff';
                break;
        }
        
        //$(this.div.childNodes[t]).css('background-position', p);
        $(this.div.childNodes[t]).css('background-color', bgc);
    }
}


///////////////////


$(ready);

function ready()
{
    /* breaks mouse controls /*
    var scale = {x: 1, y: 1};
    scale.x = ($(window).width() - 50) / VIEWPORTWIDTH;
    scale.y = ($(window).height() - 50) / VIEWPORTHEIGHT;
    if (scale.x < scale.y)
    {
        scale = scale.x;
    }
    else
    {
        scale = scale.y;
    }
    $('#mapViewport').css('WebkitTransform', 'scale(' + scale + ')');
    $('#mapViewport').css('MozTransform', 'scale(' + scale + ')');
    */
    
    title();
}


function title()
{
    $(document).bind('keydown', keyHandler);
    $(document).bind('keyup', keyHandler);
    
    $('#title_menu a').hover(
        function () {$('#title_menu a').removeClass('selected'); $(this).addClass('selected');},
        function () {}
    );
    
    $('#title_screen div.caption').slideDown();
    
    $('#start_game').click(function (e) {
        e.preventDefault();
        
        setup();
    });
}


function setup() {
    $('#you').show();
    
    $('#title_screen').hide();
    
    mapLoc.x = $('#mapViewport').offset().left;
    mapLoc.y = $('#mapViewport').offset().top;
    
    spritesDiv = $('#sprites').get();
    
    loadMap('overworld');
    //loadMap('cave', 21, 25);
    
    var menuItems = [
        {text: 'Talk', disabled: true},
        {text: 'Search', disabled: true},
        {text: 'Inventory', disabled: true, onclick: {fn: show_inventory}},
        {text: 'Cancel'}
    ];
    
    /*
    var mapContext = new YAHOO.widget.ContextMenu('mapContext', {trigger: 'mapViewport', itemdata: menuItems, lazyload: true});
    mapContext.triggerContextMenuEvent.subscribe(triggerContextHandler);
    mapContext.beforeShowEvent.subscribe(mapContextHandler);
    
    // Show and hide once so that xy property is correct first time user triggers context menu
    mapContext.show();
    mapContext.hide();
    */
    
    $('#battle_menu a').hover(
        function () {$('#battle_menu a').removeClass('selected'); $(this).addClass('selected');},
        function () {}
    );
    
    setInterval(moveChunks, MAPREFRESH);
    
    $('#mapViewport').bind('mouseup', mouseHandler);
}


function transitionIn(type, after)
{
    freeze_movement = true;
    
    if (typeof after != 'function')
    {
        after = function () {};
    }
    
    switch(type)
    {
        case 'square':
            $('#t_' + type)
            .css(
                {
                    left: (VIEWPORTWIDTH / 2) + 'px',
                    top: (VIEWPORTHEIGHT / 2) + 'px',
                    width: '10px',
                    height: '10px',
                    background: 'black',
                    border: '1px solid black'
                }
            )
            .show()
            .animate(
                {
                    borderWidth: (1 + VIEWPORTWIDTH / 2) + 'px',
                    left: 0,
                    top: 0
                },
                500,
                after
            );
            break;
        
        case 'spiral':
            $('#t_' + type).show();
            new Spiral(after).into();
            break;
    }
}

function transitionOut(type, after)
{
    if (typeof after != 'function')
    {
        after = function () {};
    }
    
    switch (type)
    {
        case 'square':
            $('#t_' + type)
            .css(
                {
                    left: 0,
                    top: 0,
                    width: '1px',
                    height: '1px',
                    background: 'transparent',
                    border: (VIEWPORTWIDTH / 2) + 'px solid black'
                }
            )
            .animate(
                {
                    borderWidth: '0',
                    width: 1 + VIEWPORTWIDTH + 'px',
                    height: 1 + VIEWPORTHEIGHT + 'px'
                },
                500,
                function ()
                {
                    $(this).hide();
                    freeze_movement = false;
                    (after)();
                }
            );
            break;
        
        case 'spiral':
            new Spiral(function () {$('#t_' + type).hide(); (after)();}).out();
            break;
    }
}


function Spiral(callback)
{
    var blocks = $('#t_spiral div');
    
    var rows = VIEWPORTHEIGHT / TILEHEIGHT;
    var cols = VIEWPORTWIDTH / TILEWIDTH;
    
    var i = 52;
    
    var c = 0;
    var n = 0;
    var dir = 0;
    var dist = 1;
    var step = function ()
    {
        if (n > blocks.length)
        {
            clearTimeout(timer);
            (callback)();
        }
        else
        {
            switch (dir % 4)
            {
                case 0:
                    i++;
                    break;
                
                case 1:
                    i += cols;
                    break;
                
                case 2:
                    i--;
                    break;
                
                case 3:
                    i -= cols;
                    break;
            }
            
            $(blocks[i]).css({left: leftPos});
            
            if (c > 0 && c % dist == 0)
            {
                dir++;
            }
            
            if (c > 0 && c % (dist * 2) == 0)
            {
                c = 0;
                dist++;
            }
            
            c++;
            n++;
        }        
    }
    
    var timer;
    
    var leftPos;
    
    this.into = function ()
    {
        leftPos = 0;
        timer = setInterval(step, 10);
    }
    
    this.out = function ()
    {
        leftPos = '-1000px';
        timer = setInterval(step, 10);
    }
}


function loadMap(mapkey, tx, ty) {
    transitionIn(
        'square',
        function ()
        {
            map = maps[mapkey];
            map.name = mapkey;
            map.chunkTiles = map.getChunkTiles();

            initSprites();

            if (map.loadSprites) {
                map.loadSprites();
            }

            if (typeof tx == 'undefined' || typeof ty == 'undefined') {
                moveToMapTile(map.enterPos[0], map.enterPos[1]);

            } else {
                moveToMapTile(tx, ty);
            }
            
            transitionOut('square');
        }
    );
}


function triggerContextHandler(e) {
    //this.cancel();
    //console.log(e);
}


function mapContextHandler(e) {
    //console.log(e);
    //console.log(this.cfg.getProperty('xy'));
    //console.log(Math.abs(this.cfg.getProperty('x') - youLoc.x));
    //console.log(mapLoc);
    
    if
    (
        Math.abs(this.cfg.getProperty('x') - youLoc.x - mapLoc.x) < TILEWIDTH / 2
        && Math.abs(this.cfg.getProperty('y') - youLoc.y - mapLoc.y) < TILEHEIGHT / 2
    )
    {
        this.getItems()[2].cfg.setProperty('disabled', false);
    }
    else
    {
        this.getItems()[2].cfg.setProperty('disabled', true);
    }
}


function show_inventory() {
    freeze_movement = true;
    
    $('#inventory').show();
}


function hide_inventory() {
    freeze_movement = false;
    
    $('#inventory').hide();
}



var battle;
function Battle()
{
    var self = this;
    
    self.init = function ()
    {
        $('#battle_menu a').click(self.menuClick);
        
        self.attacked();
    }
    
    self.menuClick = function (e)
    {
        e.preventDefault();
        
        $('#battle_menu').slideUp();
        
        if ($(this).html() == 'Fight')
        {
            self.fight();
        }
        else
        {
            self.run();
        }
    }
    
    self.attacked = function ()
    {
        var hp = 75 + Math.floor(Math.random() * 200);
        
        var msg = '<p>R. Dragon attacks!</p>';
        if (hp > 200)
        {
            msg = '<p>R. Dragon attacks! Critical Hit!</p>';
        }
        
        $('#info').slideUp(
            function ()
            {
                $('#info')
                .html(msg)
                .slideDown();
                
                $('#battle_monster').animate(
                    {left: '+=30px'},
                    function ()
                    {
                        self.damage(
                            hp,
                            'you',
                            function ()
                            {
                                $('#battle_monster').animate({left: '-=30px'});
                                $('#battle_menu').slideDown();
                            }
                        );
                    }
                );
            }
        );
    }
    
    var dragonhp = 550;
    self.damage = function (hp, victim, callback)
    {
        if (typeof callback != 'function')
        {
            callback = function () {}
        }
        
        if (victim == 'dragon')
        {
            $('#dmg').css({left: '110px', top: '215px'});
        }
        else
        {
            $('#dmg').css({left: '295px', top: '210px'});
        }
        
        $('#dmg').html('<span>' + hp.toString().split('').join('</span><span>') + '</span>');
        $('#dmg span').each(
            function ()
            {
                $(this).css('top', '-' + Math.round(10 + Math.random() * 15) + 'px').show().animate({'top': 0}, 750, 'easeOutBounce');
            }
        );
        setTimeout(
            function ()
            {
                if (victim == 'dragon')
                {
                    dragonhp -= hp;
                    
                    if (dragonhp <= 0)
                    {
                        $('#battle_monster').css('background', 'transparent').html('<span class="top"></span><span class="bottom"></span>');
                        $('#battle_monster span.top').css('background', 'transparent');
                        $('#battle_monster span.bottom').css('background', 'red');
                        
                        callback = function ()
                        {
                            $('#info').slideUp(
                                function ()
                                {
                                    $('#info')
                                    .html('<p>You have defeated R. Dragon.</p>')
                                    .slideDown();
                                    
                                    self.setNext(
                                        function ()
                                        {
                                            $('#dmg').html('');
                                            transitionIn(
                                                'square',
                                                function ()
                                                {
                                                    state.dragonIsAlive = false;
                                                    battle = null;
                                                    $('#battle').hide();
                                                    $('#sprites #dragon').hide();
                                                    map.spriteLocs['21,27'] = '';
                                                    map.spriteLocs['22,27'] = '';
                                                    map.spriteLocs['23,27'] = '';
                                                    map.spriteLocs['21,27'] = '';
                                                    map.spriteLocs['21,28'] = '';
                                                    map.spriteLocs['21,29'] = '';
                                                    transitionOut('square');
                                                }
                                            );
                                        }
                                    );
                                }
                            );
                        }
                    }
                }
                else
                {
                    var t = parseInt($('#hp').html()) - hp;
                    
                    if (t <= 50)
                    {
                        $('#you_label').css('color', '#f99');
                    }
                    
                    if (t <= 0)
                    {
                        t = 0;
                        
                        $('#battle_you span.top').css('background', 'transparent');
                        $('#battle_you span.bottom').css('background', 'red');
                        
                        callback = function ()
                        {
                            $('#info').slideUp(
                                function ()
                                {
                                    $('#info')
                                    .html('<p>Thou art dead.</p>')
                                    .slideDown();
                                    
                                    self.setNext(
                                        function ()
                                        {
                                            $('#dmg').html('');
                                            $('#t_spiral div').css('background', 'red');
                                            transitionIn('spiral');
                                        }
                                    );
                                }
                            );
                        }
                    }
                    
                    $('#hp').html(t.toString());
                }
                
                setTimeout(
                    function ()
                    {
                        $('#info').slideUp();
                        $('#dmg').html('');
                        (callback)();
                    },
                    1500
                );
            },
            500
        )
    }
    
    self.fight = function ()
    {
        var hp = 75 + Math.floor(Math.random() * 225);
        
        var msg = '<p>You attack!</p>';
        if (hp > 200)
        {
            msg = '<p>You attack! Critical Hit!</p>';
        }
        
        $('#info')
        .html(msg)
        .slideDown();
        
        $('#battle_you').animate(
            {left: '-=30px'},
            function ()
            {
                self.damage(
                    hp,
                    'dragon',
                    function ()
                    {
                        $('#battle_you').animate({left: '+=30px'});
                        //$('#battle_menu').slideDown();
                        self.attacked();
                    }
                );
            }
        );
    }
    
    self.run = function ()
    {
        $('#info')
        .html('<p>You try to run, but the way is blocked.</p>')
        .slideDown(
            function ()
            {
                self.setNext(
                    function ()
                    {
                        $('#info').slideUp();
                        //$('#battle_menu').slideDown();
                        self.attacked();
                    }
                );
            }
        );
    }
    
    var next;
    var nextTimer;
    self.setNext = function (newnext)
    {
        next = newnext;
        nextTimer = setTimeout(self.doNext, 4000);
    }
    
    self.doNext = function ()
    {
        if (nextTimer)
        {
            clearTimeout(nextTimer);
            nextTimer = null;
        }
        
        if (next)
        {
            (next)();
        }
        
        next = null;
    }
}


function moveToMapTile(tx, ty) {
    mcoords = {x: tx * TILEWIDTH, y: ty * TILEHEIGHT};
    //alert(mcoords.x);
    
    $(spritesDiv).css({'left': (-mcoords.x + VIEWPORTWIDTH / 2) + 'px', 'top': (-mcoords.y + VIEWPORTHEIGHT / 2) + 'px'});
    
    mapChunks.init();
}


function initSprites() {
    $(spritesDiv).html(''); //quick hack to keep the bartender from roaming
    
    $(spritesDiv).css('width', map.width + 'px');
    $(spritesDiv).css('height', map.height + 'px');
}


function moveChunks() {
    // There shouldn't be any pending Timers
    //clearTimeout(moveTimer);
    
    // If we're moving the chunks/map then we don't want to change
    // directions until we're moved an even tile distance.  
    if (!(mcoords.x % TILEWIDTH) && !(mcoords.y % TILEHEIGHT)) {
        if (mouseDirs.tx != 0 || mouseDirs.ty != 0) {
            /*
            if (Math.abs(mouseDirs.tx) > Math.abs(mouseDirs.ty)) {
                if (mouseDirs.tx > 0) {
                    moveDir = EAST;
                    mouseDirs.tx--;
                } else {
                    moveDir = WEST;
                    mouseDirs.tx++;
                }
            } else {
                if (mouseDirs.ty > 0) {
                    moveDir = SOUTH;
                    mouseDirs.ty--;
                } else {
                    moveDir = NORTH;
                    mouseDirs.ty++;
                }
            }
            */
            
            var mouseDir = null;
            
            if (Math.abs(mouseDirs.tx) > Math.abs(mouseDirs.ty)) {
                mouseDir = getXMouseDir();
                
                if (!mouseDir) {
                    mouseDir = getYMouseDir();
                }
                
            } else {
                mouseDir = getYMouseDir();
                
                if (!mouseDir) {
                    mouseDir = getXMouseDir();
                }
            }
            
            moveDir = mouseDir;
            
        } else {
            moveDir = keyDir;
        }
        
        if (freeze_movement)
        {
            moveDir = 0;
        }
    }
    
    if (!moveDir) {
        return;
    }
    
    // Check if there's a tile in our way
    if (!canMove(moveDir)) {
        stopMoving();
        return;
    }
    
    // Keep our world coordinates up to date
    if (moveDir & WEST) {
        mcoords.x -= MAPVELOCITY;
    } else if (moveDir & EAST) {
        mcoords.x += MAPVELOCITY;
    }
    
    if (moveDir & NORTH) {
        mcoords.y -= MAPVELOCITY;
    } else if (moveDir & SOUTH) {
        mcoords.y += MAPVELOCITY;
    }
    
    // Move sprites div
    $(spritesDiv).css({'left': (-mcoords.x + VIEWPORTWIDTH / 2) + 'px', 'top': (-mcoords.y + VIEWPORTHEIGHT / 2) + 'px'});    
    
    
    // Moved an even tile distance... Actions based on hero landing on a certain tile type here.
    if (!(mcoords.x % TILEWIDTH) && !(mcoords.y % TILEHEIGHT)) {
        
        // Time to record the direction moved
        movements.push(moveDir);
        
        var currentTile = getTile(youLoc.x, youLoc.y);
        
        // Town
        if (currentTile > 250) {
            loadMap(map.children[mcoords.x / TILEWIDTH + ',' + mcoords.y / TILEHEIGHT]);
            stopMoving();
            return;
        }
        
        var tx = mcoords.x / TILEWIDTH;
        var ty = mcoords.y / TILEHEIGHT;
        
        // Check for nearby sprite
        if (map.spriteLocs) {
            
            $('div.caption:visible').slideUp();
            
            // hack
            if (map.name == 'cave' && tx == 22 && ty == 30 && !state.pantsAreEquipped)
            {
                state.pantsAreEquipped = true;
                $('#you span.bottom').css('background-position', '-30px -15px');
                $('#pants').hide();
                $('#pants_caption div.caption').slideDown();
            }
            
            var tests = [
                (tx + 1) + ',' + ty,
                (tx - 1) + ',' + ty,
                tx + ',' + (ty + 1),
                tx + ',' + (ty - 1),
            ];
            
            for (var i = 0; i < tests.length; i++)
            {
                if (map.spriteLocs[tests[i]])
                {
                    // hacks... sprites need to be objects too
                    switch (map.spriteLocs[tests[i]])
                    {
                        case 'guard1':
                        case 'guard2':
                            if (state.pantsAreEquipped)
                            {
                                if (state.guardsHackCount == 0)
                                {
                                    $('#' + map.spriteLocs[tests[i]] + ' div.caption').html('<p>Guard: We can\'t let you in here with those terrible pants!</p>');
                                }
                                else if (state.guardsHackCount == 1)
                                {
                                    $('#' + map.spriteLocs[tests[i]] + ' div.caption').html('<p>Guard: Just kidding, you can come in.</p>');
                                     setTimeout(
                                        function ()
                                        {
                                            map.spriteLocs = {};
                                            map.spriteLocs['14,17'] = 'guard1';
                                            map.spriteLocs['14,20'] = 'guard2';
                                            
                                            $('#guard1').animate({'left': '+=90px'}, 800, 'linear');
                                            $('#guard2').animate({'left': '+=90px'}, 800, 'linear');
                                            $('#guard1').animate({'top': '-=30px'}, 200, 'linear');
                                            $('#guard2').animate({'top': '+=30px'}, 200, 'linear');
                                        },
                                        1000
                                    );
                                }
                                else
                                {
                                    $('#' + map.spriteLocs[tests[i]] + ' div.caption').html('<p>Guard: Those are fine pants.</p>');
                                }
                                
                                state.guardsHackCount++;
                            }
                            break;
                            
                        case 'woodsman':
                            setTimeout(function () {$('#woodsman span.bottom').css('background-position', '0 -15px');}, 2250);
                            setTimeout(function () {$('#woodsman span.top').css('background-position', '0 0');}, 3250);
                            break;
                        
                        case 'parent':
                            if (!state.pantsAreEquipped)
                            {
                                setTimeout(
                                    function ()
                                    {
                                        map.spriteLocs['30,23'] = '';
                                        map.spriteLocs['31,23'] = '';
                                        $('#parent').animate({'left': '-1000px'}, 7500, 'linear');
                                    },
                                    1250
                                );
                            }
                            break;
                        
                        case 'dragon':
                            freeze_movement = true;
                            
                            transitionIn(
                                'spiral',
                                function ()
                                {
                                    $('#battle').show();
                                    transitionOut(
                                        'spiral',
                                        function ()
                                        {
                                            $('#battle div.caption').not('#battle_menu').slideDown(500);
                                            setTimeout(function () {battle = new Battle(); battle.setNext(battle.init);}, 500);
                                        }
                                    );
                                }
                            );
                            break;
                    }
                    
                    $('#' + map.spriteLocs[tests[i]] + ' div.caption').slideDown();
                }
            }
        }
    }
    
    
    // Reached an edge
    if ((mcoords.x - youLoc.x) < 0 || (mcoords.x + youLoc.x) > map.width - TILEWIDTH || (mcoords.y - youLoc.y) < 0 || (mcoords.y + youLoc.y) > map.height - TILEHEIGHT) {
        if ((mcoords.x - youLoc.x) < 0) {
            mcoords.x = youLoc.x;
        }

        if ((mcoords.x + youLoc.x) > map.width - TILEWIDTH) {
            mcoords.x = map.width - TILEWIDTH - youLoc.x;
        }
        
        if ((mcoords.y - youLoc.y) < 0) {
            mcoords.y = youLoc.y;
        }

        if ((mcoords.y + youLoc.y) > map.height - TILEHEIGHT) {
            mcoords.y = map.height - TILEHEIGHT - youLoc.y;
        }
        
        // Check for edge of map exit
        if (map.exitMap) {
            // Time to record the direction moved
            movements.push(moveDir);

            map.exitMap();
        }
        
        stopMoving();
        return;
    }
    
    
    mapChunks.move();
    
        
    if (keyDir || moveDir) {
        //moveTimer = setTimeout(moveChunks, MAPREFRESH);
    }
}


// By pixel x,y
function getTile(x, y) {
    var i;
    var j;
    
    for (i = 0; i < NUMCHUNKS; i++) {
        if (x >= mapChunks.chunkObjs[i].x && y >= mapChunks.chunkObjs[i].y && x < mapChunks.chunkObjs[i].x + CHUNKWIDTH && y < mapChunks.chunkObjs[i].y + CHUNKHEIGHT) {
            j = Math.floor((y - mapChunks.chunkObjs[i].y) / TILEHEIGHT) * TILESPERROW + Math.floor((x - mapChunks.chunkObjs[i].x) / TILEWIDTH);
            
            return mapChunks.chunkObjs[i].tiles[j];
        }
    }
}


function canMove(dir) {
    var tile;
    
    var tx = mcoords.x / TILEWIDTH;
    var ty = mcoords.y / TILEHEIGHT;
    
    if (dir == NORTH) {
        tile = getTile(youLoc.x, youLoc.y - TILEHEIGHT * 0.5 - 1);
        ty--;
    } else if (dir == SOUTH) {
        tile = getTile(youLoc.x, youLoc.y + TILEHEIGHT * 0.5);
        ty++;
    } else if (dir == WEST) {
        tile = getTile(youLoc.x - TILEWIDTH * 0.5 - 1, youLoc.y);
        tx--;
    } else if (dir == EAST) {
        tile = getTile(youLoc.x + TILEWIDTH * 0.5, youLoc.y);
        tx++;
    }
    
    if (tile < 128 || (map.spriteLocs && map.spriteLocs[tx + ',' + ty])) {
        return false;
    }
    
    return true;
}


function stopMoving() {
    //clearTimeout(moveTimer);
    moveDir = 0;
    
    /* we actually want movement to resume later if a key is still being held down /*
    keyDir = 0;
    */
    
    mouseDirs.tx = 0;
    mouseDirs.ty = 0;
}


function getXMouseDir() {
    var mouseDir = null;
    
    if (mouseDirs.tx > 0 && canMove(EAST)) {
        mouseDir = EAST;
        mouseDirs.tx--;
    } else if (mouseDirs.tx < 0 && canMove(WEST)) {
        mouseDir = WEST;
        mouseDirs.tx++;
    }
    
    return mouseDir;
}


function getYMouseDir() {
    var mouseDir = null;
    
    if (mouseDirs.ty > 0 && canMove(SOUTH)) {
        mouseDir = SOUTH;
        mouseDirs.ty--;
    } else if (mouseDirs.ty < 0 && canMove(NORTH)) {
        mouseDir = NORTH;
        mouseDirs.ty++;
    }
    
    return mouseDir;
}


/*
function randomMap(i, wx, wy) {
    var map = '';
    
    var x;
    var y;
        
    var j;
    for (j = 0; j < TILESPERCHUNK; j++) {
        if (wx < 0 || wy < 0) {
            x = 'left';
            y = 'top';
        } else {
            x = Math.random() < 0.1 ? 'left' : 'right';
            y = Math.random() < 0.1 ? 'top' : 'bottom';
        }
        
        if (x == 'left' && y == 'top') {
            mapChunks.chunkObjs[i].tiles[j] = '0';
        } else {
            mapChunks.chunkObjs[i].tiles[j] = '128';
        }
        
        $D.setStyle(mapChunks.chunkObjs[i].div.childNodes[j], 'background-position', x+' '+y);
    }
}
*/


var freeze_movement = false;

var keysDown = {};

function keyHandler( e ) {
    var keycode = e.keyCode;
    
    if (e.type == 'keydown')
    {
        if (keysDown[keycode])
        {
            e.preventDefault();
            e.stopPropagation();
            return; // key is already down... implies rrrrrrepeat keydown event
        }
        
        keysDown[keycode] = true;
    }
    else if (e.type == 'keyup')
    {
        keysDown[keycode] = false;
    }
    
    // hacks
    var whichmenu = '';
    if (e.type == 'keydown')
    {
        //dragonHack();
        
        if (battle)
        {
            battle.doNext();
        }
        
        if ($('#battle_menu').is(':visible'))
        {
            whichmenu = '#battle_menu';
        }
        else if ($('#title_menu').is(':visible'))
        {
            whichmenu = '#title_menu';
        }
    }
    
    
    //var target = $E.getTarget( e );
    //var targetTag = target.nodeName;

    var dir = 0;
    switch (keycode) {
        // Left
        case 63234:
        case 37:
            dir = WEST;
            e.preventDefault();
            e.stopPropagation();
            break;

        // Up
        case 63232:
        case 38:
            dir = NORTH;
            e.preventDefault();
            e.stopPropagation();
            break;

        // Right
        case 63235:
        case 39:
            dir = EAST;
            e.preventDefault();
            e.stopPropagation();
            break;

        // Down
        case 63233:
        case 40:
            dir = SOUTH;
            e.preventDefault();
            e.stopPropagation();
            break;

        // a/A
        case 65:
        case 97:
            dir = WEST;
            break;

        // w/W
        case 87:
        case 119:
            dir = NORTH;
            break;

        // d/D
        case 68:
        case 100:
            dir = EAST;
            break;

        // s/S
        case 83:
        case 115:
            dir = SOUTH;
            break;
        
        case 32:
        case 13:
            dir = 'click';
            break;
        
        /*
        // i
        case 73:
            show_inventory();
            break;
        */
    }
    
    // so many hacks
    if (e.type == 'keydown' && whichmenu)
    {
        var mitems = $(whichmenu + ' a');
        
        if (!$(whichmenu).data('index'))
        {
            $(whichmenu).data('index', 0);
        }
        
        if (dir == SOUTH)
        {
            $(whichmenu).data('index', $(whichmenu).data('index') + 1);
        }
        else if (dir == NORTH)
        {
            $(whichmenu).data('index', $(whichmenu).data('index') - 1);
        }
        else if (dir == 'click')
        {
            $(mitems[$(whichmenu).data('index') % 2]).trigger('click');
            return;
        }
        
        $(mitems[$(whichmenu).data('index') % 2]).trigger('mouseover');
        return;
    }
    
    if (dir && dir != 'click')
    {        
        if (e.type == 'keydown' && !freeze_movement) {
            mouseDirs.tx = 0;
            mouseDirs.ty = 0;
            
            if (keyDir == 0) {
                //keyDir |= dir; //multiple directions
                keyDir = dir; //one direction
                //clearTimeout(moveTimer);
                //moveChunks();
            } else {
                //keyDir |= dir; //multiple directions
                keyDir = dir; //one direction
            }
        } else if (e.type == 'keyup') {
            keyDir &= ~dir;
        }
    }
}


/*
var dragonHackTime = false;
function dragonHack()
{
    if (dragonHackTime)
    {
        dragonHackTime = false;
        transitionIn(
            'spiral',
            function ()
            {
                $('#dragon div.caption').slideUp();
                $('#battle').show();
                $('#battle div.caption').slideDown();
            }
        );
    }
}
*/


function mouseHandler(e) {
    // hacks
    //dragonHack();
    if (battle)
    {
        battle.doNext();
    }
    
    if (!freeze_movement) {
        var x = e.clientX - mapLoc.x;
        var y = e.clientY - mapLoc.y;
    
        mouseDirs.tx = Math.round((x - youLoc.x) / TILEWIDTH);
        mouseDirs.ty = Math.round((y - youLoc.y) / TILEHEIGHT);

        //moveChunks();
    }
}


function debug(msg) {
    //$('debug').innerHTML += msg+'<br />';
    $('#debug').html(msg);
}
