// #NOTE: If you want to see a new function/feature, just request it at: https://github.com/kaansoral/adventureland/issues // Or at #feedback in Discord: https://discord.gg/4SXJGU var character={ // This object proxies the real parent.character // Normal entities have normal coordinates, their {x,y}'s are equal to their {real_x,real_y}'s // The character object is special, it's always in the middle of the screen, so it has static {x,y}'s // Added this wrapper so instead of using .real_x and .real_y on all entities, .x and .y's can be used uniformly "note":"This is a proxy object, the real character is in parent.character", "properties":["x","y"], "read_only":["x","y","real_x","real_y","from_x","from_y","going_x","going_y","moving","target","vx","vy","move_num","attack","speed","hp","mp","xp","max_hp","max_mp","range","level","rip","s","c","in","map","stand","items","slots"], "proxy_character":true, } Object.defineProperty(character,'x',{get:function(){return parent.character.real_x;},set:function(){game_log("You can't set coordinates manually, use the move(x,y) function!");},enumerable:true}); Object.defineProperty(character,'y',{get:function(){return parent.character.real_y;},set:function(){game_log("You can't set coordinates manually, use the move(x,y) function!");},enumerable:true}); for(var p in parent.character) proxy(p); // Not all properties are sadly available right away, new properties are captured imperfectly // var character=parent.character; // Old [25/06/2018] var G=parent.G; // Game Data - Use show_json(Object.keys(G)); and inspect individual data with show_json(G.skills) and alike var safeties=true; // Prevents common delay based issues that cause many requests to be sent to the server in a burst that triggers the server to disconnect the character server={ mode:parent.gameplay, // "normal", "hardcore", "test" pvp:parent.is_pvp, // true for PVP servers, use is_pvp() for maps region:parent.server_region, // "EU", "US", "ASIA" id:parent.server_identifier, // "I", "II", "PVP", "TEST" } game={ platform:parent.is_electron&&"electron"||"web", // "electron" for Steam, Mac clients, "web" for https://adventure.land graphics:!parent.no_graphics, // if game.graphics is false, don't draw stuff to the game in your Code html:!parent.no_html, // if game.html is false, this character is loaded in [CODE] mode cli:parent.is_cli, }; character.bot=parent.is_bot; //#NOTE: Most new features are experimental - for #feedback + suggestions: https://discord.gg/X4MpntA [05/01/18] // #MODES function mode_resolve_all() { // Never reject a Promise // Successful results have {success:true} // Failes results have {failed:true} parent.parent.RESOLVE_ALL=parent.RESOLVE_ALL=RESOLVE_ALL=true; } /** * Loads a character in [CODE] mode * Character is loaded inside an iframe * The Code of the character is inside another iframe within that iframe * @param {string} name - Name of your character * @param {string} code_slot_or_name - Code slot name * @returns {Promise} */ function start_character(name,code_slot_or_name) { return parent.start_character_runner(name,code_slot_or_name) } function stop_character(name) { parent.stop_character_runner(name) } function command_character(name,code_snippet) { // Commands the character in [CODE] mode parent.character_code_eval(name,code_snippet) } function get_active_characters() { // States: "self", "starting","loading", "active", "code" // Example: {"Me":"self","Protector":"loading"} return parent.get_active_characters() } function change_server(region,name) // change_server("EU","I") or change_server("ASIA","PVP") or change_server("US","III") { parent.window.location.href="/character/"+character.name+"/in/"+region+"/"+name+"/"; } function is_pvp() { return G.maps[character.map].pvp || server.pvp; } function is_npc(entity) { if(entity && (entity.npc || entity.type=="npc")) return true; } function is_monster(entity) { if(entity && entity.type=="monster") return true; } function is_character(entity) { if(entity && entity.type=="character" && !entity.npc) return true; } function is_player(e){return is_character(e);} // backwards-compatibility function interact(name) { if(name=="monsterhunt") { parent.socket.emit("monsterhunt"); return parent.push_deferred("monsterhunt"); // {started:true} / {completed:true} / {failed:true} } } function enter(place) { // Possible places: "duelland" / "crypt" / "winter_instance" parent.socket.emit('enter',{place:place}); return parent.push_deferred("enter"); } function join(event) { // show_json(G.events); return parent.join(event); } function use_nearest_door() { for(var i=0;i<(G.maps[character.map].doors||[]).length;i++) { var door=G.maps[character.map].doors[i]; if(simple_distance(character,{map:character.map,x:door[0],y:door[1]})<100) // Server range is currently 112 return transport(door[4],door[5]); // map and spawn } return rejecting_promise({reason:"distance"}); } function activate(num) // activates an item, likely a booster, in the num-th inventory slot { parent.activate(num); } function shift(num,name) // shifts an item, likely a booster, in the num-th inventory slot { // shift(0,'xpbooster') // shift(0,'luckbooster') // shift(0,'goldbooster') parent.shift(num,name); } function use_skill(name,target,extra_arg) { // target: object or string (character name or monster ID) // for "blink": use_skill("blink",[x,y]) // for "3shot", "5shot" target can be an array of objects or strings (name or ID) // example: use_skill("3shot",[target1,target2,target3]) // extra_arg is currently for use_skill("throw",target,inventory_num) and use_skill("energize",target,optional_mp) if(!target) target=get_target(); return parent.use_skill(name,target,extra_arg); // Returns a Promise // For "3shot", "5shot", "cburst" returns an array of Promise's - one for each target } function reduce_cooldown(name,ms) { // parent.next_skill contains Date objects of when the skills will be available next // show_json(parent.next_skill) to get a better idea // reduce_cooldown("attack",100) would cause the attack cooldown to reduce by 100 ms // If your ping is 100+ms - this would ~correct/improve/adjust the cooldown // attack(target).then(function(data){ reduce_cooldown("attack",character.ping*0.95); }); // Above call is likely the ideal usage if(parent.next_skill[name]) parent.skill_timeout(name,-mssince(parent.next_skill[name])-ms); } function bank_deposit(gold) { if(!character.bank) return game_log("Not inside the bank"); parent.socket.emit("bank",{operation:"deposit",amount:gold}); } function bank_withdraw(gold) { if(!character.bank) return game_log("Not inside the bank"); parent.socket.emit("bank",{operation:"withdraw",amount:gold}); } function bank_store(num,pack,pack_num) { // bank_store(0) - Stores the first item in inventory in the first/best spot in bank // bank_store(41,"items0",41) -> stores the last item on the last spot of bank's "items0" // pack is one of "items0","items1","items2",... if(!character.bank) return game_log("Not inside the bank"); if(!character.items[num]) return game_log("No item in that spot"); if(pack_num===undefined) pack_num=-1; // the server interprets -1 as first slot available if(!pack) { var cp=undefined,cs=undefined; for(var cpack in bank_packs) { if(pack || bank_packs[cpack][0]!=character.map || !character.bank[cpack]) continue; for(var i=0;i<42;i++) { if(can_stack(character.bank[cpack][i],character.items[num])) // the item we want to store and this bank item can stack - best case scenario pack=cpack; if(!character.bank[cpack][i] && !cp) cp=cpack; } } if(!pack && !cp) return game_log("Bank is full!"); if(!pack) pack=cp; } parent.socket.emit("bank",{operation:"swap",pack:pack,str:pack_num,inv:num}); } function bank_retrieve(pack,pack_num,num) { // bank_retrieve("items0",0) -> retrieves the first item from bank's "items0" // bank_retrieve("items0",0,12) -> you can optionally specify where to retrieve the item in inventory if(!character.bank) return game_log("Not inside the bank"); if(!character.bank[pack] || !character.bank[pack][pack_num]) return game_log("No item in that spot"); if(num===undefined) num=-1; // the server interprets -1 as first slot available parent.socket.emit("bank",{operation:"swap",pack:pack,str:pack_num,inv:num}); } function swap(a,b) // inventory move/swap { parent.socket.emit("imove",{a:a,b:b}); return parent.push_deferred("imove"); } function locate_item(name) { for(var i=0;i"+text+""; if(!game.html) parent.set_status(text); else { current_message=text; // $('#gg').html(text); // added the code_draw function for performance [15/01/18] // also visit set_status on functions.js for a challenge (note to self) // Last note for now: There's probably a browser/chrome bug // If you move the cursor into the iframe once - each set_message breaks the cursor // Whether code_draw is used or not // That's why iframe's have "pointer-events: none;"s now } } function game_log(message,color,x) { if(game.platform=="electron" && !x) return safe_log(message,color); if(!color) color="#51D2E1"; if(character.bot) parent.parent.add_log(character.name+": "+message,color); else parent.add_log(message,color); } function log(message,color) { if(is_object(message)) message=JSON.stringify(message); game_log(message,color); } function safe_log(message,color) { // If the logged message/object is from an untrusted source, this function must be used // For example if you: character.on("cm",function(data){log(data)}); // Someone can: send_cm("You",""); if(is_object(message)) message=JSON.stringify(message); game_log(html_escape(message),color,true); } function get_focus() { // focus is a secondary target that appears on top of the actual target // it was added to let you view other characters around and still target separately if(parent.xtarget && parent.xtarget.visible) return parent.xtarget; return 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; } function get_target() { if(parent.ctarget && parent.ctarget.visible) return parent.ctarget; if(parent.xtarget && parent.xtarget.visible) return parent.xtarget; return null; } function get_targeted_monster() { if(parent.ctarget && !parent.ctarget.dead && parent.ctarget.type=='monster') return parent.ctarget; return null; } function change_target(target) { parent.ctarget=target; parent.send_target_logic(); } function change_target_privately(target) { parent.ctarget=target; if(target) parent.last_id_sent=target.id; // Marks the id as sent, so it doesn't actually get sent else parent.last_id_sent=''; parent.send_target_logic(); } 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,base:character.base}); } function xmove(x,y) { if(can_move_to(x,y)) return move(x,y); else return smart_move({x:x,y:y}); } function is_in_range(target,skill) { // Valid usages: is_in_range(target), is_in_range(target,"attack"), is_in_range(target,"heal"), is_in_range(target,"mentalburst") if(!target || !target.visible) return false; // When a target leaves your viewpoint, .visible becomes false and the object reference is never updated again var range_multiplier=1,range_bonus=0; if(G.skills[skill] && G.skills[skill].range) { if(distance(character,target)<=G.skills[skill].range) return true; return false; } if(G.skills[skill] && G.skills[skill].range_multiplier) range_multiplier=G.skills[skill].range_multiplier; if(G.skills[skill] && G.skills[skill].range_bonus) range_bonus=G.skills[skill].range_bonus; if(distance(character,target)<=character.range*range_multiplier+range_bonus) return true; return false; } function is_on_cooldown(skill) { if(G.skills[skill] && G.skills[skill].share) return is_on_cooldown(G.skills[skill].share); if(parent.next_skill[skill] && new Date()=parent.next_skill.attack) return true; return false; } function can_heal(t) { if(is_monster(t)) return false; // ?? :D [11/10/18] return can_attack(t); } function is_moving(entity) { if(entity.me && smart.moving) return true; if(entity.moving) return true; return false; } function is_transporting(entity) { if(entity.c.town) return true; if(entity.me && parent.transporting) return true; return false; } function attack(target) { if(target==character) target=parent.character; if(!target) { game_log("Nothing to attack()","gray"); return rejecting_promise({reason:"not_found"}); } if(target.type=="character") return parent.player_attack.call(target,null,true); else return parent.monster_attack.call(target,null,true); } function heal(target) { if(target==character) target=parent.character; // Don't send the proxy object to parent [10/06/19] if(!target) { game_log("No one to heal()","gray"); return rejecting_promise({reason:"not_found"}); } return parent.player_heal.call(target,null,true); } function buy(name,quantity) //item names can be spotted from show_json(character.items) - they can be bought only if an NPC sells them { return parent.buy(name,quantity); // returns a Promise } function buy_with_gold(name,quantity) { return parent.buy_with_gold(name,quantity); // returns a Promise } function buy_with_shells(name,quantity) { parent.buy_with_shells(name,quantity); } function sell(num,quantity) //sell an item from character.items by it's order - 0 to N-1 { return parent.sell(num,quantity); } function consume(num) // consumes or uses an inventory item { parent.socket.emit("equip",{num:num,consume:true}); return parent.push_deferred("equip"); } function equip(num,slot) // slot is optional { if(num<0) { game_log("Can't equip "+num); return rejecting_promise({reason:"invalid"}); } else { parent.socket.emit("equip",{num:num,slot:slot}); return parent.push_deferred("equip"); } } function unequip(slot) // show_json(character.slots) => to see slot options { parent.socket.emit("unequip",{slot:slot}); return parent.push_deferred("unequip"); } function lock_item(num) { // Lock an item to prevent loss return parent.lock_item(num); } function seal_item(num) { // Seal an item so it can't be unlocked for 2 days return parent.seal_item(num) } function unlock_item(num) { // Unlock an item, returns {hours:47,success:false,in_progress:true} if it was sealed return parent.unlock_item(num); } function trade(num,trade_slot,price,quantity) // 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] { if(!is_string(trade_slot) || !trade_slot.startsWith("trade")) trade_slot="trade"+trade_slot; parent.trade(trade_slot,num,price,quantity||1); } function trade_buy(target,trade_slot,quantity) { // buys the item from a target by slot name // target needs to be an actual player object // quantity is optional parent.trade_buy(trade_slot,target.id,target.slots[trade_slot].rid,quantity||1); // the .rid changes when the item in the slot changes, it prevents swap-based frauds [22/11/16] } function trade_sell(target,trade_slot,quantity) { // sells an item to a target's buy listing by slot name // server automatically checks/picks the item from your inventory to sell // target needs to be an actual player object // quantity is optional parent.trade_sell(trade_slot,target.id,target.slots[trade_slot].rid,quantity||1); // the .rid changes when the item in the slot changes, it prevents swap-based frauds [22/11/16] } function upgrade(item_num,scroll_num,offering_num,only_calculate) // number of the item and scroll on the show_json(character.items) array - 0 to N-1 { return parent.upgrade(item_num,scroll_num,offering_num,"code",only_calculate); // returns a Promise } function compound(item0,item1,item2,scroll_num,offering_num,only_calculate) // for example -> compound(0,1,2,6) -> 3 items in the first 3 slots, scroll at the 6th spot { return parent.compound(item0,item1,item2,scroll_num,offering_num,"code",only_calculate); // returns a Promise } function craft(i0,i1,i2,i3,i4,i5,i6,i7,i8) // for example -> craft(null,0,null,null,1,null,null,2,null) // sends 3 items to be crafted, the 0th, 1st, 2nd items in your inventory, and it places them all in the middle column of crafting { parent.cr_items=[i0,i1,i2,i3,i4,i5,i6,i7,i8]; return parent.craft(); } function auto_craft(name) { // Picks the inventory positions automatically. Example: auto_craft("computer") return parent.auto_craft(name,true); } function dismantle(item_num) { parent.ds_item=item_num; return parent.dismantle(); } async function exchange(item_num) { parent.e_item=item_num; var call=await parent.exchange(1),num=undefined,name=undefined; if(!call.in_progress) return call; if(character.q.exchange) num=character.q.exchange.num; while(character.q.exchange) await sleep(1); if(character.items[num]) name=character.items[num].name; return {success:true,reward:name} } function exchange_buy(token,name) { // Example: exchange_buy('funtoken','confetti') return parent.exchange_buy(token,name); } function say(message) // please use MORE responsibly, thank you! :) { return parent.say(message,safeties); } function party_say(message) { return parent.party_say(message,safeties); } function pm(name,message) { return parent.private_say(name,message,safeties); } function move(x,y) { if(!can_walk(character)) return rejecting_promise({reason:"unable"}); return parent.move(x,y,true); } function cruise(speed) { // to revert, just cruise(500) - since it just sets an upper cap for speed parent.socket.emit("cruise",speed); return parent.push_deferred("cruise"); } function equip_cx(slot,cx_name) { // Equipped: show_json(character.cx) // Available: show_json(character.acx) parent.socket.emit("cx",{slot:slot,name:cx_name}); return parent.push_deferred("cx"); } function show_json(e) // renders the object as json inside the game { if(character.bot) parent.parent.show_json(parent.game_stringify(e,'\t')); else parent.show_json(parent.game_stringify(e,'\t')); } function get_servers() { // returns an array of server data // best to inspect the format with show_json(get_servers()) return parent.X.servers; } function get_characters() { // returns an infrequently updated array of your characters // best to inspect the format with show_json(get_characters()) return parent.X.characters; } function get_party() { // returns an infrequently updated object // best to inspect the format with show_json(get_party()) return parent.party; } function get_monster(id) { // returns the monster by id, an integer, if the monster is within the vision area var target=parent.entities[id]; if(target && target.type!="monster") target=null; return target; } 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; } function get_entity(id) { // entities are currently players, monsters and citizen npcs, this function returns players and monsters var target=parent.entities[id]; if(id==character.name) target=character; return target; } function find_npc(npc_id) { // returns smart_move'able coordinates for an NPC key from G.npcs for(var name in parent.G.maps) { var map=parent.G.maps[name]; if(map.ignore || !map.npcs) continue; for(var i=0;iargs.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_distcharacter.esize || chest.last_loot && mssince(chest.last_loot)<1600)) continue; chest.last_loot=last_loot; if(id_or_arg===true) last=parent.parent.open_chest(id); else last=parent.open_chest(id); looted++; if(looted==2) break; } if(!last) return resolving_promise({reason:"nothing_to_loot"}); return last; } function get_chests() { // parent.chests is an object, each key is a chest ID // you can: for(var id in get_chests()) loot(id); return parent.chests; } function open_stand(num) { // Opens the merchant stand // Inventory# is optional if(num===undefined) { for(var i=0;i<42;i++) if(character.items[i] && G.items[character.items[i].name].stand) num=i; } parent.open_merchant(num); } function close_stand() { parent.close_merchant(); } function send_gold(receiver,gold) { if(!receiver) { game_log("No receiver sent to send_gold"); return rejecting_promise({reason:"no_target"}); } if(receiver.name) receiver=receiver.name; parent.socket.emit("send",{name:receiver,gold:gold}); return parent.push_deferred("send"); } function send_item(receiver,num,quantity) { if(!receiver) { game_log("No receiver sent to send_item"); return rejecting_promise({reason:"no_target"}); } if(receiver.name) receiver=receiver.name; parent.socket.emit("send",{name:receiver,num:num,q:quantity||1}); return parent.push_deferred("send"); } function send_cx(receiver,cx) { // Sends cosmetics to one of your own characters if(!receiver) { game_log("No receiver sent to send_cx"); return rejecting_promise({reason:"no_target"}); } if(receiver.name) receiver=receiver.name; parent.socket.emit("send",{name:receiver,cx:cx}); return parent.push_deferred("send"); } function send_mail(to,subject,message,item) { item=item&&true||false; // 0th slot is sent parent.socket.emit('mail',{to:to,subject:subject,message:message,item:item}); return parent.push_deferred("mail"); } function destroy(num) // num: 0 to 41 { return parent.poof(num); } 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}); } function send_party_request(name) { send_party_invite(name,1); } function accept_party_invite(name) { parent.remove_chat("pin"+name); parent.socket.emit('party',{event:'accept',name:name}); } function accept_party_request(name) { parent.remove_chat("rq"+name); parent.socket.emit('party',{event:'raccept',name:name}); } function leave_party() { parent.socket.emit("party",{event:"leave"}); } function kick_party_member(name) { parent.socket.emit('party',{event:'kick',name:name}); } function accept_magiport(name) { parent.remove_chat("mp"+name); parent.socket.emit('magiport',{name:name}); } function unfriend(name) // instead of a name, an owner id also works, this is currently the only way to unfriend someone [20/08/18] { parent.socket.emit('friend',{event:'unfriend',name:name}); } function respawn() { parent.socket.emit('respawn'); return parent.push_deferred("respawn"); } 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; } async function send_cm(to,message) { // to: Name or Array of Name's // message: JSON object // Receive with: character.on("cm",function(m){ m.name; m.message; }); var to_server=[],locals=[],data={locals:[],receivers:[]}; if(!is_array(to)) to=[to]; to.forEach(function(name){ if(is_character_local(name)) send_local_cm(name,message),locals.push(name); else to_server.push(name); }) if(to_server.length) data=await send_server_cm(to_server,message); // message over the server - has a high call cost / character.cc locals.forEach(function(name){ data.locals.push(name); data.receivers.push(name); }) return data; } function send_server_cm(to,message) { // to: Name or Array of Name's // message: JSON object // returns a Promise with {receivers:[]} return parent.send_code_message(to,message); } function on_disappear(entity,data) { // game_log("disappear: "+entity.id+" "+JSON.stringify(data)); } function on_party_invite(name) // called by the inviter's name { // accept_party_invite(name) } function on_party_request(name) // called by the inviter's name - request = someone requesting to join your existing party { // accept_party_request(name) } function on_magiport(name) // called by the mage's name in PVE servers, in PVP servers magiport either succeeds or fails without consent { // accept_magiport(name) } function on_map_click(x,y) { // if true is returned, the default move is cancelled // xmove(x,y); // return true; } function on_destroy() // called just before the CODE is destroyed { clear_drawings(); clear_buttons(); } function on_draw() // 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 { } var PIXI=parent.PIXI; // for drawing stuff into the game var drawings=parent.drawings; var buttons=parent.code_buttons; //Documentation: 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(!game.graphics) return; 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; } // Example: draw_circle(character.real_x,character.real_y,character.range) :) [22/10/16] function draw_circle(x,y,radius,size,color) { if(!game.graphics) return; 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; } function clear_drawings() { drawings.forEach(function(e){ try{e.destroy({children:true})}catch(ex){} }); drawings=parent.drawings=[]; } function add_top_button(id,value,fn) { if(!buttons[id]) { buttons[id]={value:value,fn:function(){},place:"top"}; parent.$(".codebuttons").append("
BUTTON
") } if(fn) set_button_onclick(id,fn) if(value) set_button_value(id,value); } function add_bottom_button(id,value,fn) { if(!buttons[id]) { buttons[id]={value:value,fn:function(){},place:"bottom"}; parent.$(".codebbuttons").append("
BUTTON
") } if(fn) set_button_onclick(id,fn) if(value) set_button_value(id,value); } function set_button_value(id,value) { parent.$(".codebutton"+id).html(value); } function set_button_color(id,color) { parent.$(".codebutton"+id).css("border-color",color); } function set_button_onclick(id,fn) { buttons[id].fn=fn; } function clear_buttons() { parent.$('.codebuttons').html(""); parent.$('.codebbuttons').html(""); buttons=parent.code_buttons={}; } character.listeners=[]; character.all=function(f){ var def={f:f,id:randomStr(30),event:"all"}; character.listeners.push(def); return def.id; }; character.one=function(event,f){ // gets overwritten if another handler comes along var def={f:f,id:randomStr(30),event:event,one:true}; character.listeners.push(def); return def.id; }; character.on=function(event,f){ var def={f:f,id:randomStr(30),event:event},handled=false; for(var i=0;i"; parent.show_modal(html); parent.prop_cache={}; } function set_skillbar() // example: set_skillbar("1","2","3","4","R") or set_skillbar(["1","2","3","4","R"]) { var arr=["1"]; if(is_array(arguments[0])) arr=arguments[0]; else { arr=[]; for(var i=0;i320) { activity.heartbeat[character.name]=(new Date()).toString(); localStorage.setItem("activity"+(game.cli&&character.name||""),JSON.stringify(activity)); } var keys=Object.keys(localStorage); if(game.cli) keys=localStorage._keys; keys.forEach(function(key){ // 2x faster than for(key in localStorage) { if(key.startsWith("cm_"+character.name+"_")) { var data=localStorage.getItem(key); localStorage.removeItem(key); data=JSON.parse(data); data[2]=new Date(data[2]); messages.push(data); } } }); messages.sort(function(a,b){ if(!(a[2]-b[2])) return a[3]-b[3]; else return a[2]-b[2]; }); messages.forEach(function(cm){ try{ character.trigger("cm",{name:cm[0],message:cm[1],date:cm[2],local:true}); }catch(e){ game_log("CM Error, From: "+cm[0]); log(e); log(e.stack); } }); setTimeout(local_cm_logic,min(120,max(16,mssince(start)*10))); } setTimeout(local_cm_logic,10); var local_m_num=0; function send_local_cm(name,data) { if(game.cli) parent.CLI_OUT.push({"type":"cm","to":name,"data":[character.name,data,new Date(),++local_m_num]}); else localStorage.setItem("cm_"+name+"_"+randomStr(20),JSON.stringify([character.name,data,new Date(),++local_m_num])); } function is_character_local(name) { var activity=localStorage.getItem("activity"+(game.cli&&name||"")); activity=activity&&JSON.parse(activity)||{}; if(activity.heartbeat && activity.heartbeat[name] && mssince(new Date(activity.heartbeat[name]))<2880) return true; return false; } function pset(name,value) { // persistent set function for string values // on Web, window.localStorage is used, on Steam/Mac, the electron-store package is used for persistent storage return parent.storage_set(name,value); } function pget(name) { // persistent get function for string values // on Web, window.localStorage is used, on Steam/Mac, the electron-store package is used for persistent storage return parent.storage_get(name); } function set(name,value) { // persistent set function that works for serializable objects try{ window.localStorage.setItem("cstore_"+name,JSON.stringify(value)); return true; }catch(e){ game_log("set() call failed for: "+name+" reason: "+e,colors.code_error); return false; } } function get(name) { // persistent get function that works for serializable objects try{ return JSON.parse(window.localStorage.getItem("cstore_"+name)); }catch(e){ return null; } } function load_code(name_or_slot,onerror) { // load_code executes the code at top-level in a synchronized manner // each function/variable becomes directly available // onerror can be a function that will be executed if load_code fails var code=parent.get_code_file(name_or_slot); // works on Electron, returns the local file if(code===null) { var xhrObj = new XMLHttpRequest(); xhrObj.open('GET',"/code.js?name="+encodeURIComponent(name_or_slot)+"×tamp="+(new Date().getTime()), false); xhrObj.send(''); code=xhrObj.responseText; } var library=document.createElement("script"); library.type="text/javascript"; library.text=code; library.onerror=onerror||function(){ game_log("load_code: Failed to load",colors.code_error); }; document.getElementsByTagName("head")[0].appendChild(library); } function require_code(name_or_slot) { // require_code executes code inside it's own scope in a synchronized manner // functions returns the exports dictionary, emulating a require var code=parent.get_code_file(name_or_slot); if(code===null) { var xhrObj = new XMLHttpRequest(); xhrObj.open('GET',"/code.js?name="+encodeURIComponent(name_or_slot)+"&xrequire=1×tamp="+(new Date().getTime()), false); xhrObj.send(''); code=xhrObj.responseText; } var module={exports:{}}; var exports=module.exports; eval(code); return module.exports; } function upload_code(slot_number,slot_name,code_string) { // Some players use the code slots to persist data across platforms, but please use it sparingly :) [04/08/20] return parent.api_call("save_code",{code:code_string,slot:slot_number,name:slot_name,auto:true,electron:true},{promise:true}); } var active_code_slot=parent.code_slot||""; function get_active_code_slot() { return active_code_slot; } function get_edited_code_slot() { // Current slot on the CODE Editor return parent.code_slot; } function disconnect() { // Forces a limitdc for(var i=0;i<300;i++) parent.socket.emit("cruise"); } var smart={ moving:false, map:"main",x:0,y:0, on_done:function(){}, plot:null, edge:20, // getting 20px close to the target is enough baby_edge:80, // start treading lightly when 60px close to the target or starting point try_exact_spot: false, use_town:false, prune:{ smooth:true, map:true, }, flags:{} }; function smart_move(destination,on_done) { // despite the name, smart_move isn't very smart or efficient, it's up to the players to implement a better movement method [05/02/17] // on_done function is an old callback function for compatibility, smart_move also returns a Promise [25/03/20] if(smart.moving) smart.on_done(false,"interrupted"); smart.map=""; if(is_string(destination)) destination={to:destination}; if(is_number(destination)) destination={x:destination,y:on_done},on_done=null; 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(G.events[destination.to] && parent.S[destination.to] && G.events[destination.to].join) { join(destination.to); smart.moving=false; smart.on_done(true); return; } else if(G.monsters[destination.to]) { var locations=[],theone; for(var name in G.maps) (G.maps[name].monsters||[]).forEach(function(pack){ if(pack.type!=destination.to || G.maps[name].ignore || G.maps[name].instance) return; if(pack.boundaries) // boundaries: for phoenix, mvampire { pack.last=pack.last||0; var boundary=pack.boundaries[pack.last%pack.boundaries.length]; pack.last++; locations.push([boundary[0],(boundary[1]+boundary[3])/2,(boundary[2]+boundary[4])/2]); } else if(pack.boundary) { var boundary=pack.boundary; locations.push([name,(boundary[0]+boundary[2])/2,(boundary[1]+boundary[3])/2]); } }); if(locations.length) // This way, when you smart_move("snake") repeatedly - you can keep visiting different maps with snakes { theone=random_one(locations); smart.map=theone[0]; smart.x=theone[1]; smart.y=theone[2]; } } else if(G.maps[destination.to||destination.map]) { if(G.maps[destination.to||destination.map].event) { if(parent.S[G.maps[destination.to||destination.map].event]) { join(G.maps[destination.to||destination.map].event); smart.moving=false; smart.on_done(true); return; } else { game_log("Path not found!","#CF575F"); smart.moving=false; smart.on_done(false,"failed"); return; } } else { 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; else if(find_npc(destination.to)) { var l=find_npc(destination.to); smart.map=l.map,smart.x=l.x,smart.y=l.y+15; } } if(!smart.map) { game_log("Unrecognized location","#CF5B5B"); return rejecting_promise({reason:"invalid"}); } smart.moving=true; smart.plot=[]; smart.flags={}; smart.searching=smart.found=false; if(destination.return) { var cx=character.real_x,cy=character.real_y,cmap=character.map; smart.on_done=function(done,reason){ if(on_done) on_done(done); smart_move({map:cmap,x:cx,y:cy}); if(done) resolve_deferreds("smart_move",{success:true}); else reject_deferreds("smart_move",{reason:reason}); } } else smart.on_done=function(done,reason){ if(on_done) on_done(done); if(done) resolve_deferreds("smart_move",{success:true}); else reject_deferreds("smart_move",{reason:reason}); }; console.log("smart_move: "+smart.map+" "+smart.x+" "+smart.y); return push_deferred("smart_move"); } function stop(action,second) { if(!action || action=="move" || action=="smart") { if(smart.moving) smart.on_done(second||false,"interrupted"); smart.moving=false; if(action!="smart") return move(character.real_x,character.real_y); return resolving_promise({success:true}); } else if(action=="invis") { parent.socket.emit("stop",{action:"invis"}); } else if(action=="teleport" || action=="town") { parent.socket.emit("stop",{action:"town"}); } else if(action=="revival") { parent.socket.emit("stop",{action:"revival"}); } return push_deferred("stop"); } var queue=[],visited={},start=0,best=null; var moves=[[0,15],[0,-15],[15,0],[-15,0]]; var baby_steps=[[0,5],[0,-5],[5,0],[-5,0]]; // baby_steps is a new logic, used just around the target or starting point, to get out of tough spots [08/03/19] 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; } function smooth_path() { var i=0,j; while(i(!parent.is_hidden()&&40||500)) return; } if(result===null) { result=best,optimal=false; game_log("Path not found!","#CF575F"); smart.moving=false; smart.on_done(false,"failed"); } else { plot(result); if(1) // [08/03/19] - to attempt and move to the actual coordinates { var last=smart.plot[smart.plot.length-1]; if(!last) last={map:character.map,x:character.real_x,y:character.real_y}; if(smart.x!=last.x || smart.y!=last.y) { smart.try_exact_spot=true; smart.plot.push({map:last.map,x:smart.x,y:smart.y}); } } 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"}); } } function start_pathfinding() { smart.try_exact_spot=false; smart.searching=true; smart.start_x=character.real_x; smart.start_y=character.real_y; queue=[],visited={},start=0,best=null; if(game.cli) { parent.CLI_OUT.push({"type":"smart_move",G:G,start_x:smart.start_x,start_y:smart.start_y,start_map:character.map,x:smart.x,y:smart.y,map:smart.map}); } else { qpush({x:character.real_x,y:character.real_y,map:character.map,i:-1}); game_log("Searching for a path...","#89D4A2"); bfs(); } } function cli_smart_move_result(data) { if(data.found) { smart.found=true; smart.plot=data.plot; } else { game_log("CLI: Path not found!","#CF575F"); smart.moving=false; smart.on_done(false,"failed"); } } function continue_pathfinding() { bfs(); } function smart_move_logic() { if(!smart.moving) return; if(!smart.searching && !smart.found) { start_pathfinding(); } else if(!smart.found && game.cli) { /* Just wait */ } else if(!smart.found) { if(Math.random()<0.1) { move(character.real_x+Math.random()*0.0002-0.0001,character.real_y+Math.random()*0.0002-0.0001); parent.d_text(shuffle(["Hmm","...","???","Definitely left","No right!","Is it?","I can do this!","I think ...","What If","Should be","I'm Sure","Nope","Wait a min!","Oh my"])[0],character,{color:shuffle(["#68B3D1","#D06F99","#6ED5A3","#D2CF5A"])[0]}); } 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}); parent.push_deferred("transport") // use("transporter",current.map); } else if(character.map==current.map && (smart.try_exact_spot && !smart.plot.length || can_move_to(current.x,current.y))) { // game_log("S "+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); function proxy(name) { if(in_arr(name,character.properties)) return; character.properties.push(name); Object.defineProperty(character,name,{ get:function(){return parent.character[name];}, set:function(value){ delete this[name]; if(character.read_only.includes(name)) { game_log("You attempted to change the character."+name+" value manually. You have to use the provided functions to control your character!",colors.code_error); } else { parent.character[name]=value; } }, enumerable:true, }); } character.read_only.push(...["on","once"]); ["bank","user","code","angle","direction","target","from_x","from_y","going_x","going_y","moving","vx","vy","move_num"].forEach(function(p){proxy(p)}); setInterval(function(){ for(var p in parent.character) proxy(p); },50); // bottom of the barrel function eval_s(code) // this is how snippets are eval'ed if they include "output="/"json_output=" - so if they include these, the scope of eval isn't global - doesn't matter much [13/07/18] { var output=undefined,json_output=undefined; eval(code); if(output!==undefined) parent.show_modal("
"+output+"
"); if(json_output!==undefined) parent.show_json(json_output); } function performance_trick() { // Needed for browsers only, Steam/Mac versions of the game always deliver high JS performance [03/02/19] parent.performance_trick(); // Just plays an empty sound file, so browsers don't throttle JS, only way to prevent it, interesting cheat [05/07/18] // Lately Chrome has been screwing things up with every update, mostly it's bugs and performance issues, but this time, the way Audio is played has been changed, so, once the game refreshes itself, the tabs need to be manually focused once for performance_trick() to become effective, as Audio can no longer automatically play [21/10/18] } //safety flags var last_loot=new Date(0); var last_potion=new Date(0); var last_message="",current_message=""; function code_draw() { var t; if(last_message!=current_message) $("#gg").html(current_message),last_message=current_message; if(!game.graphics) t=setTimeout(code_draw,16); // jsdom patch [18/04/19] else requestAnimationFrame(code_draw); } if(parent.is_cli) { if(parent.ls_emulation) window._localStorage=parent.ls_emulation; window.cli_require=parent.cli_require; } code_draw();