var request=require('request'),crypto=require('crypto'),range_check=require('range_check'); var protobuf=require('protobufjs'),ByteBuffer=require('bytebuffer'); // Steam decryption var false_socket={emit:function(a,b){ if(is_sdk && !server.shutdown) console.log([a,b]);},total_calls:0,calls:[]},current_socket=false_socket,call_modifier=1,ls_method=""; var server_start=new Date(); var timers={ "pinkgoo":false, "snowman":false, "wabbit":false, "hardcore":new Date(), "hide_and_seek":false, "cyberland":new Date(), }; var npcs={}; var signups={}; var hide_and_seek={}; var stats={ kills:{}, }; var edges={ "next_goldenbat":80000, "next_cutebee":480000, }; var NPC_prefix="NPC0000000000000000NPC"; function sprocess_game_data() { for(var name in G.items) { var def=G.items[name]; def.igrade=calculate_item_grade(def); if(!def.igrade) def.igrace=1; else if(def.igrade==1) def.igrace=-1; else if(def.igrade==2) def.igrace=-2; def.a=0; if(def.compound) { var u_v=calculate_item_value({name:name,level:3+(def.edge||0)}); def.a=parseInt(round(u_v/5000000)); } else if(def.upgrade) { var u_v=calculate_item_value({name:name,level:7+(def.edge||0)}); def.a=parseInt(round(u_v/5000000)); } else { var u_v=calculate_item_value({name:name}); def.a=parseInt(round(u_v/5000000)); } if(def.event) def.a=2; if(def.rare) def.a=12; } G.items.lostearring.igrade=2; if(gameplay=="test") test_logic(); if(gameplay=="hardcore") { hardcore_logic(); for(var m in D.drops.monsters) { for(var i=0;itotal/D.drops[n].length) D.drops[n][i][0]/=12.0; else if(D.drops[n][i][0]/1.5>total/D.drops[n].length) D.drops[n][i][0]/=3.0; } } for(var mname in G.maps) { if(!D.drops.maps[mname]) D.drops.maps[mname]=[]; for(var i=0;i0.08 && def.hp>900) pack.gold*=0.7; // if(def.hp>=50000) pack.gold*=0.90; // better dps utilisation - don't punish this pack.gold*=def.difficulty||1; pack.gold=parseInt(ceil(pack.gold))||0; if(def.stationary || def.xp<0) pack.gold=D.monster_gold[pack.type]||0; D.base_gold[pack.type]=D.base_gold[pack.type]||{}; D.base_gold[pack.type][mname]=pack.gold; console.log(mname+" "+pack.type+" gold: "+pack.gold+"["+parseInt(drop_value)+","+parseInt(map_value)+"] gold/hp: "+(pack.gold/def.hp)+" total%: "+((pack.gold+drop_value+map_value)/def.hp)); }); } for(var sname in G.sets) { var set=G.sets[sname]; for(var i=2;i<=set.items.length;i++) { set[i]=set[i]||{}; for(var prop in set[i-1]) { if(set[i][prop]) set[i][prop]+=set[i-1][prop]; else set[i][prop]=set[i-1][prop]; } } } if(is_pvp) { D.drops.maps.global_static.push([1.0/(gameplay=="hardcore"&&1000||100000),"pvptoken"]); } if(events.halloween) { G.monsters.jr.respawn=480; G.monsters.greenjr.respawn=480; D.drops.maps.global.push([0.00005,"candy0"]); D.drops.maps.global.push([0.00125,"candy1"]); } if(events.holidayseason) { events.snowman=60; } } function calculate_xvalue(arr,rec,divide,mult) { var value=0,total=0; // console.log(arr); arr.forEach(function(drop){ total+=drop[0]; }); if(!mult) mult=1; if(!divide) total=1; else mult=1; arr.forEach(function(drop){ if(drop[1]=="open") value+=min(1,drop[0]*mult/total)*calculate_xvalue(D.drops[drop[2]],1,1,mult); else if(drop[1]=="shells") value+=min(1,drop[0]*mult/total)*drop[2]*G.multipliers.shells_to_gold; else if(drop[1]=="empty"); else if(drop[1]=="gold") value+=drop[2]*min(1,drop[0]*mult/total); else { var def=G.items[drop[1]],q=drop[2]||1; var g=def.g,avalue=def.g; if(def.e && !rec) { var suffix=""; if(def.upgrade || def.compound) suffix="0"; avalue=calculate_xvalue(D.drops[drop[1]+suffix],1,1,mult)/def.e; // console.log(drop[1]+" value: "+g+" actual: "+avalue); } value+=q*min(1,drop[0]*mult/total)*(g+avalue)/2; } if(!value) console.log(drop); }); return value; } function add_to_trade_history(player,event,name,item,price) { if(!player.p.trade_history) player.p.trade_history=[]; var last=player.p.trade_history[player.p.trade_history.length-1]; if(last && last[0]==event && last[1]==name && last[2].name==item.name && last[2].level==item.level) { last[2].q=(last[2].q||1)+(item.q||1); last[3]+=price; return; } if(player.p.trade_history.length>=40) player.p.trade_history.shift(); player.p.trade_history.push([event,name,item,price]); } function add_to_history(player,event) { if(!player.p.history) player.p.history=[]; if(player.p.history.length>=400) player.p.history.shift(); player.p.history.push(event); } function is_free(player) { if(player.bot || player.p.free || player.s.licenced || player.role=="gm") return true; return false; } function is_player_allowed(player) { if(gameplay=="hardcore") { //if(player.type=="mage" || player.type=="priest") return false; //if(player.type=="rogue") return false; } var socket=player.socket,characters=0,ips=0,ipx=player.ipx||1,auths=0; if(player.temp_auth || player.auth_id) ipx=12; if(is_free(player) || player.stones && 0) return true; if(player.type=="merchant") { for(var id in players) { if(players[id].stones && 0 || id==player.socket.id || is_free(player)) continue; if(players[id].owner==player.owner && players[id].type=="merchant") return false; } return true; } for(var id in players) { if(players[id].stones && 0 || is_free(players[id]) || players[id].type=="merchant") continue; // was requested [28/10/16] if(players[id].owner==player.owner) characters++; if(players[id].name==player.name && player!=players[id]) return false; // "hardcore" if(get_ip(players[id])==get_ip(player)) ips++; if(player.auth_id && players[id].auth_id==player.auth_id) auths++; if(auths>variables.character_limit) return false; if(characters>variables.character_limit) return false; if(ips>variables.ip_limit*ipx) return false; } return true; } function rip(player) { player.hp=0; player.rip=true; player.rip_time=new Date(); player.moving=false; player.abs=true; if(player.party) send_party_update(player.party); } function notify_friends(data) { data.list.forEach(function(name){ var player=players[name_to_id[name]]; if(!player) return; player.socket.emit("online",{name:data.name,server:data.server}); }); } function check_player(player) // to check players in setTimeout's { if(!player || !player.socket || player.dc || !players[player.socket.id]) return false; return true; } function is_invis(player) { if(player.s && (player.s.invis || player.s.ethereal)) return true; return false; } function is_invinc(player) { if(player.s && (player.s.invis || player.s.ethereal || player.s.invincible)) return true; return false; } function is_in_pvp(player,allow_safe) { if(allow_safe && G.maps[player.map].safe) return false; if(is_pvp || G.maps[player.map].pvp) return true; return false; } function is_map_pvp(map,allow_safe) { if(allow_safe && G.maps[map].safe) return false; if(is_pvp || G.maps[map].pvp) return true; return false; } function is_same(player1,player2,party) { if(is_sdk && player1.name!=player2.name) return false; if(player1.name==player2.name) return true; if(player1.owner && player1.owner==player2.owner || !is_sdk && get_ip(player1)==get_ip(player2)) return true; if(party==3 && !is_in_pvp(player1)) return true; if(party && player1.s && player1.s.coop && player2.s && player2.s.coop && player1.s.coop.id==player2.s.coop.id) return true; // previously party==2 [08/05/21] if(party && player1.party && player1.party==player2.party) return true; if(party && player1.team && player1.team==player2.team) return true; return false; } function decay_s(player,ms) { for(var name in (player.s||{})) { if(G.conditions[name] && G.conditions[name].debuff) continue; if(player.s[name].ms<60000 || player.s[name].citizens) { if(player.s[name].citizens) { for(var p in player.s[name]) { if(p!="ms" && is_number(player.s[name][p])) player.s[name][p]=round(player.s[name][p]/4.0); } } player.s[name].ms-=ms; } } } function reset_player(player,soft) { if(!player.p.hardcore) { player.p={hardcore:true,dt:{}}; player.max_stats={monsters:{}}; } player.xp=0; if(soft) { // player.level=max(1,player.level-20); } else { player.level=1; player.xp=0; player.s={}; player.slots={}; player.items=[{"name":"computer"},{"name":"tracker"}]; player.gold=0; if(P[player.real_id] && gameplay!="test") { for(var p in P[player.real_id]) player[p]=P[player.real_id][p]; P[player.real_id]=null; } } } function save_player(player) { P[player.real_id]={ level:player.level, xp:player.xp, slots:player.slots, items:player.items, gold:player.gold, x:player.x, y:player.y, map:player.map, in:player.in, rip:player.rip, kills:player.kills, p:player.p, name:player.name, auth_id:player.auth_id, }; } function secondhands_logic(item,quantity) { var new_item=cache_item(item),done=false,count=0,replace=null; if(!(!G.items[item.name].buy || G.items[item.name].upgrade && item.level>=7 || G.items[item.name].compound && item.level>=2) || G.items[item.name].cash || item.expires || item.acl) return; if(item.grace!==undefined) new_item.grace=item.grace; if(new_item.q!==undefined) new_item.q=quantity; delete new_item.v; new_item.rid=randomStr(5); for(var i=0;i=5) current=replace; S.sold[current]=new_item; csold[current]=cache_item(S.sold[current],true); } } function lostandfound_logic(item) { var done=false,count=0,replace=null; item.rid=randomStr(5); for(var i=0;i=5) current=replace; S.found[current]=item; cfound[current]=cache_item(S.found[current],true); } } function server_loot(type) { if(type=="all") { for(var mtype in D.drops.monsters) for(var j=0;j0.1) D.drops.monsters[mtype][j][0]=0.05; for(var id in instances) { if(instances[id].map!=instances[id].name) continue; for(var mid in instances[id].monsters) if(instances[id].monsters[mid].frequency<4 && instances.main) drop_something(instances.main.players[NPC_prefix+"Kane"],instances[id].monsters[mid]); } } for(var id in chests) { var chest=chests[id]; if(type!="all" && hsince(chests[id].date)<48) return; delete chests[id]; S.gold+=chest.gold; if(chest.cash) S.cash+=chest.cash; if(chest.items) chest.items.forEach(function(item){ lostandfound_logic(item); }); if(chest.pvp_items) chest.pvp_items.forEach(function(item){ lostandfound_logic(item); }); } } function server_tax(gold,preview) { var tax=0; if(1) tax=parseInt(gold*0.1); else if(S.gold<500000000) tax=parseInt(gold*0.25); else if(S.gold<1000000000) tax=parseInt(gold*0.5); if(!preview) S.gold+=tax; return gold-tax; } function merchant_xp_logic(player,seller,price,tax) { if(is_same(player,seller)) return; if(!player.p.xpcache || Object.keys(player.p.xpcache).length>120 || hsince(player.p.dt.last_xpcache)>5*24) player.p.xpcache={},player.p.dt.last_xpcache=new Date(); if(!player.p.xpcache[seller.name]) player.p.xpcache[seller.name]=0; var initial=player.p.xpcache[seller.name]; player.p.xpcache[seller.name]=min(120000000,player.p.xpcache[seller.name]+round(price/4)); player.xp+=player.p.xpcache[seller.name]-initial; } function merchant_xp_logic(player,seller,price,tax) { if(is_same(player,seller)) return; player.xp+=tax*3.2; } function normalise(data) // normalise(data,["gold","gold"],["num","inv"]) { for(var i=1;inew Date()) { player.auth_type="mas"; player.auth_id=player.p.mas_auth_id; delete player.s.authfail; } else { console.log("verify_mas_receipt for "+player.name); player.temp_auth="mas"; //player.mas_receipt=receipt; var content={'receipt-data':receipt,password:variables.apple_token}; var url="https://buy.itunes.apple.com/verifyReceipt"; request( { url:url, method:"POST", json:content, }, function(err,response,body){ if(err) { console.log("#M: mas error: "+player.name+" - "+err); } if(body && body.status==0 && body.receipt.download_id && body.receipt.original_purchase_date_ms) { delete player.temp_auth; player.auth_type="mas"; player.auth_id=(""+body.receipt.download_id).substr(0,4)+body.receipt.original_purchase_date_ms; player.p.mas_auth_id=player.auth_id; player.p.mas_hash=quick_hash(receipt); player.p.dt.mas_expire=future_s(3*24*60*60); delete player.s.authfail; } else { delete player.temp_auth; console.log("#M: mas declined: "+player.name+" "+(body&&body.status)); } }); } } function verify_steam_ownership(player) { var url="https://partner.steam-api.com/ISteamUser/CheckAppOwnership/v2/"; data={ key:variables.steam_partner_key, steamid:player.p.steam_id, appid:"777150", } request.get({url:url,qs:data},function(err,response,body){ console.log(body); }); } function initiate_steam_microtxn(player) { var url="https://partner.steam-api.com/ISteamMicroTxn/InitTxn/v3/"; var orderid=parseInt(Math.random()*1000000000+1); console.log(orderid); data={ key:variables.steam_partner_key, steamid:player.p.steam_id, appid:"777150", usersession:"web", ipaddress:"85.98.170.74", orderid:orderid, itemcount:1, language:"en", currency:"USD", "itemid[0]":"123", "qty[0]":1, "amount[0]":999, // $9.99 "description[0]":"1000 Shells", } request.post({url:url,form:data},function(err,response,body){ console.log(body); }); } function invincible_logic(player,place) { if(is_pvp || G.maps[player.map].pvp) { player.s.invincible={ms:max(6000,player.s.invincible&&player.s.invincible.ms||0)}; } } function serverhop_logic(player) { delete player.s.hopsickness; player.p.entries=player.p.entries||[]; var servers={},main=null,mx=0,hops=0; for(var i=player.p.entries.length-2;i>=0;i--) { var hours = Math.abs(new Date(player.p.entries[i+1][1])-new Date(player.p.entries[i][1])) / 36e5; servers[player.p.entries[i][0]]=(servers[player.p.entries[i][0]]||0)+hours; } for(var id in servers) { if(servers[id]>mx) main=id,mx=servers[id]; } for(var i=player.p.entries.length-1;i>=0;i--) { if(hsince(new Date(player.p.entries[i][1]))<4 && player.p.entries[i][0]!=main) hops++; } if(main && hops && main!=region+server_name && player.level>=60) { player.s.hopsickness={ms:min(10000*hops*hops,3*60*60*1000)}; if(hops>20) player.s.hopsickness.luck=-80,player.s.hopsickness.gold=-80,player.s.hopsickness.output=-50; else if(hops>15) player.s.hopsickness.luck=-60,player.s.hopsickness.gold=-60,player.s.hopsickness.output=-40; else if(hops>10) player.s.hopsickness.luck=-40,player.s.hopsickness.gold=-40,player.s.hopsickness.output=-30; else if(hops>5) player.s.hopsickness.luck=-30,player.s.hopsickness.gold=-30,player.s.hopsickness.output=-20; else if(hops>2) player.s.hopsickness.luck=-20,player.s.hopsickness.gold=-20,player.s.hopsickness.output=-10; calculate_player_stats(player); } var c=[region+server_name,""+(new Date())]; player.p.entries.push(c); if(player.p.entries.length>60) player.p.entries.shift(); } function serverhop_logic(player) { if(player.p.entries && player.p.entries[0] && player.p.entries[0][0]==region+server_name) return; delete player.s.hopsickness; if(!player.p.home) player.p.home=region+server_name; player.p.entries=[[region+server_name,""+(new Date())]]; if(player.p.home!=region+server_name && player.level>=60 && server_name!="PVP") add_condition(player,"hopsickness"); } function ghash(entity,zone,a_d,b_d) //zone is the square's dimension, a_d, and b_d are displacements { var a=floor(1.0*entity.x/zone)+(a_d||0),b=floor(1.0*entity.y/zone)+(b_d||0); return a+"|"+b; } function set_ghash(hash,entity,zone) { var h=ghash(entity,zone); if(!hash[h]) hash[h]={}; hash[h][entity.id]=entity; } function get_nearby_ghash(hash,entity,zone) { var d=[-1,0,1],l=[],h; for(var i=0;i<3;i++) for(var j=0;j<3;j++) { h=ghash(entity,zone,d[i],d[j]); for(var id in hash[h]) l.push(hash[h][id]); } return l; } function screenshot_npc(player,args) { if(!args) args={}; if(!args.name) args.name=randomStr(10); instance=instances[player.in]; var npc=create_npc({name:args.name,level:args.level||40,speed:1200,hp:100000000,skin:args.skin||"mranger",cx:args.cx||[]},{position:[player.x,player.y],id:args.name},instance); if(args.stand) npc.p.stand=args.stand; npc.screenshot=true; npc.direction=player.direction; npc.id=args.name; instance.players[NPC_prefix+npc.id]=npc; } function get_npc_coords(id) { for(var iid in instances) { if(G.maps[iid] && G.maps[iid].ref && G.maps[iid].ref[id]) return G.maps[iid].ref[id]; } return {x:0,y:0,map:"main",in:"main"}; } function pmap_remove(player) { try{ delete instances[player.in].pmap[player.last_hash][player.id]; if(!Object.keys(instances[player.in].pmap[player.last_hash]).length) delete instances[player.in].pmap[player.last_hash]; } catch(e){} } function pmap_get(player) { var hash=ghash(player,6); if(!instances[player.in].pmap[hash]) return {}; return instances[player.in].pmap[hash]; } function pmap_add(player) { var hash=ghash(player,6); if(!instances[player.in].pmap[hash]) instances[player.in].pmap[hash]={}; instances[player.in].pmap[hash][player.id]=player; player.last_hash=hash; } function pmap_move(player) { var hash=ghash(player,6); if(player.last_hash==hash) return; pmap_remove(player); pmap_add(player); } function init_tavern() { if(!instances.tavern) return; tavern=instances.tavern; tavern.roulette={ state:"bets", next:future_s(25), "odds":{ "red":2, "black":2, "odd":2, "even":2, "1/3":3, "2/3":3, "3/3":3, "1/2":2, "2/2":2, "3n":3, "3n+1":3, "3n+2":3, }, gain:0, players:{}, }; for(var i=0;i<=36;i++) tavern.roulette.odds[""+i]=36; tavern.poker={ rooms:[], gain:0, }; tavern.dice={ rolls:[], state:"lock", seconds:0, next:future_s(1), players:{}, bets:[], }; tavern.dice.num=""+parseInt(Math.random()*10)+""+parseInt(Math.random()*10)+"."+parseInt(Math.random()*10)+""+parseInt(Math.random()*10); tavern.info.dice="lock"; tavern.info.num=tavern.dice.num; tavern.info.seconds=tavern.dice.seconds; } function house_debt() { var gold=0; for(var id in tavern.dice.players) { var player=players[id]; if(!player) continue; for(var b_id in player.bets||{}) { var bet=player.bets[b_id]; if(bet.type!="dice") continue; gold+=bet.win-bet.edge-bet.gold; } } return gold; } function house_edge() { var gold=S.gold-house_debt(); if(gold>5000000000) return 0.5; if(gold>2000000000) return 1; if(gold>1000000000) return 1.5; return 2; } var dice_last_roll=0; function tavern_loop() { try{ if(!server.live || !instances.tavern) return; var c=new Date(); if(c>tavern.dice.next) { if(tavern.dice.state=="bets") { tavern.dice.seconds+=1; tavern.info.seconds=tavern.dice.seconds; if(tavern.dice.seconds>=30) { tavern.info.dice=tavern.dice.state="roll"; dice_last_roll=tavern.info.num; delete tavern.info.num; tavern.dice.next=future_s(10); instance_emit(tavern,"dice",{state:"roll"}); //server_log("Dice: Bets Over"); } } else if(tavern.dice.state=="roll") { tavern.info.dice=tavern.dice.state="lock"; tavern.info.num=tavern.dice.num; tavern.dice.next=future_s(1.6); for(var id in tavern.dice.players) { var player=players[id]; if(!player) continue; for(var b_id in player.bets||{}) { var bet=player.bets[b_id]; if(bet.type!="dice") continue; delete players[bet.pid].bets[bet.id]; tavern.dice.bets.push(bet); if(bet.dir=="up" && parseFloat(tavern.dice.num)>=bet.num || bet.dir=="down" && parseFloat(tavern.dice.num)<=bet.num) { if(player.type=="merchant") player.xp+=parseInt(bet.edge*7.2); player.gold+=bet.win-bet.edge,S.gold-=bet.win-bet.edge-bet.gold; if(bet.win-bet.edge-bet.gold>=12000000) lstack(S.logs.dice,{name:player.name,gold:bet.win-bet.edge-bet.gold,odds:bet.odds}); } else { if(bet.gold>=10000000) lstack(S.logs.dice,{name:player.name,gold:-bet.gold,odds:bet.odds}); S.gold+=bet.gold; } } } instance_emit(tavern,"dice",{state:"lock",num:tavern.dice.num,text:tavern.dice.text,key:tavern.dice.key}); //server_log("Dice: Roll Over - Locking"); } else if(tavern.dice.state=="lock") { tavern.dice.state="suspense"; tavern.dice.bets.forEach(function(bet){ var player=players[bet.pid]; if(!player) return; if(bet.dir=="up" && parseFloat(tavern.dice.num)>=bet.num || bet.dir=="down" && parseFloat(tavern.dice.num)<=bet.num) { player.socket.emit("game_log",{"message":"Won: "+to_pretty_num(bet.win)+" gold ["+tavern.dice.num+"]","color":"gold"}); player.socket.emit("game_log",{"message":"House edge: "+to_pretty_num(bet.edge)+" gold","color":"gray"}); instance_emit(tavern,"tavern",{event:"won",name:player.name,type:"dice",num:bet.num,gold:bet.win,dir:bet.dir,net:bet.win-bet.gold-bet.edge}); } else { player.socket.emit("game_log",{"message":"Lost: "+to_pretty_num(bet.gold)+" gold ["+tavern.dice.num+"]","color":"gray"}); instance_emit(tavern,"tavern",{event:"lost",name:player.name,type:"dice",num:bet.num,gold:bet.gold,dir:bet.dir}); } if(player.xp>=player.max_xp) resend(player,"reopen"); else resend(player,"reopen+nc"); }); tavern.dice.next=future_s(2); //server_log("Dice: Locked - Suspensing"); } else if(tavern.dice.state=="suspense") { var timer=new Date(); tavern.info.dice=tavern.dice.state="bets"; tavern.info.seconds=tavern.dice.seconds=0; tavern.dice.players={}; tavern.dice.bets=[]; tavern.dice.num=""+parseInt(Math.random()*10)+""+parseInt(Math.random()*10)+"."+parseInt(Math.random()*10)+""+parseInt(Math.random()*10); tavern.dice.key=randomStr(20+parseInt(Math.random()*20)); var initials=""; for(var id in tavern.players) if(!tavern.players[id].npc) initials+=tavern.players[id].name[0]; tavern.dice.text="Num: "+tavern.dice.num+" Initials: "+initials+" Random: "+randomStr(16+parseInt(Math.random()*16)); var hmac=crypto.createHmac('sha256',tavern.dice.key); hmac.update(tavern.dice.text); tavern.dice.hex=hmac.digest('hex'); hmac.end(); instance_emit(tavern,"dice",{state:"bets",hex:tavern.dice.hex,algorithm:"hmac-sha256"}); if(gameplay=="hardcore" && Math.random()<0.07 && dice_last_roll) tavern.dice.num=dice_last_roll; //server_log("Dice: Bets Starting For: "+tavern.dice.num+" in "+mssince(timer)+"ms"); } } if(c>tavern.roulette.next && 0) { if(tavern.roulette.state=="bets") { server_log("Roulette: Bets Over"); tavern.roulette.state="roll"; tavern.roulette.next=future_s(10); } else if(tavern.roulette.state=="roll") { tavern.roulette.roll=floor(Math.random()*37); tavern.roulette.state="award"; tavern.roulette.next=future_s(5); server_log("Roulette: Rolled "+tavern.roulette.roll); } else if(tavern.roulette.state=="award") { var winners={},roll=tavern.roulette.roll; if(roll==0) winners["0"]=1; else { if(roll%2) winners["odd"]=1; else winners["even"]=1; if([2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35].indexOf(roll)!=-1) winners["black"]=1; else winners["red"]=1; if(roll<=12) winners["1/3"]=1; else if(roll<=24) winners["2/3"]=1; else winners["3/3"]=1; if(roll<=18) winners["1/2"]=1; else winners["2/2"]=1; if((roll%3)==1) winners["3n+1"]=1; else if((roll%3)==2) winners["3n+2"]=1; else winners["3n"]=1; } var totals={}; try{ for(var id in tavern.roulette.players) { var player=players[id]; if(!player) continue; for(var b_id in player.bets||{}) { var bet=player.bets[b_id]; if(bet.type!="roulette") continue; if(!totals[bet.pid]) totals[bet.pid]=0; if(winners[bet.odd]) totals[bet.pid]+=bet.gold*tavern.roulette.odds[bet.odd],tavern.roulette.gain-=bet.gold*tavern.roulette.odds[bet.odd]; else tavern.roulette.gain+=bet.gold; // else totals[bet.pid]-=bet.gold; if(players[bet.pid]) { delete players[bet.pid].bets[bet.id]; if(winners[bet.odd]) players[bet.pid].gold+=bet.gold*tavern.roulette.odds[bet.odd]; } } } }catch(e){ log_trace("Critical-roulette_win",e); } server_log("Roulette: Awards | Gain: "+to_pretty_num(tavern.roulette.gain)); for(var id in totals) { var player=players[id]; if(!player) continue; if(winners["0"]) player.socket.emit("game_log",{message:"The lucky number is "+roll+" Green",color:"#2E7C2A"}); else if(winners["red"]) player.socket.emit("game_log",{message:"The lucky number is "+roll+" Red",color:"#911609"}); else player.socket.emit("game_log",{message:"The lucky number is "+roll+" Black",color:"#5C5D5D"}); if(!totals[id]) { player.socket.emit("game_log","Better luck next time"); } else { player.socket.emit("game_log",{message:"You've won "+to_pretty_num(totals[id])+" gold",color:"gold"}); } resend(player,"reopen+nc"); } tavern.roulette.state="bets"; tavern.roulette.next=future_s(25); server_log("Roulette: Bets Open"); } } tavern.poker.rooms.forEach(function(room){ if(room.state=="shuffle") { room.cards=[]; for(var i=1;i<=52;i++) room.cards.push(i); shuffle(room.cards); } }); }catch(e){ log_trace("Critical-tavern_loop",e);} } function create_npc(npc,map_def,instance) { var entity={ speed:npc.speed||20, attack:npc.attack||100, range:npc.range||40, level:npc.level||100,hp:npc.hp||1200,max_hp:npc.hp||1200,armor:500,mp:2000, a:{}, xp:0, pdps:0, // once got killed by a blaster ... [07/04/21] skin:npc.skin,name:npc.name, red_zone:0, in:instance.name,map:instance.map, def:npc, npc:map_def.id, ntype:map_def.id,is_player:true,is_npc:true, type:npc['class']||"merchant", id:"$"+npc.name, gold:0,items:[],citems:[], cid:0,u:true, citizen:true, luckm:1, // for loot_all_monsters s:{},c:{},q:{},p:{},m:0,slots:{},bets:{}, vision:[0,0],socket:false_socket,cx:npc.cx||{}, base:{h:8,v:7,vn:2}, last:{move:new Date(),attack:really_old,attacked:really_old}, delay:npc.delay||600,d_multiplier:1,steps:npc.steps||40, } if(npc.slots) entity.slots=npc.slots; if(npc.projectile) entity.projectile=npc.projectile; if(map_def.positions) entity.x=map_def.positions[0][0],entity.y=map_def.positions[0][1],entity.positions=map_def.positions; else { entity.x=map_def.position[0],entity.y=map_def.position[1]; if(npc.type=="fullstatic" && map_def.position.length==3) entity.direction=map_def.position[2]; } if(map_def.boundary) entity.boundary=map_def.boundary; if(map_def.loop) { entity.going_x=entity.x,entity.going_y=entity.y; start_moving_element(entity); entity.loop=true,entity.last_m=0; } if(npc.role=="citizen" || npc.moving) entity.movable=true; return entity; } function create_instance(name,map_name,args) { if(!map_name) map_name=name; if(!smap_data[map_name] && smap_data[map_name]!=-1) server_bfs(map_name); if(!args) args={}; var instance={players:{},monsters:{},observers:{},map:map_name,allow:true,name:name,pmap:{},rage_list:[],last_update:future_ms(parseInt(Math.random()*30)),last_player:future_s(240),npcs:0,paused:false,info:{},operators:1}; instances[name]=instance; if(args.solo) instance.solo=args.solo; if(args.pvp) instance.pvp=true,instance.allow=false; if(args.event) instance.allow=false; var map=G.maps[map_name]; if(map.mount && gameplay=="normal") instance.mount=true; for(var i=0;i<(map.monsters||[]).length;i++) { var map_def=map.monsters[i]; if(map_def.special) continue; if(map_def.rage) { map_def.id=randomStr(5); instance.rage_list.push(map_def); } for(var j=0;j=12 is a must."); }); server_log("Created an instance of "+instances[name].map,1); return instance; } function pause_instance(instance) { server_log("Paused: "+instance.name); instance.paused=true; } function resume_instance(instance) { if(!instance.paused) return; // server_log("Resumed: "+instance.name); instance.paused=false; update_instance(instance); } function destroy_instance(name) { for(var id in instances[name].players) { var player=instances[name].players[id]; if(player.npc) continue; if(player.state && !player.state.restored && instances[player.state.map] && !instances[player.state.map].mount && player.in!=name) transport_player_to(player,player.state.map,[player.state.x,player.state.y]); else transport_player_to(player,"main"); restore_state(player); resend(player,"cid+reopen"); // reopen for abtesting } for(var id in instances[name].monsters) { remove_monster(instances[name].monsters[id],{silent:true}); // to make sure targets are always updated } server_log("Deleted an instance of "+instances[name].map,1); delete instances[name]; } function spawn_special_monster(type) { if(type=="pinkgoo") { var packs=[]; ["main","cave","halloween","winterland"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(!p.boundaries) packs.push({"type":"pinkgoo",boundary:p.boundary,count:1,i:m}); // comment boundary out next year [09/02/19] }); }); var pack=packs[floor(Math.random()*packs.length)]; pack.gold=D.monster_gold.pinkgoo; server_log("Love Goo: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"pinkgoo",map:pack.i}); } if(type=="crabxx") { var pack=null; ["main"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(p.type=="crabx") pack={"type":"crabxx",boundary:p.boundary,count:1,i:m}; }); }); pack.gold=D.monster_gold.crabxx; server_log("Crab XX: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"crabxx",map:pack.i}); } if(type=="snowman") { var packs=[]; ["winterland"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(!p.boundaries) packs.push({"type":"snowman",boundary:p.boundary,count:1,i:m}); }); }); var pack=packs[floor(Math.random()*packs.length)]; pack.gold=D.monster_gold.snowman; pack={"type":"snowman",boundary:[682,-967,1482,-779],count:1,i:"winterland"} server_log("Snowman: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"snowman",map:pack.i,x:900,y:-800}); } if(type=="dragold") { var packs=[]; var pack=packs[floor(Math.random()*packs.length)]; pack={"type":"dragold",boundary:[1018,-940,1385,-624],count:1,i:"cave"}; pack.gold=100000; server_log("Dragold: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"dragold",map:pack.i,x:900,y:-800}); } if(type=="franky") { var map="level2w"; var pack={"type":"franky",boundary:G.maps[map].monsters[0].boundary,count:1,i:map}; pack.gold=D.monster_gold.franky; server_log("Franky: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"franky",map:pack.i,x:G.maps[map].monsters[0].boundary[0],y:G.maps[map].monsters[0].boundary[1]}); } if(type=="icegolem") { var map="winterland"; var pack={"type":"icegolem",boundary:[782.25,395.96,888.71,450.28],count:1,i:map,roam:true}; pack.gold=D.monster_gold.icegolem; server_log("Ice Golem: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"icegolem",map:pack.i,x:pack.boundary[0],y:pack.boundary[1]}); } if(type=="mrpumpkin" || type=="mrgreen" || type=="grinch") { var pack={},map="none"; for(var mname in G.maps) { var def=G.maps[mname]; if(def.ignore) continue; (def.monsters||[]).forEach(function(p) { if(p.type==type) pack=p,map=mname; }); } server_log(G.monsters[type].name+": "+JSON.stringify(pack)); var monster=new_monster(map,pack); broadcast("game_event",{name:type,map:map,x:pack.boundary[0],y:pack.boundary[1]}); if(type=="grinch") monster.extra_gold=12000000; } if(type=="slenderman") { var packs=[]; var m=random_one(["cave","halloween","spookytown"]) var p=random_place(m); var pack={"type":"slenderman",boundary:[p.x,p.y,p.x,p.y],count:1,i:m}; pack.gold=D.monster_gold.slenderman; //server_log("Love Goo: "+JSON.stringify(pack)); var monster=new_monster(pack.i,pack); broadcast("game_event",{name:"slenderman",map:pack.i}); } if(type=="tiger") { var packs=[]; var m=random_one(["cave","main"]) var p=random_place(m); var pack={"type":"tiger",boundary:[p.x,p.y,p.x,p.y],count:1,i:m}; pack.gold=D.monster_gold.tiger; //server_log("Love Goo: "+JSON.stringify(pack)); var monster=new_monster(pack.i,pack); broadcast("game_event",{name:"tiger",map:pack.i}); } if(type=="wabbit") { var packs=[]; ["main","cave","halloween","winterland","tunnel","mansion","winter_cave"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(!p.boundaries) packs.push({"type":"wabbit",boundary:p.boundary,count:1,i:m,roam:true}); }); }); var pack=packs[floor(Math.random()*packs.length)]; pack.gold=D.monster_gold.wabbit; //server_log("Love Goo: "+JSON.stringify(pack)); new_monster(pack.i,pack); broadcast("game_event",{name:"wabbit",map:pack.i}); } if(type=="goldenbat") { var packs=[]; ["cave"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(!p.boundaries) packs.push({"type":"goldenbat",boundary:p.boundary,count:1,i:m,roam:true}); }); }); var pack=packs[floor(Math.random()*packs.length)]; pack.gold=D.monster_gold.goldenbat; //server_log("Love Goo: "+JSON.stringify(pack)); new_monster(pack.i,pack); // broadcast("game_event",{name:"goldenbat",map:pack.i}); } if(type=="cutebee") { var packs=[]; ["main"].forEach(function(m){ G.maps[m].monsters.forEach(function(p){ if(!p.boundaries) packs.push({"type":"cutebee",boundary:p.boundary,count:1,i:m,roam:true}); }); }); var pack=packs[floor(Math.random()*packs.length)]; pack.gold=D.monster_gold.cutebee; //server_log("Love Goo: "+JSON.stringify(pack)); new_monster(pack.i,pack); } } function collect_signups(event) { var names=[],toggle=0; for(var name in signups) names.push(name); shuffle(names); names.forEach(function(name){ var player=players[name_to_id[name]]; if(!player) return; if(event=="abtesting") player.team=(toggle==1&&"A")||"B",toggle=1-toggle; if(event=="abtesting" && player.team=="A") transport_player_to(player,event,2),resend(player,"cid"); else if(event=="abtesting" && player.team=="B") transport_player_to(player,event,3),resend(player,"cid"); else transport_player_to(player,event); }); } var last_daily=null; function event_loop() { try { var c=new Date(),ch=(c.getUTCHours()+24+E.schedule.time_offset)%24; var change=false; if(ch>=0 && ch<=5) E.schedule.night=true; else E.schedule.night=false; E.schedule.dailies.forEach(function(h){ if(ch==h && last_daily!=h && msince(server_start)>2) { last_daily=h; var event=dailies.shift(); dailies.push(event); events[event]=true; } }); E.schedule.nightlies.forEach(function(h){ if(ch==h && last_daily!=h && msince(server_start)>2) { last_daily=h; var event=nightlies.shift(); nightlies.push(event); events[event]=true; } }); if(events.halloween) { var s=get_monster("slenderman"); if(s) { var p=false; for(var id in projectiles) { if(projectiles[id].target==s) p=true; } if(!s.last_jump || msince(s.last_jump)>2) p=true; for(var name in instances[s.in].players) { var player=instances[s.in].players[name]; if(p || !player.s.invis && distance(player,s)<600) { xy_emit(s,"disappear",{id:s.id}); delete instances[s.in].monsters[s.id]; s.oin=s.in=s.map=random_one(["spookytown","halloween","cave"]); instances[s.in].monsters[s.id]=s; var p=random_place(s.map); s.moving=false; s.abs=true; s.map_def.boundary=[p.x,p.y,p.x,p.y]; s.map_def.i=s.map; s.x=p.x; s.y=p.y; s.u=true; s.cid++; s.last_jump=new Date(); break; } } } } if(monster_c.tiger) { var m=get_monster("tiger"); var ps=[]; for(var id in players) ps.push(players[id]); var player=random_one(ps); var spot=null,p=false; for(var id in projectiles) { if(projectiles[id].target==m && projectiles[id].attack>999) p=true; } if(p || Math.random()<0.01) { if(player) spot=safe_xy_nearby(player.map,player.x,player.y); if(m && player && player.in==player.map && spot && !G.maps[player.map].mount && !player.s.hopsickness && player.p.home==region+server_name) { xy_emit(m,"disappear",{id:m.id}); delete instances[m.in].monsters[m.id]; m.oin=m.in=m.map=player.map; instances[m.in].monsters[m.id]=m; m.moving=false; m.abs=true; m.x=spot.x; m.y=spot.y; m.map_def.boundary=[m.x,m.y,m.x,m.y]; m.map_def.i=m.map; m.u=true; m.cid++; m.last_jump=new Date(); } } } ["crabxx","franky","icegolem"].forEach(function(name){ if(events[name]) { var monster=get_monster(name); if(!monster && monster_c[name]) return; // irregular move if(!timers[name]) { timers[name]=future_s(G.events[name].duration); spawn_special_monster(name); } else if(c>timers[name] && (!monster || !monster.last.attacked || ssince(monster.last.attacked)>20)) { if(monster) remove_monster(monster,{method:"disappear"}); broadcast("notice",{message:G.monsters[name].name+" Event is over ..."}); events[name]=false; change=true; delete E[name]; delete timers[name]; } else { var monster=get_monster(name) if(!monster) { broadcast("notice",{message:G.monsters[name].name+" has been defeated!"}); events[name]=false; change=true; delete E[name]; delete timers[name]; } else { if(!E[name]) change=true; E[name]={live:true,map:monster.map,hp:monster.hp,max_hp:monster.max_hp,target:monster.target,x:monster.x,y:monster.y,end:timers[name]}; if(name=="crabxx") { var big=get_monster("crabx"); if(monster && big && !monster["1hp"]) { monster["1hp"]=true; monster.s={}; monster.cid++; monster.u=true; } else if(monster && !big && monster["1hp"]) { monster["1hp"]=false; monster.cid++; monster.u=true; } } } } } }); if(events.goobrawl) { if(!timers.goobrawl) { timers.goobrawl=future_s(G.events.goobrawl.duration); E.goobrawl={end:timers.goobrawl}; change=true; //create_instance("goobrawl","goobrawl",{event:true}); // collect_signups("goobrawl"); broadcast("notice",{message:"Goo Brawl has begun!"}); } else if(c>timers.goobrawl) { events.goobrawl=false; delete E.goobrawl; delete timers.goobrawl; change=true; //destroy_instance("goobrawl"); broadcast("notice",{message:"Goo Brawl is over, hope you had fun!"}); } else if(instances.goobrawl && Object.keys(instances.goobrawl.monsters).length<6 && Math.random()<0.3) { if(Math.random()<0.01) { var data=clone(G.maps.goobrawl.monsters[0]); data.type="rgoo"; var m=new_monster("goobrawl",data); //m.s.filter={ms:9999999,name:"scale",scale:2}; } else { var data=clone(G.maps.goobrawl.monsters[0]); data.type="bgoo"; var m=new_monster("goobrawl",data); m.skin=random_one(["goo0","goo1","goo2","goo3","goo4","goo5","goo6"]); // m.drops=[[0.5,"funtoken"]]; m.u=true; m.cid++; } } } if(gameplay=="hardcore" && ssince(timers.hardcore)>64) { timers.hardcore=new Date(); for(var id in instances.woffice.players) { var player=instances.woffice.players[id],gold=480000; player.gold+=gold; player.socket.emit("game_log","Received "+to_pretty_num(gold)+" gold"); player.socket.emit("disappearing_text",{"message":"+"+gold,"x":player.x,"y":player.y-32,"args":{color:"+gold","size":"large"}}); resend(player,"reopen"); } } if(events.goldenbat && stats.kills.bat>edges.next_goldenbat) { edges.next_goldenbat+=parseInt(events.goldenbat*Math.random()); spawn_special_monster("goldenbat"); } if(events.cutebee && stats.kills.bee>edges.next_cutebee) { edges.next_cutebee+=parseInt(events.cutebee*Math.random()); spawn_special_monster("cutebee"); } if(!events.holidayseason && events.snowman && !monster_c.snowman) { if(!timers.snowman) timers.snowman=future_s(events.snowman*60); else if(timers.snowman && c>timers.snowman) timers.snowman=0,spawn_special_monster("snowman"); } var eventmap=[ ["halloween","mrpumpkin"], ["halloween","mrgreen"], ["halloween","slenderman",true], ["holidayseason","grinch"], ["holidayseason","snowman"], ["lunarnewyear","dragold"], ["lunarnewyear","tiger",true], ["valentines","pinkgoo",true], ["egghunt","wabbit",true], ]; eventmap.forEach(function(s){ if(events[s[0]] && !monster_c[s[1]]) { if(!timers[s[1]]) { if(timers[s[1]]!==0) timers[s[1]]=future_s(120); else timers[s[1]]=future_s(G.monsters[s[1]].respawn); E[s[1]]={live:false,spawn:timers[s[1]]}; broadcast_e(); } else if(timers[s[1]] && c>timers[s[1]]) { timers[s[1]]=0; spawn_special_monster(s[1]); var m=get_monsters(s[1])[0]; var data={live:true,map:m.map,hp:m.hp,max_hp:m.max_hp,target:m.target}; if(!s[2]) data.x=m.x,data.y=m.y; E[s[1]]=data; broadcast_e(); } } }); ["holidayseason","halloween","lunarnewyear","valentines","egghunt"].forEach(function(event){ if(events[event]) E[event]=true; else if(E[event]) delete E[event]; }); ["snowman","grinch","dragold","mrpumpkin","mrgreen","wabbit","pinkgoo","tiger"].forEach(function(type){ if(E[type] && !monster_c[type]) { delete E[type]; // [20/01/23] change=true; } }); for(var name in instances) { for(var id in instances[name].monsters) { var monster=instances[name].monsters[id]; ["tiger"].forEach(function(type){ if(monster.type==type) { E[type]={live:true,map:monster.map,hp:monster.hp,max_hp:monster.max_hp,target:monster.target}; } }); ["snowman","grinch","dragold","mrpumpkin","mrgreen","wabbit","pinkgoo"].forEach(function(type){ if(monster.type==type) { if(!E[type]) change=true; E[type]={live:true,map:monster.map,hp:monster.hp,max_hp:monster.max_hp,target:monster.target,x:monster.x,y:monster.y}; } }); if(monster.type=="grinch") { if(instances[monster.in].paused) resume_instance(instances[monster.in]); var phrase=null,disengage=false; if(Math.random()<0.1) { if(monster.target && !G.monsters.grinch.good) phrase=random_one(["Come to papa","This is not a chew toy!","Give me that! Don't you know you're not supposed to take things that don't belong to you? What's the matter with you? You some kind of wild animal?","HELP ME…I'm FEELING.","It came without ribbons, it came without tags. It came without packages, boxes, or bags.","Poor, poor, "+monster.target,"Innie, minnie, tiny "+monster.target+"innie"]); else if(!monster.target) phrase=random_one(["That is not a chew toy!","Stupid. Ugly. Out of date. This is ridiculous. If I can't find something nice to wear I'm not going.","Kids today. So desensitized by movies and television.","Holiday who-be what-ee?","I could use a little social interaction.","It's because I'm green isn't it?","Social distancing what?"]) } if(monster.target && get_player(monster.target) && get_player(monster.target).slots.chest && get_player(monster.target).slots.chest.name=="xmassweater" && !G.monsters.grinch.good) phrase="Ugh. What's that ugly thing you are wearing?! I can't look at it. Stop.",disengage=true; if(phrase) xy_emit(monster,"chat_log",{owner:"Grinch",message:phrase,id:monster.id,color:"#418343"}); if(!monster.target && Math.random()<0.1*Object.keys(players).length) { monster.last_attack=future_ms(1000); var player=random_one(players); if(player.level<50 && Math.random()>0.08 || G.maps[player.map] && (G.maps[player.map].safe || G.maps[player.map].instance || G.maps[player.map].irregular)) player=null; if(player && 0) // attack everyone for(var pid in instances[player.in].players) { if(!player) break; var ally=instances[player.in].players[pid]; if(ally.name!=player.name && !ally.npc && (ally.type!="merchant" || player.type=="merchant") && simple_distance(ally,player)<500) { player=null; break; } } if(player) target_player(monster,player); } if(monster.target && !is_disabled(monster) && Math.random()<0.05 || disengage) stop_pursuit(monster,{force:true,cause:"disengage"}); } } } eventmap.forEach(function(s){ if(events[s[0]] && !E[s[1]]) E[s[1]]={live:false,spawn:timers[s[1]]},change=true; }); if(events.goblin) { } if(events.hide_and_seek) { } if(events.abtesting) { if(timers.abtesting && c>timers.abtesting) { var winner="A"; if(E.abtesting.B>E.abtesting.A) winner="B"; events.abtesting=false; timers.abtesting=false; delete E.abtesting; change=true; broadcast("server_message",{message:"Team "+winner+" wins! Hope you all had fun!",color:"#4BB6E1"}); for(var id in instances.abtesting.players) { var player=instances.abtesting.players[id],table="abtesting"; if(!player.team) continue; if(player.team!=winner) table="abtesting_loser"; if(!player.esize) { socket.emit("game_log","Full inventory. Unable to receive a prize."); continue;} exchange(player,table); } destroy_instance("abtesting"); } else if(!timers.abtesting) { timers.abtesting=future_s(G.events.abtesting.duration); change=true; timers.abtesting_start=c; E.abtesting={end:timers.abtesting,signup_end:future_s(60),A:0,B:0,id:randomStr(5)}; create_instance("abtesting","abtesting",{event:true}); collect_signups("abtesting"); broadcast("server_message",{message:"A/B Testing has begun!",color:"#4BB6E1"}); // npcs.bean.s.invis={ms:999999999999}; xy_emit(npcs.bean,"disappear",{id:"Bean"}); } } if(instances.cyberland && ssince(timers.cyberland)>10) { var detected=false; timers.cyberland=new Date(); for(var id in instances.cyberland.monsters) { var monster=instances.cyberland.monsters[id]; if(monster.target) detected=get_player(monster.target); } if(detected) { xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"ALERT",id:"mainframe"}); for(var id in instances.cyberland.monsters) { var monster=instances.cyberland.monsters[id]; if(!monster.target) target_player(monster,detected); } } if(Math.random()<1.0/1600) { S.misc.spares.push("electronics"); if(Math.random()<1.0/1020) S.misc.spares.push("networkcard"); if(S.misc.spares.length>2000) S.misc.spares.length=2000; } } for(var id in (E.duels||{})) { var duel=E.duels[id]; var instance=instances[id]; var info=instance.info,a=0,b=0; if(info.seconds) info.seconds--; // console.log(info.A); console.log(info.B); for(var i=0;imonster.range) stop_pursuit(monster,{cause:"cant_move_smart"}); } } }); worker.on('error',function(data){ console.log("#W Worker error: "+JSON.stringify(data)); }); worker.on('exit',function(code){ workers[this.wnum]=new_worker(this.wnum); }); return worker; } function init_server() { if(gameplay=="hardcore") { E={ "rewards":{ "item8":null, "item9":null, "item10":null, "item11":null, "item12":null, "leader":null, "first_fvampire":null, "first_mvampire":null, "first_skeletor":null, "first_stompy":null, "first_franky":null, "first_ent":null, "first_wabbit":null, "accessory5":null, "accessory6":null, "first_warrior_70":null, "first_paladin_70":null, "first_priest_70":null, "first_mage_70":null, "first_ranger_70":null, "first_rogue_70":null, "!participation":"Every authorized account with a level 60+ character receives a participation award!", }, } E.minutes=340; if(is_sdk) E.minutes=12; setInterval(hardcore_loop,60*1000); } setInterval(event_loop,1000); setInterval(tavern_loop,1000); workers=[ new_worker(0), new_worker(1), new_worker(2), new_worker(3), new_worker(4), new_worker(5), new_worker(6), new_worker(7), ]; } function init_server_data() { if(!S.dt) S.dt={}; if(!S.sold) S.sold=[]; if(!S.found) S.found=[]; if(!S.gold) S.gold=0; if(!S.cash) S.cash=0; if(!S.logs) S.logs={"donate":[],"dice":[]}; if(!S.misc) S.misc={spares:["electronics"]}; ["sold","found"].forEach(function(store){ for(var i=S[store].length-1;i>=0;i--) if(!S[store][i].name || !G.items[S[store][i].name] || in_arr(S[store][i].name,["cxjar","emotionjar"]) || G.items[S[store][i].name].cash) S[store].splice(i,1); }); for(var i=0;i=2) parties[newparty[0]]=newparty; if(newparty.length<2) { if(newparty.length) { var player=players[name_to_id[newparty[0]]]; if(!player) { console.log("#X SHOULD'VE FOUND2 "+newparty[0]); } else player.party=null; } } else { newparty.forEach(function(player_name){ var player=players[name_to_id[player_name]]; if(!player) { console.log("#X SHOULD'VE FOUND3 "+newparty[0]); return; } player.party=newparty[0]; }); } // Moved the socket routine to the end, after all party changes are made [10/07/18] oldparty.forEach(function(player_name){ var player=players[name_to_id[player_name]]; if(!player || player.name==leaver.name) return; newparty=newparty&&parties[newparty[0]]||[]; // During these .socket.emit's, "disconnect"'s happen, the parties can become empty, and party_to_client fails [21/08/18] player.socket.emit("party_update",{message:leaver.name+" left the party",leave:1,list:newparty.length>=2&&newparty,party:newparty.length>=2&&party_to_client(newparty[0])||{}}); resend(player,"nc+u+cid"); }); } function delete_observer(socket) { var observer=observers[socket.id]; delete observers[socket.id]; delete instances[observer.in].observers[observer.id]; } function send_all_xy(observer,args) { var data={players:[],monsters:[],type:'all',in:observer.in,map:observer.map},instance=instances[observer.in]; for(var id in instance.players) if(!instance.players[id].s.invis && within_xy_range(observer,instance.players[id])) data.players.push(player_to_client(instance.players[id],1)); for(var id in instance.monsters) if(within_xy_range(observer,instance.monsters[id])) data.monsters.push(monster_to_client(instance.monsters[id])); observer.last_ux=observer.x; observer.last_uy=observer.y; if(observer.moving && mode.xyinf) data.xy={x:observer.x,y:observer.y}; if(args && args.raw) return data; observer.socket.emit("entities",data); } function xy_emit(entity,event,data,must) { var x=entity.x,y=entity.y,owners={}; for(var id in instances[entity.in].players) { var player=instances[entity.in].players[id]; if(player.npc) continue; if(player.x-player.vision[0]B.u_boundary) u=true; else if(abs(element.last_u[1]-element.y)>B.u_boundary) u=true; if(u) { element.last_u=[element.x,element.y]; element.u=true; } } function xy_upush_logic(element) // sets .push so new entities in that area are pushed to the user/element { var u=false; if(!element.last_upush) {element.last_upush=[element.x,element.y]; return;} else if(abs(element.last_upush[0]-element.x)>B.u_vision) u=true; else if(abs(element.last_upush[1]-element.y)>B.u_vision) u=true; if(u) { element.push=element.last_upush; element.last_upush=[element.x,element.y]; } } function appengine_call(method,args,on_success,on_error) { var api_path="/api/"+method; if(mode.prevent_external && !in_arr(method,["create_server","update_server","stop_server","start_character","send_mail"])) return; function retry_call() { var t=1600; args.retried=(args.retried||0)+1; args.retries--; if(args.retried>20) t=240000; else if(args.retried>10) t=60000; else if(args.retried>5) t=20000; setTimeout(function(){ appengine_call(method,args,on_success,on_error); },t); // network retries, for "Error: socket hang up" etc. [09/09/16] } if(!args) args={}; if(!on_success) on_success=function(){}; if(args.suffix) { api_path+=args.suffix; delete args.suffix; } data={ 'method':method, 'arguments':JSON.stringify(args), 'server_auth':server_id+"-"+server_auth, 'auth':args.auth }; try{ request.post({url:base_url+api_path,form:data},function(err,response,body){ try{ if(err || !response || response.statusCode!=200) // node.request sends an undefined response when there is an issue ... { console.log("appengine_call - unknown error "+err+" or code: "+(response&&response.statusCode)+" retries: "+args.retries+(new Date())+" on "+api_path); if(method!="log_error") setTimeout(function(err,response,api_path,body){ return function(){ try{ appengine_call("log_error",{type:"appengine_call_error",err:(response&&response.statusCode)+" "+api_path,info:""+body+""+err+"\n"+JSON.stringify(response&&response.headers||{})}); }catch(e) { log_trace("appengine_call's log_error "+api_path,e); } }}(err,response,api_path,body),120000); if(args.retries) retry_call(); else if(on_error) on_error(""+err+" "+(response&&response.statusCode)); } else { ct=JSON.parse(body); if(on_success) on_success.apply(this,[ct]); } }catch(e){ log_trace("appengine_call exception on "+api_path,e); if(args.retries) retry_call(); else if(on_error) on_error(""+err+" "+e+" "+(response&&response.statusCode)); else if(on_success) on_success.apply(this,[{"failed":1,"reason":"callbackfailed"}]); } }); }catch(e){ log_trace("appengine_call on "+api_path,e); if(args.retries) retry_call(); else if(on_error) on_error(""+e+" "+(response&&response.statusCode)); else if(on_success) on_success.apply(this,[{"failed":1,"reason":"callfailed"}]); } if(!args.init) args.init=new Date(); return args; } function discord_call(message) { if(gameplay=="hardcore" || gameplay=="test") return; if(is_sdk) return server_log("Discord: "+message); var url="https://discordapp.com/api/channels/404333059018719233/messages"; if(message.search(" joined Adventure Land")!=-1) url="https://discordapp.com/api/channels/839163123499794481/messages" request( { url:url, headers:{"Authorization":"Bot "+variables.discord_token}, method:"POST", json:{ "content":message, } }, function(err,response,body){ //console.log(response); }); } function server_log(message,important) { if(is_sdk || important) { if(is_sdk) console.log(message); else console.log(message+" ("+new Date()+")"); if(message && (message+"").indexOf("SEVERE")!=-1) { appengine_call("server_event",{ event:"notice", keyword:variables.keyword, id:server_id, message:message, color:"red", },function(result){}); } } } function appengine_log(event,message,color) { if(!color) color="gray"; appengine_call("server_event",{ event:event, keyword:variables.keyword, id:server_id, message:message, color:color, },function(result){}); } function disappearing_text(socket,entity,text,args) { var x=entity.x,y=entity.y-30,d_args={}; if(!args) args={}; // if(!args.size) args.size=16; if(!args.color) args.color=""; if(args.color) d_args.c=args.color; // color if(args.s) d_args.s=args.s; // sound if(args.size) d_args.sz=args.size; if(args.from) d_args.from=args.from; // for d_text + .evade + d_line if(args.xy && args.nv) xy_emit(entity,"disappearing_text",{"message":text,"x":x,"y":y,"id":entity.id,"args":d_args,"nv":1}); else if(args.xy) xy_emit(entity,"disappearing_text",{"message":text,"x":x,"y":y,"id":entity.id,"args":d_args}); else if(args.party) party_emit(args.party,"disappearing_text",{"message":text,"x":x,"y":y,"id":entity.id,"args":d_args},{map:args.map}); else socket.emit("disappearing_text",{"message":text,"x":x,"y":y,"id":entity.id,"args":d_args}); // volatile. } function magiport_someone(pulled,player) { var spot=random_one([[-10,16],[10,16],[10,-16],[-10,-16],[0,-24],[-20,-32],[20,32],[-20,32],[20,-32]]); var spot=safe_xy_nearby(player.map,player.x+spot[0],player.y+spot[1]); if(!spot) return false; pulled.s.magiport={ms:400}; pulled.s.magiport.x=spot.x; pulled.s.magiport.y=spot.y; pulled.s.magiport.f=player.name; pulled.s.magiport.in=player.in; pulled.s.magiport.map=player.map; if(player.party==pulled.party) player.pdps+=2000; resend(pulled,"u+cid") return true; } function exchange(player,name,args) { if(!args) args={}; var socket=player.socket,done=false,total=0,current=0; var table=D.drops[name]; if(is_array(name)) { table=name; name=args.name; } if(name.startsWith("cosmo")) { table=clone(table); table.forEach(function(drop){ if(player.p.acx[drop[2]]) drop[0]/=10**player.p.acx[drop[2]]; }); console.log(table); } table.forEach(function(drop){ total+=drop[0]; }); result=Math.random()*total; table.forEach(function(drop){ if(done) return; current+=drop[0]; if(result<=current) { done=true; if(drop[1]=="gold") { player.gold+=drop[2]; socket.emit("game_log",{message:"Received "+to_pretty_num(drop[2])+" gold",color:"gold"}); if(drop[2]>3000000 && !player.stealth) broadcast("server_message",{"message":player.name+" received "+to_pretty_num(drop[2])+" gold","color":"gold"}); } else if(drop[1]=="shells") { add_shells(player,drop[2],name,true,"override"); } else if(drop[1]=="empty") { socket.emit("game_log","Didn't receive anything"); } else if(drop[1]=="cx" || drop[1]=="cxbundle") { player.p.acx[drop[2]]=(player.p.acx[drop[2]]||0)+1; socket.emit("game_response",{response:"cx_new",acx:player.p.acx,name:drop[2],from:name,bundle:drop[1]=="cxbundle"}); } else if(drop[1]=="open") { exchange(player,drop[2]); } else { var item=create_new_item(drop[1],drop[2]),prop=undefined; if(name=="glitch" && (G.items[item.name].upgrade || G.items[item.name].compound || character_slots.includes(G.items[item.name].type))) item.p="glitched"; if(name=="glitch") args.phrase="Glitched"; if(args.v) item.v=args.v; if(drop[1]=="cxjar" || drop[1]=="emotionjar") item.data=drop[3]; add_item(player,item,{r:1,phrase:args.phrase}); socket.emit("game_log",{message:(args.phrase||"Received")+" "+item_to_phrase(item),color:colors.server_success}); } } }); if(!done) socket.emit("game_log","Didn't receive anything"); } function chest_exchange(chest,name) { var done=false,total=0,current=0; D.drops[name].forEach(function(drop){ total+=drop[0]; }); result=Math.random()*total; D.drops[name].forEach(function(drop){ if(done) return; current+=drop[0]; if(result<=current) { done=true; if(drop[1]=="gold") { chest.gold+=drop[2]; } else if(drop[1]=="shells") { chest.cash+=drop[2]; } else if(drop[1]=="empty"); else if(drop[1]=="open") { chest_exchange(chest,drop[2]); } else { chest.items.push(create_new_item(drop[1])); } } }); } var item_p_ignore={"grace":true,"giveaway":true,"gf":true,"price":true,"b":true,"rid":true,"list":true,"o":true,"oo":true,"src":true},item_trade_p_ignore={"grace":true,"o":true,"oo":true,"src":true}; // DOC // .name -> key from G.items // .level -> 9 // .stat_type -> "str" // .p -> property // .q -> quantity // .m -> merchant luck // .v -> pvp // .l -> lock state // .b -> block state // .r -> rented // .skin -> .skin override // .charges // .data -> free data // .expires -> ""+(new Date()) // .gift -> gift item // .acl -> account locked // UPGRADE // .p -> upgrade state #CLASH // TRADE // .rid // .b -> buy order #CLASH // .r -> for rent // .giveaway // .list -> the list for .giveaway function cache_item(current,trade,override) { if(!current) return null; var item={}; if(trade) { for(var p in current) if(!item_trade_p_ignore[p]) item[p]=current[p]; if(!item.giveaway) delete item.list; } else { for(var p in current) if(!item_p_ignore[p]) item[p]=current[p]; } if(override) for(var p in override) item[p]=override[p]; return item; } function get_trade_slots(player) { if(player.p.stand) { var num=16,slots=[]; if(player.type=="merchant" && player.level>=80) num=30; else if(player.type=="merchant" && (player.level>=70 || player.p.stand=="cstand")) num=24; for(var i=1;i<=num;i++) slots.push("trade"+i); return slots; } else if(player.p.trades) return ["trade1","trade2","trade3","trade4"]; return []; } function reslot_player(player) { trade_slots.forEach(function(slot){try{ delete player.cslots[slot]; }catch(e){}}) get_trade_slots(player).forEach(function(slot){ player.cslots[slot]=cache_item(player.slots[slot],1); }); } function cache_player_items(player) { // console.log("Cached "+player.name+"'s items"); if(player.slots && player.slots.ring1 && player.slots.ring1.name=="tristone" && player.slots.ring1.level>=4) player.slots.ring1.name="darktristone"; if(player.slots && player.slots.ring2 && player.slots.ring2.name=="tristone" && player.slots.ring2.level>=4) player.slots.ring2.name="darktristone"; player.cslots={}; player.citems=[]; character_slots.forEach(function(slot){ player.cslots[slot]=cache_item(player.slots[slot]); }); for(var i=0;i42) player.user[pack].length=42; } } function init_player(player) { var class_def=G.classes[player.type]; player.citems=[]; if(player.slots.mainhand && player.slots.offhand && G.classes[player.type].doublehand[G.items[player.slots.mainhand.name].wtype||G.items[player.slots.mainhand.name].type] || player.slots.mainhand && !G.classes[player.type].mainhand[G.items[player.slots.mainhand.name].wtype||G.items[player.slots.mainhand.name].type] && !G.classes[player.type].doublehand[G.items[player.slots.mainhand.name].wtype||G.items[player.slots.mainhand.name].type]) { add_item(player,player.slots.mainhand,{announce:false}); player.slots.mainhand=null; } if(player.slots.offhand && !G.classes[player.type].offhand[G.items[player.slots.offhand.name].wtype||G.items[player.slots.offhand.name].type]) { add_item(player,player.slots.offhand,{announce:false}); player.slots.offhand=null; } for(var i=0;i60) delete player.items[i].v; if(player.items[i].expires) player.items[i].expires=new Date(player.items[i].expires); if(!Object.keys(player.q||{}).length && player.items[i].name=="placeholder") player.items[i]=null; } check_slots.forEach(function(slot){ if(player.slots[slot] && player.slots[slot].expires) player.slots[slot].expires=new Date(player.slots[slot].expires); }); if(player.p.item_num===undefined) player.p.item_num=parseInt(Math.random()*42) for(var s in player.s||{}) { if(player.s[s] && player.s[s].last) player.s[s].last=new Date(player.s[s].last); } for(var dt in player.p.dt) { if(!is_string(player.p.dt[dt])) continue; player.p.dt[dt]=new Date(player.p.dt[dt]); } for(var id in G.skills) { if(G.skills[id]['class'] && G.skills[id]['class'].includes(player.type) && G.skills[id].persistent) { player.last[id]=player.p.dt[id]||(new Date()); if((G.skills[id].cooldown||G.skills[id].reuse_cooldown)>mssince(player.last[id])) player.hitchhikers.push(["eval",{code:"skill_timeout('"+id+"',"+((G.skills[id].cooldown||G.skills[id].reuse_cooldown)-mssince(player.last[id]))+")"}]); } } if(!player.skin || !T[player.skin]) player.skin=class_def.looks[0][0],player.cx=clone(class_def.looks[0][1]); prune_cx(player.cx||{}); if(!player.p.acx) { player.p.acx={}; player.p.xcx=[]; } if(!player.p.emx) player.p.emx={}; if(!player.p.ap) player.p.ap={}; // achievement progress if(!player.p.achievements) player.p.achievements={}; if(player.p.mute) player.mute=true; if(player.p.role=="gm") player.gm=true; if(player.p.role) player.role=player.p.role; if(events.holidayseason && !player.p.firstbuff) add_condition(player,"holidayspirit"),player.p.firstbuff=true; if(!player.p.ugrace || player.p.ugrace.length!=15) player.p.ugrace=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; // first digit and some of the last aren't used [01/10/17] if(!player.p.cgrace || player.p.cgrace.length!=15) player.p.cgrace=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; if(!player.p.ograce) player.p.ograce=0; if(!player.p.minutes) player.p.minutes=0; if(!player.p.stats) player.p.stats={"monsters":{}}; if(!player.p.stats.exchanges) player.p.stats.exchanges={}; if(!player.p.stats.monsters_diff) player.p.stats.monsters_diff={}; if(is_array(player.cx)) player.cx={}; if(player.items.length<42) player.items.length=42; //if(player.s.monsterhunt && player.s.monsterhunt.sn!=region+" "+server_name) delete player.s.monsterhunt; for(var n in player.s) { if(to_number(player.s[n])) player.s[n]={ms:to_number(player.s[n])}; } player.xyh=[{x:player.x,y:player.y,map:player.map,t:new Date()}]; // xy history player.rid=round(Math.random()*1000); // random number to randomise events } function init_player_exit(player) // these are safety routines, if a bug allows the inventory to grow infinitely, this prevents item duplication possibilities [18/10/18] { for(var id in player.q) player.q[id].stale=true; if(player.items.length>70) player.items.length=70; if(Object.keys(player.slots).length>500) player.slots={}; for(var id in G.skills) { if(G.skills[id]['class'] && G.skills[id]['class'].includes(player.type) && G.skills[id].persistent && player.last[id]) { player.p.dt[id]=player.last[id]; } } } function push_xyh(player,x,y) { if(!player.xyh) return; var last=player.xyh[player.xyh.length-1],current={x:x,y:y,map:player.map,t:new Date()}; if(mssince(last.t)>50 || simple_distance(last,current)>1) player.xyh.push(current); if(player.xyh.length>100) player.xyh.shift(); } function item_name(item) { var def=G.items[item.name],name=def.name; if(item.level) name+=" +"+item.level; return name; } function startswith_an(name) { if(in_arr(name.toLowerCase()[0],["a","e","o","u","i"])) return true; return false; } function item_to_phrase(item) { if(item.q && item.q>1) return G.items[item.name].name+" [x"+item.q+"]"; var prefix=""; if(startswith_an(G.items[item.name].name)) prefix="an " else prefix="a "; if(item.p) prefix+=item.p.toTitleCase()+" "; if(item.level) return prefix+G.items[item.name].name+" +"+item.level; return prefix+G.items[item.name].name; } function killed_message(type) { if(G.monsters[type].prefix==="the") return "killed the "+G.monsters[type].name; else if(G.monsters[type].prefix==="") return "killed "+G.monsters[type].name; else if(startswith_an(G.monsters[type].name)) return "killed an "+G.monsters[type].name; else return "killed a "+G.monsters[type].name; } function is_xy_safe_old(map,x,y) { var safe=true; if(smap_data[map]==-1) return true; [[0,0],[0,smap_step+1],[0,-smap_step-1],[smap_step+1,0],[-smap_step-1,0],[0,smap_step/2],[smap_step/2,0],[0,-smap_step/2],[-smap_step/2,0]].forEach(function(m){ var data=smap_data[map][phash(x+m[0],y+m[1])]; if(data || data===undefined) safe=false; }); return safe; } function is_xy_safe(map,x,y) { var safe=true; if(smap_data[map]==-1) return true; // x=parseInt(x)-(parseInt(x)%smap_step); // y=parseInt(y)-(parseInt(y)%smap_step); [[0,0],[0,1],[0,-1],[1,0],[-1,0],[1,1],[-1,1],[1,-1],[-1,-1]].forEach(function(m){ // ,[2,2],[-2,-2],[2,-2],[-2,2] var data=smap_data[map][phash(x+m[0]*smap_step,y+m[1]*smap_step)]; if(data || data===undefined) safe=false; }); return safe; } function safe_xy_nearby(map,x,y) { var point=false; // if(is_xy_safe(map,x,y)) return {x:x,y:y}; x=smap_round(x); y=smap_round(y); // [1,0],[0,1],[1,1],[-1,1],[1,-1],[-1,0],[0,-1],[-1,-1],[2,0],[0,2],[2,2],[-2,-2],[-2,0],[0,-2] [[0,0],[1,0],[0,1],[-1,0],[0,-1],[2,0],[0,2],[-2,0],[0,-2],[3,0],[0,3],[-3,0],[0,-3],].forEach(function(m){ if(!point && is_xy_safe(map,x+m[0]*smap_step,y+m[1]*smap_step)) point={x:x+m[0]*smap_step,y:y+m[1]*smap_step}; }); return point; } function smap_round(x) // for blink, port, magiport { var x1=parseInt(x)-(parseInt(x)%smap_step); var x2=x1+smap_step; if(x<0) x2=x1-smap_step; if(abs(x-x1)50000) { smap_data[map]=-1; server_log(map+" is either un-bounded or the spawn point is too close to an edge",1); return; } var current=queue[start++]; [[0,smap_step],[0,-smap_step],[smap_step,0],[-smap_step,0]].forEach(function(m){ if(level==0) { if(can_move({map:map,x:current[0],y:current[1],going_x:current[0]+m[0],going_y:current[1]+m[1]})) push(current[0]+m[0],current[1]+m[1],level); else push(current[0]+m[0],current[1]+m[1],level+1); } else { push(current[0]+m[0],current[1]+m[1],level+1); } }); } if(level==0 && 0) // In the new system, 1 is safe too [01/08/18] { for(var i=0;i<(G.maps[map].data.x_lines||[]).length;i++) { var current=G.maps[map].data.x_lines[i]; push(current[0],current[1],0); push(current[0],current[2],0); for(var j=current[1];j120000) { amap_data[map]={}; server_log(map+" is either un-bounded or the spawn point is too close to an edge [server_bfs2]",1); return; } var current=queue[start++]; [[amap_step,amap_step],[amap_step,-amap_step],[-amap_step,-amap_step],[-amap_step,amap_step],[0,amap_step],[0,-amap_step],[amap_step,0],[-amap_step,0]].forEach(function(m){ if(can_move({map:map,x:current[0],y:current[1],going_x:current[0]+m[0]*xmult,going_y:current[1]+m[1]*xmult,base:base})) push(current[0]+m[0],current[1]+m[1]); }); } for(var hash in visited) { var x=parseInt(hash.split("|")[0]); var y=parseInt(hash.split("|")[1]); var count=0; [[amap_step,amap_step],[amap_step,-amap_step],[-amap_step,-amap_step],[-amap_step,amap_step],[0,amap_step],[0,-amap_step],[amap_step,0],[-amap_step,0]].forEach(function(m){ if(visited[(x+m[0])+"|"+(y+m[1])]) count+=1; }); if(count>=2) { amap_data[map][hash]=1; } } for(var hash in amap_data[map]) { var x=parseInt(hash.split("|")[0]); var y=parseInt(hash.split("|")[1]); var count=0; [[amap_step,amap_step],[amap_step,-amap_step],[-amap_step,-amap_step],[-amap_step,amap_step],[0,amap_step],[0,-amap_step],[amap_step,0],[-amap_step,0]].forEach(function(m){ if(amap_data[map][(x+m[0])+"|"+(y+m[1])]) count+=1; }); amap_data[map][hash]=count; } } function can_amove(map,sx,sy,tx,ty) { if(!amap_data[map][amap_round(tx)+"|"+amap_round(ty)]) return false; if(sy==ty) { var p=tx1200 || total>4000) { server_log(["heap","fast_astar"]); if(theone) { var result=finalise(theone); result.push("bad"); return result; } return null; } var current=heap.removeTop(),dist=point_distance(current.x,current.y,tx,ty),rnd=Math.random()*100; // console.log([current.x,current.y,visited[current.x+"|"+current.y]]); if(dist<28) { good=true; return finalise(current); } if(dist+current.bad-rnd1200 || last>4000) { server_log(["queue","fast_abfs"]); monster.bpath=(monster.bpath||0)+1; if(theone && monster.bpath<20) return finalise(theone); return null; } var current=queue[start++],dist=point_distance(current.x,current.y,tx,ty),rnd=Math.random()*100; if(dist<28) { monster.bpath=0; good=true; return finalise(current); } if(dist+current.bad-rnd4000) socket.calls.shift(); // #TODO: make one operation [27/02/23] if(socket.calls.length && socket.calls[socket.calls.length-1][1]==method && num!=-1) socket.calls[socket.calls.length-1][2]+=num; else socket.calls.push([new Date(),method,num==-1?1:num]); } function reduce_call_cost(socket,num) { if(!socket) socket=1; if(is_number(socket)) num=socket,socket=current_socket; if(socket.socket) socket=socket.socket; // player if(!num) num=1; if(socket.calls.length && socket.calls[socket.calls.length-1][1]==ls_method) { socket.calls[socket.calls.length-1][2]-=num; if(socket.calls[socket.calls.length-1][2]<=0) socket.calls.pop(); } } function get_call_cost(socket) { if(!socket) socket=current_socket; if(socket.socket) socket=socket.socket; // player while(socket.calls.length && mssince(socket.calls[0][0])>4000) socket.calls.shift(); // #TODO: make one operation [27/02/23] var cost=0; socket.calls.forEach(function(c){cost+=c[2];}); return cost; } function set_direction(){} // compatibility function symmetricDecrypt(input, key, checkHmac) { var aesIv = crypto.createDecipheriv('aes-256-ecb', key, ''); aesIv.setAutoPadding(false); aesIv.end(input.slice(0, 16)); var iv = aesIv.read(); var aesData = crypto.createDecipheriv('aes-256-cbc', key, iv); aesData.end(input.slice(16)); var plaintext = aesData.read(); if (checkHmac) { // The last 3 bytes of the IV are a random value, and the remainder are a partial HMAC var remotePartialHmac = iv.slice(0, iv.length - 3); var random = iv.slice(iv.length - 3, iv.length); var hmac = crypto.createHmac("sha1", key.slice(0, 16)); hmac.update(random); hmac.update(plaintext); if (!remotePartialHmac.equals(hmac.digest().slice(0, remotePartialHmac.length))) { throw new Error("Received invalid HMAC from remote host."); } } return plaintext; }; function parseAppTicket(ticket) { // https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl // console.log(ticket); if (!ByteBuffer.isByteBuffer(ticket)) { ticket = ByteBuffer.wrap(ticket, ByteBuffer.LITTLE_ENDIAN); } let details = {}; try { let initialLength = ticket.readUint32(); // console.log(initialLength); if (initialLength == 20) { // This is a full appticket, with a GC token and session header (in addition to ownership ticket) details.authTicket = ticket.slice(ticket.offset - 4, ticket.offset - 4 + 52).toBuffer(); // this is the part that's passed back to Steam for validation details.gcToken = ticket.readUint64().toString(); //details.steamID = new SteamID(ticket.readUint64().toString()); ticket.skip(8); // the SteamID gets read later on details.tokenGenerated = new Date(ticket.readUint32() * 1000); if (ticket.readUint32() != 24) { // SESSIONHEADER should be 24 bytes. return null; } ticket.skip(8); // unknown 1 and unknown 2 details.sessionExternalIP = Helpers.ipIntToString(ticket.readUint32()); ticket.skip(4); // filler details.clientConnectionTime = ticket.readUint32(); // time the client has been connected to Steam in ms details.clientConnectionCount = ticket.readUint32(); // how many servers the client has connected to if (ticket.readUint32() + ticket.offset != ticket.limit) { // OWNERSHIPSECTIONWITHSIGNATURE sectlength return null; } } else { ticket.skip(-4); } // Start reading the ownership ticket let ownershipTicketOffset = ticket.offset; let ownershipTicketLength = ticket.readUint32(); // including itself, for some reason if (ownershipTicketOffset + ownershipTicketLength != ticket.limit && ownershipTicketOffset + ownershipTicketLength + 128 != ticket.limit) { return null; } let i, j, dlc; details.version = ticket.readUint32(); details.steamID = ticket.readUint64().toString(); details.appID = ticket.readUint32(); details.ownershipTicketExternalIP = ticket.readUint32(); details.ownershipTicketInternalIP = ticket.readUint32(); // Helpers.ipIntToString( details.ownershipFlags = ticket.readUint32(); details.ownershipTicketGenerated = new Date(ticket.readUint32() * 1000); details.ownershipTicketExpires = new Date(ticket.readUint32() * 1000); details.licenses = []; // return details; let licenseCount = ticket.readUint16(); for (i = 0; i < licenseCount; i++) { details.licenses.push(ticket.readUint32()); } details.dlc = []; let dlcCount = ticket.readUint16(); for (i = 0; i < dlcCount; i++) { dlc = {}; dlc.appID = ticket.readUint32(); dlc.licenses = []; licenseCount = ticket.readUint16(); for (j = 0; j < licenseCount; j++) { dlc.licenses.push(ticket.readUint32()); } details.dlc.push(dlc); } ticket.readUint16(); // reserved if (ticket.offset + 128 == ticket.limit) { // Has signature details.signature = ticket.slice(ticket.offset, ticket.offset + 128).toBuffer(); } let date = new Date(); details.isExpired = details.ownershipTicketExpires < date; details.hasValidSignature = !!details.signature && SteamCrypto.verifySignature(ticket.slice(ownershipTicketOffset, ownershipTicketOffset + ownershipTicketLength).toBuffer(), details.signature); details.isValid = !details.isExpired && (!details.signature || details.hasValidSignature); } catch (ex) { console.log("parseAppTicket: "+ex); return details; return null; // not a valid ticket } return details; }; var proto={ "nested": {EncryptedAppTicket: { fields: { ticketVersionNo: { type: 'uint32', id: 1 }, crcEncryptedticket: { type: 'uint32', id: 2 }, cbEncrypteduserdata: { type: 'uint32', id: 3 }, cbEncryptedAppownershipticket: { type: 'uint32', id: 4 }, encryptedTicket: { type: 'bytes', id: 5 } } }}}; var proto_root=protobuf.Root.fromJSON(proto); var EncryptedAppTicket=proto_root.lookupType("EncryptedAppTicket"); function getClientIp(req) { //https://github.com/pbojinov/request-ip/blob/master/index.js // NOTE: Back in the day, we had a system called ipass - for some weird and unexplainable reason, no Node server, mainly Socket.io, can track the current IP of a client // So the clients needed to ping the game every 40 seconds, at a secondary, http-only handler just to get their IP - and, again, for some extremely weird reason, Node servers, don't let you just easily disconnect a client // Rather than auto Keep-Alive, so it had a hacky disconnect routine - all in all, decided to move this system to App Engine, while the SSL refactoring was happening [17/11/18] // the ipAddress we return var ipAddress; // workaround to get real client IP // most likely because our app will be behind a [reverse] proxy or load balancer var clientIp = req.headers['x-client-ip']; var forwardedForAlt = req.headers['x-forwarded-for']; var realIp = req.headers['x-real-ip']; // more obsure ones below var clusterClientIp = req.headers['x-cluster-client-ip']; var forwardedAlt = req.headers['x-forwarded']; var forwardedFor = req.headers['forwarded-for']; var forwarded = req.headers['forwarded']; // remote address check var reqConnectionRemoteAddress = req.connection ? req.connection.remoteAddress : null; var reqSocketRemoteAddress = req.socket ? req.socket.remoteAddress : null; var reqConnectionSocketRemoteAddress = (req.connection && req.connection.socket) ? req.connection.socket.remoteAddress : null; var reqInfoRemoteAddress = req.info ? req.info.remoteAddress : null; // x-client-ip if (clientIp) { ipAddress = clientIp; } // x-forwarded-for // (typically when your node app is behind a load-balancer (eg. AWS ELB) or proxy) else if (forwardedForAlt) { // x-forwarded-for may return multiple IP addresses in the format: // "client IP, proxy 1 IP, proxy 2 IP" // Therefore, the right-most IP address is the IP address of the most recent proxy // and the left-most IP address is the IP address of the originating client. // source: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html var forwardedIps = forwardedForAlt.split(','); ipAddress = forwardedIps[0]; } // x-real-ip // (default nginx proxy/fcgi) else if (realIp) { // alternative to x-forwarded-for, used by some proxies ipAddress = realIp; } // x-cluster-client-ip // (Rackspace LB and Riverbed's Stingray) // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address // https://splash.riverbed.com/docs/DOC-1926 else if (clusterClientIp) { ipAddress = clusterClientIp; } // x-forwarded else if (forwardedAlt) { ipAddress = forwardedAlt; } // forwarded-for else if (forwardedFor) { ipAddress = forwardedFor; } // forwarded else if (forwarded) { ipAddress = forwarded; } // remote address checks else if (reqConnectionRemoteAddress) { ipAddress = reqConnectionRemoteAddress; } else if (reqSocketRemoteAddress) { ipAddress = reqSocketRemoteAddress } else if (reqConnectionSocketRemoteAddress) { ipAddress = reqConnectionSocketRemoteAddress } else if (reqInfoRemoteAddress) { ipAddress = reqInfoRemoteAddress } // return null if we cannot find an address else { ipAddress = null; } return ipAddress; } function deduct_gender(player) { if(player.cx && player.cx.head && player.cx.head[0]=="f") return "female"; return "male"; } function safe_search(obj,phrase) { obj=JSON.parse(safe_stringify(obj)); for(l1 in obj) { var c1=obj[l1]; if(c1 && is_string(c1) && c1.indexOf(phrase)!=-1 || l1.indexOf(phrase)!=-1) console.log(l1+" "); if(!is_object(c1)) continue; for(l2 in c1) { var c2=c1[l2]; if(c2 && is_string(c2) && c2.indexOf(phrase)!=-1 || l2.indexOf(phrase)!=-1) console.log(l1+" "+l2); if(!is_object(c2)) continue; for(l3 in c2) { var c3=c2[l3]; if(c3 && is_string(c3) && c3.indexOf(phrase)!=-1 || l3.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3); if(!is_object(c3)) continue; for(l4 in c3) { var c4=c3[l4]; if(c4 && is_string(c4) && c4.indexOf(phrase)!=-1 || l4.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4); if(!is_object(c4)) continue; for(l5 in c4) { var c5=c4[l5]; if(c5 && is_string(c5) && c5.indexOf(phrase)!=-1 || l5.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5); if(!is_object(c5)) continue; for(l6 in c5) { var c6=c5[l6]; if(c6 && is_string(c6) && c6.indexOf(phrase)!=-1 || l6.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5+" "+l6); if(!is_object(c6)) continue; for(l7 in c6) { var c7=c6[l7]; if(c7 && is_string(c7) && c7.indexOf(phrase)!=-1 || l7.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5+" "+l6+" "+l7); if(!is_object(c7)) continue; for(l8 in c7) { var c8=c7[l8]; if(c8 && is_string(c8) && c8.indexOf(phrase)!=-1 || l8.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5+" "+l6+" "+l7+" "+l8); if(!is_object(c8)) continue; for(l9 in c8) { var c9=c8[l9]; if(c9 && is_string(c9) && c9.indexOf(phrase)!=-1 || l9.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5+" "+l6+" "+l7+" "+l8+" "+l9); if(!is_object(c9)) continue; for(l10 in c9) { var c10=c9[l10]; if(c10 && is_string(c10) && c10.indexOf(phrase)!=-1 || l10.indexOf(phrase)!=-1) console.log(l1+" "+l2+" "+l3+" "+l4+" "+l5+" "+l6+" "+l7+" "+l8+" "+l9+" "+l10); if(!is_object(c10)) continue; } } } } } } } } } } } function strip_string(str) { var regexSymbolWithCombiningMarks = /([\0-\u02FF\u0370-\u1AAF\u1B00-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uE000-\uFE1F\uFE30-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])([\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]+)/g; var regexLineBreakCombiningMarks = /[\0-\x08\x0E-\x1F\x7F-\x84\x86-\x9F\u0300-\u034E\u0350-\u035B\u0363-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u061C\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D4-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFB-\u1DFF\u200C\u200E\u200F\u202A-\u202E\u2066-\u206F\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3035\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFFF9-\uFFFB]|\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD804[\uDC00-\uDC02\uDC38-\uDC46\uDC7F-\uDC82\uDCB0-\uDCBA\uDD00-\uDD02\uDD27-\uDD34\uDD73\uDD80-\uDD82\uDDB3-\uDDC0\uDDCA-\uDDCC\uDE2C-\uDE37\uDE3E\uDEDF-\uDEEA\uDF00-\uDF03\uDF3C\uDF3E-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC35-\uDC46\uDCB0-\uDCC3\uDDAF-\uDDB5\uDDB8-\uDDC0\uDDDC\uDDDD\uDE30-\uDE40\uDEAB-\uDEB7]|\uD807[\uDC2F-\uDC36\uDC38-\uDC3F\uDC92-\uDCA7\uDCA9-\uDCB6]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF51-\uDF7E\uDF8F-\uDF92]|\uD82F[\uDC9D\uDC9E\uDCA0-\uDCA3]|\uD834[\uDD65-\uDD69\uDD6D-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD4A]|\uDB40[\uDC01\uDC20-\uDC7F\uDD00-\uDDEF]/g; // https://github.com/mathiasbynens/strip-combining-marks/blob/master/strip-combining-marks.js return str.replace(regexLineBreakCombiningMarks, '').replace(regexSymbolWithCombiningMarks, '$1').replace(/[^\p{L}\p{N}\p{S}\p{P}\p{Z}]/gu, ''); // https://stackoverflow.com/a/63464318/914546 } // #HARDCORE var hardcore_done=false; function hardcore_loop() { if(hardcore_done) return; var m=0; for(var id in players) if(players[id].level>m) E.rewards.leader=players[id].name,m=players[id].level; if(E.minutes) E.minutes-=1; else { hardcore_done=true; send_hardcore_rewards(); shutdown_routine(); } broadcast("hardcore_info",{E:E}); } // #ACHIEVEMENTS function send_hardcore_rewards() { rd=[ ["leader","Leadership",{"name":"xbox",q:1}], ["item8","First +8 Item",{"name":"armorbox",q:10}], ["item9","First +9 Item",{"name":"weaponbox",q:10}], ["item10","First +X Item",{"name":"armorbox",q:100}], ["item11","First +Y Item",{"name":"armorbox",q:100}], ["item12","First +Z Item",{"name":"scroll3",q:1}], ["accessory5","First +V Accessory",{"name":"armorbox",q:50}], ["accessory6","First +S Accessory",{"name":"cscroll3",q:1}], ["first_warrior_70","First Warrior to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_paladin_70","First Paladin to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_priest_70","First Priest to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_mage_70","First Mage to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_rogue_70","First Rogue to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_ranger_70","First Ranger to Level 70",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_franky","First Franky Kill",{"name":"brownegg",q:1}], ["first_stompy","First Stompy Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_ent","First Ent Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_wabbit","First Wabbit Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_fvampire","First Ms.Vampire Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_mvampire","First Mr.Vampire Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_skeletor","First Skeletor Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ["first_goo","First Goo Kill",{"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1}], ]; rd.forEach(function(r){ if(E.rewards[r[0]]) appengine_call("send_mail",{subject:"HARDCORE: Congratulations!",message:"Reward for "+r[1],type:"system",rid:randomStr(50),to:E.rewards[r[0]],item:JSON.stringify(r[2]),retries:8}); }); for(var id in players) save_player(players[id]); var sent={}; for(var id in P) { var p=P[id]; if(p.auth_id && p.level>=60 && !sent[p.auth_id]) { sent[p.auth_id]=true; appengine_call("send_mail",{subject:"HARDCORE: Congratulations!",message:"Reward for Participation",type:"system",rid:randomStr(50),to:p.name,item:JSON.stringify({"name":(Math.random()<0.1&&"gift0"||"gift1"),q:1})}); } } } function add_achievement(player,name) { player.p.achievements[name]=(player.p.achievements[name]||0)+1; player.socket.emit("achievement_success",{name:name}); } function item_achievement_increment(player,item,ach,amount) { if(!G.achievements[ach] || !item) return; if(item.ach!=ach || !item.acc) item.acc=0; var needed=G.achievements[ach].count,old=item.acc,rr=G.achievements[ach].rr||1; item.ach=ach; item.acc+=(amount||1); if(item.acc=70 && !E.rewards['first_'+c+'_70'] && E.minutes) E.rewards['first_'+c+'_70']=player.name,announce=true; }); if(announce) broadcast("hardcore_info",{E:E,achiever:player.name}); } else { } }catch(e){console.log("#A: "+e);}} function achievement_logic_compound_success(player,item) {try{ if(gameplay=="hardcore") { var announce=false; if(item.level==5 && !E.rewards.accessory5 && E.minutes) E.rewards.accessory5=player.name,announce=true; if(item.level==6 && !E.rewards.accessory6 && E.minutes) E.rewards.accessory6=player.name,announce=true; if(announce) broadcast("hardcore_info",{E:E,achiever:player.name}); } else { } }catch(e){console.log("#A: "+e);}} function achievement_logic_upgrade_success(player,item) {try{ if(gameplay=="hardcore") { var announce=false; if(item.level==8 && !E.rewards.item8 && E.minutes) E.rewards.item8=player.name,announce=true; if(item.level==9 && !E.rewards.item9 && E.minutes) E.rewards.item9=player.name,announce=true; if(item.level==10 && !E.rewards.item10 && E.minutes) E.rewards.item10=player.name,announce=true; if(item.level==11 && !E.rewards.item11 && E.minutes) E.rewards.item11=player.name,announce=true; if(item.level==12 && !E.rewards.item12 && E.minutes) E.rewards.item12=player.name,announce=true; if(announce) { broadcast("hardcore_info",{E:E,achiever:player.name}); } } else { } }catch(e){console.log("#A: "+e);}} function achievement_logic_monster_last_hit(player,monster) {try{ if(server.shutdown) return; if(player.slots.mainhand) delete player.slots.mainhand.ach; }catch(e){console.log("#A: "+e);}} function achievement_logic_monster_hit(monster,player,attack) {try{ if(monster.type=="stompy" && !monster.target) item_achievement_increment(player,player.slots.helmet,"stomped"); else if(monster.type!="stompy" && player.slots.helmet) delete player.slots.helmet.ach; if(monster.type=="goo" || monster.type=="cgoo") item_achievement_increment(player,player.slots.pants,"gooped",attack); }catch(e){console.log("#A: "+e);}} function achievement_logic_burn_last_hit(player) {try{ if(server.shutdown) return; item_achievement_increment(player,player.slots.mainhand,"firehazard"); }catch(e){console.log("#A: "+e);}}