Source: runner_functions.js

//#NOTE: If you want to see a new function/feature, just request it at: https://github.com/kaansoral/adventureland/issues
var character = parent.character;
var G = parent.G; // Game data
var safeties = true;

/**
 * Activates an item, likely a booster, in the num-th inventory slot
 * @param {number} num number of Inventory slot
 */
function activate(num)
{
    parent.activate(num);
}

/**
 * Shifts an item, likely a booster, in the num-th inventory slot to s pecific mode
 * shift(0,'goldbooster'); would set the booster int the first inventory slot to gold mode
 * Other modes are "xpbooster" and "luckbooster"
 * @param {number} num   - Position of Inventory Slot starting from 0
 * @param {string} name  - name of the Item
 */
function shift(num, name)
{
    parent.shift(num, name);
}
/**
 * (WIP) Check if the cooldown of a class skill has passed
 * @param {string} name - name of skill
 * @returns {boolean}   - true of cooldown has passed
 */
function can_use(name) // work in progress, can be used to check cooldowns of class skills [02/02/17]
{
    return parent.can_use(name);
}

/**
 * if name parameter is a number, the character will try to equip the Item in the specified slot.
 * Otherwise it will try to cast the skill with that name and Target
 * @param {number|string} name - skill name or item slot
 * @param {Monster} [target]   - target Entity when using skill
 */
function use(name, target) // a multi-purpose use function, works for skills too
{
    if (isNaN(name)) // if name is not an integer, use the skill
    {
        if (!target) target = get_target();
        parent.use_skill(name, target);
    }
    else {
        // for example, if there is a potion at the first inventory slot, use(0) would use it
        equip(name);
    }
}

/**
 * Will use a skill on the specified target
 * If no target is specified the current character target will be used
 * @param {string} name         - name of skill
 * @param {Monster} [target]    - target Entity
 */
function use_skill(name, target) {
    if (!target) target = get_target();
    parent.use_skill(name, target);
}

/**
 * Returns an object containing all the stat properties of an item
 * @param {Gear} item               - The Item in question
 * @returns {ItemStats|null}      - An Object containing all stats and there number or null on error
 */
function item_properties(item) // example: item_properties(character.items[0])
{
    if (!item || !item.name) return null;
    return calculate_item_properties(G.items[item.name], item);
}

/**
 *  Returns the item Grade in Number format.
 *  -1: Invalid Input
 *  0 : Normal
 *  1 : High
 *  2 : Rare
 * @param {Object} item
 * @param {string} item.name
 * @returns {number} - {-1:Something went wrong, 0:Normal, 1:High, 2:Rare}
 */
function item_grade(item) // example: item_grade(character.items[0])
{
    if (!item || !item.name) return -1;
    return calculate_item_grade(G.items[item.name], item);
}
/**
 * Returns how much the item is worth in gold
 * @param {Gear|Consumables} item
 * @returns {number} - How the item is worth in gold when sold to an npc
 */
function item_value(item) // example: item_value(character.items[0])
{
    if (!item || !item.name) return 0;
    return calculate_item_value(item);
}
/**
 * Return Socket.IO socket, this is the Object which the game uses to communicate with the server
 * @returns {socket}
 */
function get_socket() {
    return parent.socket;
}
/**
 * Returns current map the player is on
 * @returns {Map} current map the player is on
 */
function get_map() {
    return parent.G.maps[parent.current_map];
}
/**
 * When CODE is active uses a window in the bottom right hand corner to display a status messages
 * The window is pretty small so keep it short.
 * @param {string} text the text that should be displayed
 * @param {string} [color] the color in which the text should be displayed
 */
function set_message(text, color) {
    if (color) text = "<span style='color: " + color + "'>" + text + "</span>";
    $('#gg').html(text);
}
/**
 * Uses the game log to print out a message
 * @param {string} message the message to print
 * @param {string} [color=#51D2E1] the color in which to display the message
 */
function game_log(message, color) {
    if (!color) color = "#51D2E1";
    parent.add_log(message, color);
}
/**
 * Returns Entity which the Entity is targeting
 * @param {Monster} entity
 * @returns {Monster|null}
 */
function get_target_of(entity) // .target is a Name for Monsters and `id` for Players - this function return whatever the entity in question is targeting
{
    if (!entity || !entity.target) return null;
    if (character.id == entity.target) return character;
    for (var id in parent.entities) {
        var e = parent.entities[id];
        if (e.id == entity.target) return e;
    }
    return null;
}

/**
 * Returns the current target Entity of the character without checks
 * @returns {Monster|Player|null} - Returns the current target Entity of the character
 */
function get_target() {
    return parent.ctarget;
}

/**
 * Returns the current target Entity of the character but with additional checks.
 * This prevents the targeting of already dead targets or players
 * @returns {Monster|null} - Returns the current target Entity of the character
 */
function get_targeted_monster() {
    if (parent.ctarget && !parent.ctarget.dead && parent.ctarget.type == 'monster') return parent.ctarget;
    return null;
}

/**
 * Change the targeted entity
 * @param {Monster} target
 * @param {boolean} [send=false] - send change target to server
 */
function change_target(target, send) {
    parent.ctarget = target;
    if (!send) //no need to send the target on default for CODE, some people are using change_target 5-6 times in an interval
    {
        // user change_target(target,true) from now on to send the target to the server explicitly [23/10/16]
        if (target) parent.last_id_sent = target.id;
        else parent.last_id_sent = '';
    }
    parent.send_target_logic();
}

/**
 * Checks if there is a clear path to the coordinates or the entity.
 * For an entity you don't have to supply the second argument
 * @param {Monster|Player|number} x
 * @param {number} [y]
 * @returns {boolean}
 */
function can_move_to(x,y)
{
    if(is_object(x)) y=x.real_y,x=x.real_x;
    return can_move({map:character.map,x:character.real_x,y:character.real_y,going_x:x,going_y:y});
}

/**
 * Checks if the Entity is in attack range
 * @param {Player|Monster} target
 * @returns {boolean}
 */
function in_attack_range(target) // also works for priests/heal
{
    if (!target) return false;
    return (parent.distance(character, target) <= character.range);
}

/**
 * Checks if the character is able to attack the target
 * @param {Monster|Player} target - the Entity for which to check
 * @returns {boolean}
 */
function can_attack(target) // also works for priests/heal
{
    // is_disabled function checks .rip and .stunned
    if (!target) return false;
    return (!parent.is_disabled(character) && in_attack_range(target) && new Date() >= parent.next_attack);
}

/**
 * Checks if the character is able to heal the target
 * Same as can_attack but with a more intuitive name
 * @param {Monster|Player} target - the Entity for which to check
 * @returns {boolean}
 */
function can_heal(target) {
    return can_attack(target);
}

/**
 *
 * @param {Player|Character|Monster} entity
 * @returns {boolean}
 */
function is_moving(entity) {
    if (entity.me && smart.moving) return true;
    if (entity.moving) return true;
    return false;
}

/**
 * Is the entity using the town teleportation skill
 * @param entity
 * @returns {boolean}
 */
function is_transporting(entity) {
    if (entity.c.town) return true;
    if (entity.me && parent.transporting) return true;
    return false;
}

/**
 * Tries to attack the target
 * @param {Player|Monster} target - Target to attack
 */
function attack(target) {
    if (safeties && mssince(last_attack) < 400) return;
    if (!target) {
        game_log("Nothing to attack()", "gray");
        return;
    }
    if (target.type == "character") parent.player_attack.call(target);
    else parent.monster_attack.call(target);
    last_attack = new Date();
}

/**
 * Tries to heal the targeted Character
 * @param {Player} target
 */
function heal(target) {
    if (safeties && mssince(last_attack) < 400) return;
    if (!target) {
        game_log("No one to heal()", "gray");
        return;
    }
    parent.player_heal.call(target);
    last_attack = new Date();
}
/**
 * Buys items from NPC. The NPC has to be near enough to be able to buy from him.
 * @param {String} name      - Name of the Item
 * @param {number} quantity  - Quantity
 */
function buy(name, quantity) //item names can be spotted from show_json(character.items) - they can be bought only if an NPC sells them
{
    parent.buy(name, quantity);
}
/**
 * Sell the Item in the num-th inventory slot starting from 0.
 * @param {number} num       - Inventory slot
 * @param {number} quantity  - Quantity
 */
function sell(num, quantity) //sell an item from character.items by it's order - 0 to N-1
{
    parent.sell(num, quantity);
}
/**
 * Equips the Item in the num-th inventory Slot starting from 0.
 * @param {number} num
 */
function equip(num) {
    parent.socket.emit("equip", {num: num});
}

function trade(num, trade_slot, price) // where trade_slot is 1 to 16 - example, trade(0,4,1000) puts the first item in inventory to the 4th trade slot for 1000 gold [27/10/16]
{
    parent.trade("trade" + trade_slot, num, price);
}
/**
 *
 * @param {Player} target
 * @param {number} trade_slot
 */
function trade_buy(target, trade_slot) // target needs to be an actual player
{
    parent.trade_buy(trade_slot, target.id, target.slots[trade_slot].rid); // the .rid changes when the item in the slot changes, it prevents swap-based frauds [22/11/16]
}

/**
 *
 * @param {number} item_num
 * @param {number} scroll_num
 * @param {number} [offering_num]
 */
function upgrade(item_num, scroll_num, offering_num) //number of the item and scroll on the show_json(character.items) array - 0 to N-1
{
    parent.u_item = item_num;
    parent.u_scroll = scroll_num;
    parent.u_offering = offering_num;
    parent.upgrade();
}
/**
 * Uses the upgrade npc to combine jewelery
 * Combining works by taking 3 jewelery items of the same type and a scroll
 * for example -> compound(0,1,2,6) -> 3 items in the first 3 slots, scroll at the 6th spot
 * @param {number} item0 - Item one
 * @param {number} item1 - Item two
 * @param {number} item2 - Item three
 * @param {number} scroll_num
 * @param {number} [offering_num]
 */
function compound(item0, item1, item2, scroll_num, offering_num) // for example -> compound(0,1,2,6) -> 3 items in the first 3 slots, scroll at the 6th spot
{
    parent.c_items = [item0, item1, item2];
    parent.c_last = 3;
    parent.c_scroll = scroll_num;
    parent.c_offering = offering_num;
    parent.compound();
}

function exchange(item_num) {
    parent.e_item = item_num;
    parent.exchange(1);
}

/**
 * Acts as if the player had typed the message into the chat and then send it.
 * This also allows for the use of commands.
 * @param {string} message - The message
 */
function say(message) // please use responsibly, thank you! :)
{
    parent.say(message, 1);
}

/**
 * sets the character moving to specific coordinates
 * @param {number} x
 * @param {number} y
 */
function move(x, y) {
    if (!can_walk(character)) return;
    var move = parent.calculate_move(parent.M, character.real_x, character.real_y, parseFloat(x) || 0, parseFloat(y) || 0);
    character.from_x = character.real_x;
    character.from_y = character.real_y;
    character.going_x = move.x;
    character.going_y = move.y;
    character.moving = true;
    parent.calculate_vxy(character);
    parent.socket.emit("move", {
        x: character.real_x,
        y: character.real_y,
        going_x: character.going_x,
        going_y: character.going_y,
        m: character.m
    });
}
/**
 * A debug Command which opens a Window and shows the json representation of the Object.
 * @param {Object} e - The Object you like to inspect
 */
function show_json(e) // renders the object as json inside the game
{
    parent.show_json(parent.game_stringify(e, 2));
}
/**
 *
 * @param {String} name - The name of the character
 * @returns {Player} - The Player Object
 */
function get_player(name) // returns the player by name, if the player is within the vision area
{
    var target = null, entities = parent.entities;
    if (name == character.name) target = character;
    for (i in entities) if (entities[i].type == "character" && entities[i].name == name) target = entities[i];
    return target;
}
/**
 *
 * @param {Object} args
 * @param {string} args.type the monster type of {@link Monster}
 * @param {number} args.min_xp Minimum xp that the Monster should give
 * @param {number} args.max_att The Maximum attack value a Monster should have
 * @param {boolean} args.no_target Attack only targets that have no target
 * @param {boolean} args.path_check Check If you can walk straight to the target
 * @returns {Monster}
 */
function get_nearest_monster(args) {
    //args:
    // max_att - max attack
    // min_xp - min XP
    // target: Only return monsters that target this "name" or player object
    // no_target: Only pick monsters that don't have any target
    // path_check: Checks if the character can move to the target
    // type: Type of the monsters, for example "goo", can be referenced from `show_json(G.monsters)` [08/02/17]
    var min_d = 999999, target = null;

    if (!args) args = {};
    if (args && args.target && args.target.name) args.target = args.target.name;

    for (id in parent.entities) {
        var current = parent.entities[id];
        if (current.type != "monster" || current.dead) continue;
        if (args.type && current.mtype != args.type) continue;
        if (args.min_xp && current.xp < args.min_xp) continue;
        if (args.max_att && current.attack > args.max_att) continue;
        if (args.target && current.target != args.target) continue;
        if (args.no_target && current.target && current.target != character.name) continue;
        if (args.path_check && !can_move_to(current)) continue;
        var c_dist = parent.distance(character, current);
        if (c_dist < min_d) min_d = c_dist, target = current;
    }
    return target;
}
/**
 *
 * @param {Object} args
 * @param {Array.<string>} [args.exclude] A list of Player names which will not be considered hostile
 * @param {boolean} [args.friendship=true] Should friends be considered not hostile
 * @returns {Player}
 */
function get_nearest_hostile(args) // mainly as an example [08/02/17]
{
    var min_d = 999999, target = null;

    if (!args) args = {};
    if (args.friendship === undefined) args.friendship = true;

    for (id in parent.entities) {
        var current = parent.entities[id];
        if (current.type != "character" || current.rip || current.invincible || current.npc) continue;
        if (current.party && character.party == current.party) continue;
        if (current.guild && character.guild == current.guild) continue;
        if (args.friendship && in_arr(current.owner, parent.friends)) continue;
        if (args.exclude && in_arr(current.name, args.exclude)) continue; // get_nearest_hostile({exclude:["Wizard"]}); Thanks
        var c_dist = parent.distance(character, current);
        if (c_dist < min_d) min_d = c_dist, target = current;
    }
    return target;
}
/**
 * Uses a simple algorithm to decide weather to use a mana or health potion.
 * Tries to max out the health and mana.
 * This function should not be called in fast running loops or otherwise it will use a high amount of potions.
 */
function use_hp_or_mp() {
    if (safeties && mssince(last_potion) < 600) return;
    var used = false;
    if (new Date() < parent.next_potion) return;
    if (character.mp / character.max_mp < 0.2) use('use_mp'), used = true;
    else if (character.hp / character.max_hp < 0.7) use('use_hp'), used = true;
    else if (character.mp / character.max_mp < 0.8) use('use_mp'), used = true;
    else if (character.hp < character.max_hp) use('use_hp'), used = true;
    else if (character.mp < character.max_mp) use('use_mp'), used = true;
    if (used) last_potion = new Date();
}
/**
 * Looks for chests and tries to open them
 */
function loot() {
    var looted = 0;
    if (safeties && mssince(last_loot) < 200) return;
    last_loot = new Date();
    for (id in parent.chests) {
        var chest = parent.chests[id];
        if (safeties && (chest.items > character.esize || chest.last_loot && mssince(chest.last_loot) < 1600)) continue;
        chest.last_loot = last_loot;
        parent.socket.emit("open_chest", {id: id});
        looted++;
        if (looted == 2) break;
    }
}

/**
 * Send gold to another player
 * @param {Player|string} receiver - Either a character name or a OtherCharacter Object
 * @param {number} gold
 */
function send_gold(receiver, gold) {
    if (!receiver) return game_log("No receiver sent to send_gold");
    if (receiver.name) receiver = receiver.name;
    parent.socket.emit("send", {name: receiver, gold: gold});
}

/**
 * Send item to another player
 * @param {Player|string} receiver - Either a character name or a OtherCharacter Object
 * @param {number} num                     - Inventory slot starting from 0
 * @param {number} [quantity=1]                - Quantity
 */
function send_item(receiver, num, quantity) {
    if (!receiver) return game_log("No receiver sent to send_item");
    if (receiver.name) receiver = receiver.name;
    parent.socket.emit("send", {name: receiver, num: num, q: quantity || 1});
}

/**
 * Destorys an item in the num-th Inventory slot
 * @param {number} num
 */
function destroy_item(num) // num: 0 to 41
{
    parent.socket.emit("destroy", {num: num});
}

/**
 * Sens a party Invite to another character
 * @param {Player|string} name   - name ca be a player object, a player name, or an id
 * @param {boolean} is_request           - Is requesting to be invited
 */
function send_party_invite(name, is_request) // name could be a player object, name, or id
{
    if (is_object(name)) name = name.name;
    parent.socket.emit('party', {event: is_request && 'request' || 'invite', name: name});
}

/**
 * Request to be invited into a party
 * @param {Player|string} name   - name ca be a player object, a player name, or an id
 */
function send_party_request(name) {
    send_party_invite(name, 1);
}

/**
 * Accept party invite from player
 * @param {string} name
 */
function accept_party_invite(name) {
    parent.socket.emit('party', {event: 'accept', name: name});
}

/**
 * Accept party request from player.
 * @param {string} name
 */
function accept_party_request(name) {
    parent.socket.emit('party', {event: 'raccept', name: name});
}

/**
 * Lets the player respawn
 */
function respawn() {
    parent.socket.emit('respawn');
}

/**
 * When a character dies, character.rip is true, you can override handle_death and manually respawn
 * IDEA: A Resident PVP-Dweller, with an evasive Code + irregular respawning
 * respawn current has a 12 second cooldown, best wait 15 seconds before respawning [24/11/16]
 * setTimeout(respawn,15000);
 * return true;
 * NOTE: Add `if(character.rip) {respawn(); return;}` to your main loop/interval too, just in case
 */
function handle_death() {

    return -1;
}
/**
 *
 * @param command
 * @param args
 * @returns {number}
 */
function handle_command(command, args) // command's are things like "/party" that are entered through Chat - args is a string
{
    // game_log("Command: /"+command+" Args: "+args);
    // return true;
    return -1;
}
/**
 * Send CODE messages to the characters, of course it only works if both characters have CODE active.
 * @param {Array|string} to - Either an Array of names or just a name
 * @param {Object} data     - The data to be sent in object form
 */
function send_cm(to, data) {
    // to: Name or Array of Name's
    // data: JSON object
    parent.send_code_message(to, data);
}
/**
 *
 * @param {string} name - Sender of Code Message
 * @param {Object} data - An Object containing the information send
 */
function on_cm(name, data) {
    game_log("Received a code message from: " + name);
}
/**
 * This function gets called whenever an entity disappears
 * @param {Player|Monster}entity
 * @param data
 */
function on_disappear(entity, data) {
    // game_log("disappear: "+entity.id+" "+JSON.stringify(data));
}
/**
 * When multiple characters stay in the same spot, they receive combined damage, this function gets called whenever a monster deals combined damage.
 * Override this function in CODE to react to it
 */
function on_combined_damage()
{
    // move(character.real_x+5,character.real_y);
}
/**
 * Someone is inviting you to a party
 * @param {string} name - The name of the inviting player
 */
function on_party_invite(name) // called by the inviter's name
{
    // accept_party_invite(name)
}
/**
 * Someone requesting to join your existing party
 * @param {string} name  - The name of the player
 */
function on_party_request(name)
{
    // accept_party_request(name)
}
/**
 * Called just before the CODE is destroyed
 */
function on_destroy()
{
    clear_drawings();
}
/**
 * The game calls this function at the best place in each game draw frame, so if you are playing the game at 60fps, this function gets called 60 times per second
 */
function on_draw()
{

}
/**
 * @callback 
 * @param event
 */
function on_game_event(event) {
    if (event.name == "pinkgoo") {
        // start searching for the "Love Goo" of the Valentine's Day event
    }
    if (event.name == "goblin") {
        // start searching for the "Sneaky Goblin"
    }
}

var PIXI = parent.PIXI; // for drawing stuff into the game
var drawings = parent.drawings;
/**
 * Documentation: [https://pixijs.github.io/docs/PIXI.Graphics.html]{@link https://pixijs.github.io/docs/PIXI.Graphics.html}
 * keep in mind that drawings could significantly slow redraws, especially if you don't .destroy() them
 * @param x
 * @param y
 * @param x2
 * @param y2
 * @param size
 * @param color
 * @returns {PIXI.Graphics} [https://pixijs.github.io/docs/PIXI.Graphics.html]{@link https://pixijs.github.io/docs/PIXI.Graphics.html}
 */

function draw_line(x, y, x2, y2, size, color) {
    // keep in mind that drawings could significantly slow redraws, especially if you don't .destroy() them
    if (!color) color = 0xF38D00;
    if (!size) size = 1;
    e = new PIXI.Graphics();
    e.lineStyle(size, color);
    e.moveTo(x, y);
    e.lineTo(x2, y2);
    e.endFill();
    parent.drawings.push(e); //for the game to keep track of your drawings
    parent.map.addChild(e); //e.destroy() would remove it, if you draw too many things and leave them there, it will likely bring the game to a halt
    return e;
}
/**
 * Documentation: [https://pixijs.github.io/docs/PIXI.Graphics.html]{@link https://pixijs.github.io/docs/PIXI.Graphics.html}
 * Example: draw_circle(character.real_x,character.real_y,character.range) :) [22/10/16]
 * @param x
 * @param y
 * @param radius
 * @param size
 * @param color
 * @returns {PIXI.Graphics}
 */

function draw_circle(x, y, radius, size, color) {
    if (!color) color = 0x00F33E;
    if (!size) size = 1;
    e = new PIXI.Graphics();
    e.lineStyle(size, color);
    e.drawCircle(x, y, radius);
    parent.drawings.push(e);
    parent.map.addChild(e);
    return e;
}
/**
 *  Clears drawings on the screen.
 */
function clear_drawings() {
    drawings.forEach(function (e) {
        try {
            e.destroy({children: true})
        } catch (ex) {
        }
    });
    drawings = parent.drawings = [];
}

var game = {
    last: 0,
    callbacks: [],
    on: function (event, f) {

    },
    once: function (event, f) {

    },
    remove: function (num) {

    },
    trigger: function (event, args) {

    },
};

function preview_item(def, args) {
    //PLANNED Improvements:
    //- Importing a custom thumbnail
    //- Drafting custom item abilities
    // Email me or create an issue if you need these features (if you want to suggest new items) [20/03/17]
    if (!args) args = {};
    var html = "";
    var styles = "vertical-align: top; margin: 10px";
    var name = def.id || args.id || "staff";
    parent.prop_cache = {}; // bust the item cache
    if (def.compound || def.upgrade) {
        for (var level = 0; level <= 10; level++)
            html += parent.render_item("html", {
                item: def,
                name: name,
                styles: styles,
                actual: {name: name, level: level},
                sell: true,
                thumbnail: args.thumbnail
            });
    }
    else {
        html += parent.render_item("html", {item: def, name: name, thumbnail: args.thumbnail});
    }
    html += "<div style='margin: 10px; border: 5px solid gray; padding: 4px'>" + parent.json_to_html(def) + "</div>";
    parent.show_modal(html);
    parent.prop_cache = {};
}
/**
 *
 * @param {string} name
 * @param {function} onerror -  A function that gets executed when the code errors
 */
function load_code(name, onerror) // onerror can be a function that will be executed if load_code fails
{
    if (!onerror) onerror = function () {
        game_log("load_code: Failed to load", "#E13758");
    }
    var xhrObj = new XMLHttpRequest();
    xhrObj.open('GET', "/code.js?name=" + encodeURIComponent(name) + "&timestamp=" + (new Date().getTime()), false);
    xhrObj.send('');
    var library = document.createElement("script");
    library.type = "text/javascript";
    library.text = xhrObj.responseText;
    library.onerror = onerror;
    document.getElementsByTagName("head")[0].appendChild(library);
}

var smart = {
    moving: false,
    map: "main", x: 0, y: 0,
    on_done: function () {
    },
    plot: null,
    edge: 20,
    use_town: false,
    prune: {
        smooth: true,
        map: true,
    },
    flags: {}
};
/**
 * smart_move uses a Breadth-first search path finding algorithm to find the shortest path
 * despite the name, smart_move isn't very smart or efficient, it's up to the players to
 * implement a better movement method.
 * @param {Object} destination
 * @param {string} destination.to - Can be of multiple values.
 *      If destination.to is a monster name we will find where they spawn and set the target destination.
 *      Monsters like the phoenix or mvampire have random spawn locations we want to check for the accordingly
 *      Every time we search for the phoenix we will try a different location so if nobody kill it we will eventually find it
 *      If destination to is one of "upgrade", "exchange", "potions", "scrolls" we will find the path to the closest shop that sells the items.
 * @param {string} destination.map -  Destination map
 * @param {number} destination.x - Destination coordinates
 * @param {number} destination.y - Destination coordinates
 * @param on_done
 */
function smart_move(destination, on_done)
{
    smart.map = "";
    if (is_string(destination)) destination = {to: destination};
    if ("x" in destination) {
        smart.map = destination.map || character.map;
        smart.x = destination.x;
        smart.y = destination.y;
    }
    else if ("to" in destination || "map" in destination) {
        if (destination.to == "town") destination.to = "main";

        //If destination.to is a monster name we will find where they spawn and set the target destination.
        if (G.monsters[destination.to]) {
            for (var name in G.maps)
                (G.maps[name].monsters || []).forEach(function (pack) {
                    if (pack.type != destination.to || G.maps[name].ignore) return;

                    //Monsters like the phoenix or mvampire have random spawn locations we want to check for the accordingly
                    if (pack.boundaries) // boundaries: for phoenix, mvampire
                    {
                        //Every time we search for the phoenix we will try a different location so if nobody kill it we will eventually find it
                        pack.last = pack.last || 0;
                        var boundary = pack.boundaries[pack.last % pack.boundaries.length];
                        pack.last++;
                        smart.map = boundary[0];
                        smart.x = (boundary[1] + boundary[3]) / 2;
                        smart.y = (boundary[2] + boundary[4]) / 2;
                    }
                    else if (pack.boundary) {
                        var boundary = pack.boundary;
                        smart.map = name;
                        smart.x = (boundary[0] + boundary[2]) / 2;
                        smart.y = (boundary[1] + boundary[3]) / 2;
                    }
                });
        }

        else if (G.maps[destination.to || destination.map]) {
            smart.map = destination.to || destination.map;
            smart.x = G.maps[smart.map].spawns[0][0];
            smart.y = G.maps[smart.map].spawns[0][1];
        }
        else if (destination.to == "upgrade" || destination.to == "compound") smart.map = "main", smart.x = -204, smart.y = -129;
        else if (destination.to == "exchange") smart.map = "main", smart.x = -26, smart.y = -432;
        else if (destination.to == "potions" && character.map == "halloween") smart.map = "halloween", smart.x = 149, smart.y = -182;
        else if (destination.to == "potions" && in_arr(character.map, ["winterland", "winter_inn", "winter_cave"])) smart.map = "winter_inn", smart.x = -84, smart.y = -173;
        else if (destination.to == "potions") smart.map = "main", smart.x = 56, smart.y = -122;
        else if (destination.to == "scrolls") smart.map = "main", smart.x = -465, smart.y = -71;
    }
    if (!smart.map) {
        game_log("Unrecognized", "#CF5B5B");
        return;
    }
    smart.moving = true;
    smart.plot = [];
    smart.flags = {};
    smart.searching = smart.found = false;
    //should we return to the starting point
    if (destination.return) {
        var cx = character.real_x, cy = character.real_y, cmap = character.map;
        smart.on_done = function () {
            if (on_done) on_done();
            smart_move({map: cmap, x: cx, y: cy});
        }
    }
    else smart.on_done = on_done || function () {
        };
    console.log(smart.map + " " + smart.x + " " + smart.y);
}
/**
 * Stop path finding and moving
 */
function stop() {
    if (smart.moving) smart.on_done(false);
    smart.moving = false;
    move(character.real_x, character.real_y);
}

var queue = [], visited = {}, start = 0, best = null;
var moves = [[0, 15], [0, -15], [15, 0], [-15, 0]];

function plot(index) {
    if (index == -1) return;
    plot(queue[index].i); // Recursively back-tracks the path we came from
    smart.plot.push(queue[index]);
}

function qpush(node) {
    // If we haven't visited this location, adds the location to the queue
    if (smart.prune.map && smart.flags.map && node.map != smart.map) return;
    if (visited[node.map + "-" + node.x + "-" + node.y]) return;
    if (!node.i) node.i = start; // set the index, to aid the plot function
    queue.push(node);
    visited[node.map + "-" + node.x + "-" + node.y] = true;
}
/**
 * Internal smart_move function
 */
function smooth_path() {
    var i = 0, j;
    while (i < smart.plot.length) {
        // Assume the path ahead is [i] [i+1] [i+2] - This routine checks whether [i+1] could be skipped
        // The resulting path is smooth rather than rectangular and bumpy
        // Try adding "function smooth_path(){}" or "smart.prune.smooth=false;" to your Code
        while (i + 2 < smart.plot.length && smart.plot[i].map == smart.plot[i + 1].map && smart.plot[i].map == smart.plot[i + 1].map &&
        can_move({
            map: smart.plot[i].map,
            x: smart.plot[i].x,
            y: smart.plot[i].y,
            going_x: smart.plot[i + 2].x,
            going_y: smart.plot[i + 2].y
        }))
            smart.plot.splice(i + 1, 1);
        i++;
    }
}
/**
 * Internal smart_move function
 */
function bfs() {
    var timer = new Date(), result = null, optimal = true;

    while (start < queue.length) {
        var current = queue[start];
        if (current.map == smart.map) { //Is same MAP
            smart.flags.map = true;
            if (abs(current.x - smart.x) + abs(current.y - smart.y) < smart.edge) {
                result = start;
                break;
            }
            else if (best === null || abs(current.x - smart.x) + abs(current.y - smart.y) < abs(queue[best].x - smart.x) + abs(queue[best].y - smart.y)) {
                best = start;
            }
        }
        else if (current.map != smart.map) {
            if (smart.prune.map && smart.flags.map) {
                start++;
                continue;
            }
            G.maps[current.map].doors.forEach(function (door) {
                if (simple_distance({x: door[0] + door[2] / 2, y: door[1] + door[3] / 2}, {
                        x: current.x,
                        y: current.y
                    }) < 45)
                    qpush({
                        map: door[4],
                        x: G.maps[door[4]].spawns[door[5] || 0][0],
                        y: G.maps[door[4]].spawns[door[5] || 0][1],
                        transport: true,
                        s: door[5] || 0
                    });
            });
            G.maps[current.map].npcs.forEach(function (npc) {
                if (npc.id == "transporter" && simple_distance({x: npc.position[0], y: npc.position[1]}, {
                        x: current.x,
                        y: current.y
                    }) < 75) {
                    for (var place in G.npcs.transporter.places) {
                        qpush({
                            map: place,
                            x: G.maps[place].spawns[G.npcs.transporter.places[place]][0],
                            y: G.maps[place].spawns[G.npcs.transporter.places[place]][1],
                            transport: true,
                            s: G.npcs.transporter.places[place]
                        });
                    }
                }
            });
        }

        if (smart.use_town) qpush({
            map: current.map,
            x: G.maps[current.map].spawns[0][0],
            y: G.maps[current.map].spawns[0][1],
            town: true
        }); // "town"

        shuffle(moves);
        moves.forEach(function (m) {
            var new_x = parseInt(current.x + m[0]), new_y = parseInt(current.y + m[1]);
            // utilise can_move - game itself uses can_move too - smart_move is slow as can_move checks all the lines at each step
            if (can_move({map: current.map, x: current.x, y: current.y, going_x: new_x, going_y: new_y}))
                console.log(new_x, new_y);
            qpush({map: current.map, x: new_x, y: new_y});
        });

        start++;
        if (mssince(timer) > (!parent.is_hidden() && 40 || 500)) return;
    }

    if (result === null) result = best, optimal = false;
    if (result === null) {
        game_log("Path not found!", "#CF575F");
        smart.moving = false;
        smart.on_done(false);
    }
    else {
        plot(result);
        smart.found = true;
        if (smart.prune.smooth) smooth_path();
        if (optimal) game_log("Path found!", "#C882D1");
        else game_log("Path found~", "#C882D1");
        // game_log(queue.length);
        parent.d_text("Yes!", character, {color: "#58D685"});
    }
}
/**
 * Internal smart_move function
 */
function start_pathfinding() {
    smart.searching = true;
    queue = [], visited = {}, start = 0, best = null;
    qpush({x: character.real_x, y: character.real_y, map: character.map, i: -1});
    game_log("Searching for a path...", "#89D4A2");
    bfs();
}
/**
 * Internal smart_move function
 */
function continue_pathfinding() {
    bfs();
}
/**
 * Internal smart_move function
 */
function smart_move_logic() {
    if (!smart.moving) return;
    if (!smart.searching && !smart.found) {
        start_pathfinding();
    }
    else if (!smart.found) {
        continue_pathfinding();
    }
    else if (!character.moving && can_walk(character) && !is_transporting(character)) {
        if (!smart.plot.length) {
            smart.moving = false;
            smart.on_done(true);
            return;
        }
        var current = smart.plot[0];
        smart.plot.splice(0, 1);
        // game_log(JSON.stringify(current));
        if (current.town) {
            use("town");
        }
        else if (current.transport) {
            parent.socket.emit("transport", {to: current.map, s: current.s});
            // use("transporter",current.map);
        }
        else if (character.map == current.map && can_move_to(current.x, current.y)) {
            move(current.x, current.y);
        }
        else {
            game_log("Lost the path...", "#CF5B5B");
            smart_move({map: smart.map, x: smart.x, y: smart.y}, smart.on_done);
        }
    }
}

setInterval(function () {
    smart_move_logic();
}, 80);

//safety flags
var last_loot = new Date(0);
var last_attack = new Date(0);
var last_potion = new Date(0);
var last_transport = new Date(0);