From 8099f31e4ca0331471787437edb1910c61d9138b Mon Sep 17 00:00:00 2001 From: Telokis Date: Sat, 28 Oct 2023 15:21:37 +0200 Subject: [PATCH] Autofix all files for the game server --- node/.eslintrc | 2 +- node/precompute.js | 60 +- node/server.js | 22830 +++++++++++++++++----------- node/server_functions.js | 8469 ++++++----- node/server_worker.js | 45 +- node/test.js | 260 +- useful/template.live_variables.js | 36 +- useful/template.variables.js | 42 +- 8 files changed, 18788 insertions(+), 12956 deletions(-) diff --git a/node/.eslintrc b/node/.eslintrc index 9ae15de..a806da0 100644 --- a/node/.eslintrc +++ b/node/.eslintrc @@ -10,6 +10,7 @@ "rules": { "no-constant-condition": "off", "no-dupe-else-if": "off", + "indent": "off", "no-empty": "warn", "no-inner-declarations": "warn", "no-unreachable": "warn", @@ -17,7 +18,6 @@ "no-unused-vars": "warn", "no-redeclare": "warn", "curly": "error", - "indent": ["error", 2], "prettier/prettier": "error", "one-var": ["error", "never"], "no-sequences": "error" diff --git a/node/precompute.js b/node/precompute.js index d5d258a..13b73ec 100644 --- a/node/precompute.js +++ b/node/precompute.js @@ -1,27 +1,35 @@ -var is_game=0,is_server=0,is_code=0; -var variables=require('./variables'); -var is_sdk=variables.is_sdk; -var fs=require('fs'); -var precomputed=null; -eval(""+fs.readFileSync(variables.cfunctions_path)); -eval(""+fs.readFileSync(variables.functions_path)); -var mode={},server_id='precompute',server_auth="123456",base_url=variables.base_url; +var is_game = 0; +var is_server = 0; +var is_code = 0; +var variables = require("./variables"); +var is_sdk = variables.is_sdk; +var fs = require("fs"); +var precomputed = null; +eval("" + fs.readFileSync(variables.cfunctions_path)); +eval("" + fs.readFileSync(variables.functions_path)); +var mode = {}; +var server_id = "precompute"; +var server_auth = "123456"; +var base_url = variables.base_url; -appengine_call("reload_server",{keyword:variables.keyword},function(result){ - if(result.failed) return; - G=result.game; - var start=new Date(); - for(var mname in G.maps) - { - if(G.maps[mname].ignore) continue; - var cstart=new Date(); - server_bfs(mname); - console.log("Precomputed: "+mname+" in "+mssince(cstart)+"ms"); - } - console.log("Done: "+mssince(start)+"ms"); - var precomputed={}; - precomputed.version=G.version; - precomputed.amap_data=amap_data; - precomputed.smap_data=smap_data; - fs.writeFileSync(variables.data_path,'var precomputed='+JSON.stringify(precomputed)+';'); -}); \ No newline at end of file +appengine_call("reload_server", { keyword: variables.keyword }, function (result) { + if (result.failed) { + return; + } + G = result.game; + var start = new Date(); + for (var mname in G.maps) { + if (G.maps[mname].ignore) { + continue; + } + var cstart = new Date(); + server_bfs(mname); + console.log("Precomputed: " + mname + " in " + mssince(cstart) + "ms"); + } + console.log("Done: " + mssince(start) + "ms"); + var precomputed = {}; + precomputed.version = G.version; + precomputed.amap_data = amap_data; + precomputed.smap_data = smap_data; + fs.writeFileSync(variables.data_path, "var precomputed=" + JSON.stringify(precomputed) + ";"); +}); diff --git a/node/server.js b/node/server.js index c594c53..28bf769 100644 --- a/node/server.js +++ b/node/server.js @@ -1,9444 +1,13782 @@ -var is_game=0,is_server=1,is_code=0,is_pvp=false; -var server={ - started:false, - live:false, - last_update:false, - shutdown:false, // shutdown start - stopped:false, // shutdown end - s:{}, +var is_game = 0; +var is_server = 1; +var is_code = 0; +var is_pvp = false; +var server = { + started: false, + live: false, + last_update: false, + shutdown: false, // shutdown start + stopped: false, // shutdown end + s: {}, }; -var variables=require('./variables'); -var is_sdk=variables.is_sdk; -var app=require('http').createServer(http_handler); +var variables = require("./variables"); +var is_sdk = variables.is_sdk; +var app = require("http").createServer(http_handler); //var io=require('socket.io')(app,{pingInterval:2400,pingTimeout:6000}); -var io=require('socket.io')(app,{pingInterval:4000,pingTimeout:12000,cors:{ - origin:false, - methods:"GET,HEAD,PUT,PATCH,POST,DELETE", - credentials:true, - }}); // default is 25000 to 60000 -var fs=require('fs'),url=require('url'); -var {Worker,SHARE_ENV}=require('worker_threads'),workers=[],wlast=0; -eval(""+fs.readFileSync(variables.cfunctions_path)); -eval(""+fs.readFileSync(variables.functions_path)); -eval(""+fs.readFileSync(variables.data_path)); -var base_url=variables.base_url,server_id="1",server_auth="123456",server_name=""; -var players={}; -var dc_players={}; -var sockets={}; -var observers={}; -var total_monsters=0; -var max_players=96; -var chests={}; -var projectiles={}; -var name_to_id={},id_to_id={}; -var invitations={},challenges={},magiportations={},requests={},parties={},frequests={},trequests={}; -var total_moves=1,total_players=0,unique_players=0; -var observer_map="winterland",observer_x=0,observer_y=0; -var merchant_map="main",merchant_y=-50,merchant_x=0,total_merchants=0; -var csold=[],cfound=[]; // cache of S.sold + S.found -var monster_c={}; // monster counts -var pwns=[],pend=0; -var tavern={}; -var dbase={h:8,v:7,vn:2}; // default-base -var G={},D={},P={}; // the game -var S={}; // server data -var T={}; // cosmetics -var E={ - schedule:{ - time_offset:0, - dailies:[13,20], - nightlies:[23], - night:false, - }, +var io = require("socket.io")(app, { + pingInterval: 4000, + pingTimeout: 12000, + cors: { + origin: false, + methods: "GET,HEAD,PUT,PATCH,POST,DELETE", + credentials: true, + }, +}); // default is 25000 to 60000 +var fs = require("fs"); +var url = require("url"); +var { Worker, SHARE_ENV } = require("worker_threads"); +var workers = []; +var wlast = 0; +eval("" + fs.readFileSync(variables.cfunctions_path)); +eval("" + fs.readFileSync(variables.functions_path)); +eval("" + fs.readFileSync(variables.data_path)); +var base_url = variables.base_url; +var server_id = "1"; +var server_auth = "123456"; +var server_name = ""; +var players = {}; +var dc_players = {}; +var sockets = {}; +var observers = {}; +var total_monsters = 0; +var max_players = 96; +var chests = {}; +var projectiles = {}; +var name_to_id = {}; +var id_to_id = {}; +var invitations = {}; +var challenges = {}; +var magiportations = {}; +var requests = {}; +var parties = {}; +var frequests = {}; +var trequests = {}; +var total_moves = 1; +var total_players = 0; +var unique_players = 0; +var observer_map = "winterland"; +var observer_x = 0; +var observer_y = 0; +var merchant_map = "main"; +var merchant_y = -50; +var merchant_x = 0; +var total_merchants = 0; +var csold = []; +var cfound = []; // cache of S.sold + S.found +var monster_c = {}; // monster counts +var pwns = []; +var pend = 0; +var tavern = {}; +var dbase = { h: 8, v: 7, vn: 2 }; // default-base +var G = {}; +var D = {}; +var P = {}; // the game +var S = {}; // server data +var T = {}; // cosmetics +var E = { + schedule: { + time_offset: 0, + dailies: [13, 20], + nightlies: [23], + night: false, + }, }; // server event data -var TX=[],TXX={}; // all inter account transactions [07/02/22] -var B={ - "ext_vision":120, // extends the vision by 120 each at all 4 directions -- obsolete now - "u_boundary":70, // when an entity moves 70px in x or y, triggers .u || xy_u_logic, last_u - "u_vision":65, // when an entity moves 65px in x or y, new entities in the new area are sent || xy_upush_logic, last_upush - "vision":[700,500], - "max_vision":1000, // used at the 'attack'/'heal'-'nothtere' logic - "dist":400, - "sell_dist":400, - "door_dist":112, - // game dynamics - "transporter_dist":160, - "rip_time":12, - "hlevel_loss":3, - "heal_multiplier":1, - "arena_limit":1, - "use_pack_golds":true, - "v":true, - "dps_heal_mult":1.8, // originally 1.6 - "dps_tank_mult":0.25, // originally 0.25 - "game_loop_log_edge":60, - "instance_loop_log_edge":120, - "m_outgoing_gmult":0.12, - "start_map":"main", - "free_last_hits":false, - "pause_instances":true, - "global_drops":true, +var TX = []; +var TXX = {}; // all inter account transactions [07/02/22] +var B = { + ext_vision: 120, // extends the vision by 120 each at all 4 directions -- obsolete now + u_boundary: 70, // when an entity moves 70px in x or y, triggers .u || xy_u_logic, last_u + u_vision: 65, // when an entity moves 65px in x or y, new entities in the new area are sent || xy_upush_logic, last_upush + vision: [700, 500], + max_vision: 1000, // used at the 'attack'/'heal'-'nothtere' logic + dist: 400, + sell_dist: 400, + door_dist: 112, + // game dynamics + transporter_dist: 160, + rip_time: 12, + hlevel_loss: 3, + heal_multiplier: 1, + arena_limit: 1, + use_pack_golds: true, + v: true, + dps_heal_mult: 1.8, // originally 1.6 + dps_tank_mult: 0.25, // originally 0.25 + game_loop_log_edge: 60, + instance_loop_log_edge: 120, + m_outgoing_gmult: 0.12, + start_map: "main", + free_last_hits: false, + pause_instances: true, + global_drops: true, }; -var CC={ - "auth":2, - "move":1.5, - "players":12, - "secondhands":16, - "friend":24, - "send_updates":12, - "cruise":10, - "random_look":10, - "equip":3, - "unequip":6, - "tracker":50, - "ccreport":3, +var CC = { + auth: 2, + move: 1.5, + players: 12, + secondhands: 16, + friend: 24, + send_updates: 12, + cruise: 10, + random_look: 10, + equip: 3, + unequip: 6, + tracker: 50, + ccreport: 3, }; -var limits={ - "calls":200, // 4 seconds - "party":9, - "party_max":10, +var limits = { + calls: 200, // 4 seconds + party: 9, + party_max: 10, }; -var W={ // warning flags - "chest":{}, +var W = { + // warning flags + chest: {}, }; -var mode={ - "range_test":0, - "path_checks":1, // when pursuing a player, monsters check each x/y-line, stop pursuit on collision [14/08/16] - //#IDEA: Disable when low performance is detected [14/08/16] - "upush_test":0, // stops monsters to test the "push" logic - "random_attacks":0, // a test mode that makes monsters attack nearby players randomly [11/09/16] - "aggro":1, - "dpvpblock":1, // double-sided pvp blocks - "novi":1, // no vision improvement - added as a patch [12/01/17] - "xyinf":0, //sends xy data for corrections - "log_pvp":1, //sends pvp data to appengine, mainly to see whether people leech xp from sub-characters [03/02/17] - "drop_all":0, - "red_zone":1, - "lcorrection":1, - "enforce_smap":1, - "rbugs":0, - "noxy":0, - "nopush":0, - "freeze_latest":0, // to test latest_calls numbers - "log_all":0, // logs all incoming websocket calls - "friendly_fire":1, - "fast_mlevels":0, - "map_respawns":0, // respawn at maps or where they define [05/12/18] - "low49_20xglobal":1, // 20x global drops for <=49 level players [03/02/19] - "low49_200xgoo":1, - "pve_safe_magiports":1, - "instant_monster_attacks":1, // #TODO: Consider dynamically sending target data instantly too - "drm_check":1, - "all_roam":0, - "all_smart":1, - "prevent_external":0, // for "test" / "hardcore" - "pvp_level_gap":0, // have to be within 10 level to attack +var mode = { + range_test: 0, + path_checks: 1, // when pursuing a player, monsters check each x/y-line, stop pursuit on collision [14/08/16] + //#IDEA: Disable when low performance is detected [14/08/16] + upush_test: 0, // stops monsters to test the "push" logic + random_attacks: 0, // a test mode that makes monsters attack nearby players randomly [11/09/16] + aggro: 1, + dpvpblock: 1, // double-sided pvp blocks + novi: 1, // no vision improvement - added as a patch [12/01/17] + xyinf: 0, //sends xy data for corrections + log_pvp: 1, //sends pvp data to appengine, mainly to see whether people leech xp from sub-characters [03/02/17] + drop_all: 0, + red_zone: 1, + lcorrection: 1, + enforce_smap: 1, + rbugs: 0, + noxy: 0, + nopush: 0, + freeze_latest: 0, // to test latest_calls numbers + log_all: 0, // logs all incoming websocket calls + friendly_fire: 1, + fast_mlevels: 0, + map_respawns: 0, // respawn at maps or where they define [05/12/18] + low49_20xglobal: 1, // 20x global drops for <=49 level players [03/02/19] + low49_200xgoo: 1, + pve_safe_magiports: 1, + instant_monster_attacks: 1, // #TODO: Consider dynamically sending target data instantly too + drm_check: 1, + all_roam: 0, + all_smart: 1, + prevent_external: 0, // for "test" / "hardcore" + pvp_level_gap: 0, // have to be within 10 level to attack }; -var events={ - // SEASONS - "holidayseason":false, - "lunarnewyear":false, - "valentines":false, - "pinkgoo":0, // every N minutes - 60 - "snowman":20*60, // 1200 normally - 60 - at sprocess_game_data - "egghunt":0, // every N minutes - 60 - // RANDOM - "halloween":true, - "goblin":false, - "goldenbat":160000, - "cutebee":960000, - "hide_and_seek":0, - // DAILIES - "goobrawl":false, - "crabxx":false, - "abtesting":false, - // NIGHTLIES - "icegolem":false, - "franky":false, +var events = { + // SEASONS + holidayseason: false, + lunarnewyear: false, + valentines: false, + pinkgoo: 0, // every N minutes - 60 + snowman: 20 * 60, // 1200 normally - 60 - at sprocess_game_data + egghunt: 0, // every N minutes - 60 + // RANDOM + halloween: true, + goblin: false, + goldenbat: 160000, + cutebee: 960000, + hide_and_seek: 0, + // DAILIES + goobrawl: false, + crabxx: false, + abtesting: false, + // NIGHTLIES + icegolem: false, + franky: false, }; -var dailies=["crabxx","goobrawl","abtesting"]; shuffle(dailies); -var nightlies=["icegolem","franky"]; shuffle(nightlies); -if(events.holidayseason) events.snowman=60; -if(events.valentines) events.pinkgoo=60; -TIMEO={ - "EU":1, - "US":-5, - "ASIA":7, +var dailies = ["crabxx", "goobrawl", "abtesting"]; +shuffle(dailies); +var nightlies = ["icegolem", "franky"]; +shuffle(nightlies); +if (events.holidayseason) { + events.snowman = 60; } -var perfc={ - "game_loop":{}, - "instance_loop":{}, - "instance_delay":{}, - "cps":0, - "sxyu":0, - "game_loops":0, - "instance_loops":0, - "roams":0, - "roam_ops":0, +if (events.valentines) { + events.pinkgoo = 60; } -var instances={}; +TIMEO = { + EU: 1, + US: -5, + ASIA: 7, +}; +var perfc = { + game_loop: {}, + instance_loop: {}, + instance_delay: {}, + cps: 0, + sxyu: 0, + game_loops: 0, + instance_loops: 0, + roams: 0, + roam_ops: 0, +}; +var instances = {}; // at first there were no instances, monsters were global // instances improve performance and allow things like dynamic events, dungeons -var luckm=1,xpm=1,goldm=1,buym=1; +var luckm = 1; +var xpm = 1; +var goldm = 1; +var buym = 1; +region = process.argv[process.argv.length - 3]; +server_name = process.argv[process.argv.length - 2]; +port = process.argv[process.argv.length - 1]; -region=process.argv[process.argv.length-3]; -server_name=process.argv[process.argv.length-2]; -port=process.argv[process.argv.length-1]; +E.schedule.time_offset = TIMEO[region]; -E.schedule.time_offset=TIMEO[region]; - -if(server_name=="HARDCORE") -{ - gameplay="hardcore"; - xpm=2188; - // luckm=500; - goldm=12000; - is_pvp=true; - variables.ip_limit=1; - variables.character_limit=1; - B.rip_time=-1; - B.heal_multiplier=0.6; - B.sell_dist=9999999+1000; - events.golden_bat=8000; - B.arena_limit=1; - B.v=false; - B.free_last_hits=true; - mode.prevent_external=1; - mode.pvp_level_gap=1; +if (server_name == "HARDCORE") { + gameplay = "hardcore"; + xpm = 2188; + // luckm=500; + goldm = 12000; + is_pvp = true; + variables.ip_limit = 1; + variables.character_limit = 1; + B.rip_time = -1; + B.heal_multiplier = 0.6; + B.sell_dist = 9999999 + 1000; + events.golden_bat = 8000; + B.arena_limit = 1; + B.v = false; + B.free_last_hits = true; + mode.prevent_external = 1; + mode.pvp_level_gap = 1; +} else if (server_name == "TEST") { + gameplay = "test"; + is_pvp = true; + variables.ip_limit = 5; + variables.character_limit = 5; + B.rip_time = -1; + B.sell_dist = 9999999 + 1000; + B.arena_limit = 1; + B.rbugs = 1; + mode.prevent_external = 1; +} else if (server_name == "DUNGEON") { + gameplay = "dungeon"; + is_pvp = true; + variables.ip_limit = 1; + variables.character_limit = 1; + B.start_map = observer_map = "d_e"; + B.free_last_hits = true; + B.pause_instances = false; + for (var name in events) { + if (is_number(events[name])) { + events[name] = (events[name] !== 0 && 9999999999) || 0; + } else { + events[name] = false; + } + } +} else { + gameplay = "normal"; } -else if(server_name=="TEST") -{ - gameplay="test"; - is_pvp=true; - variables.ip_limit=5; - variables.character_limit=5; - B.rip_time=-1; - B.sell_dist=9999999+1000; - B.arena_limit=1; - B.rbugs=1; - mode.prevent_external=1; -} -else if(server_name=="DUNGEON") -{ - gameplay="dungeon"; - is_pvp=true; - variables.ip_limit=1; - variables.character_limit=1; - B.start_map=observer_map="d_e"; - B.free_last_hits=true; - B.pause_instances=false; - for(var name in events) - if(is_number(events[name])) events[name]=events[name]!==0&&9999999999||0; - else events[name]=false; -} -else gameplay="normal"; // B.u_boundary=12; B.u_vision=12; B["vision"]=[320,270] // B.vision[0]*=100; B.vision[1]*=100; -if(server_name.startsWith("PVP") || server_name.startsWith("HARDCORE")) -{ - is_pvp=true; - // variables.ip_limit=2+1; - // variables.character_limit=1+1; - luckm*=1.15; - xpm*=1.20; - goldm*=1.25; +if (server_name.startsWith("PVP") || server_name.startsWith("HARDCORE")) { + is_pvp = true; + // variables.ip_limit=2+1; + // variables.character_limit=1+1; + luckm *= 1.15; + xpm *= 1.2; + goldm *= 1.25; } // var secure_app=require('https').createServer(s_options,https_handler); #GTODO: Implement secure communications at one point -function init_game() -{ - appengine_call("create_server",{keyword:variables.keyword,port:port,region:region,name:server_name,pvp:is_pvp||'',gameplay:gameplay},function(result){ - try{ - if(result.exists) return [console.log("Server Exists!"),process.exit()]; - // console.log(result); - server_id=result.id; - server_auth=result.auth; - server_name=result.name; - server_log("Server Live: "+server_name+" "+server_id,1); - server_log("Node Version: "+process.version,1); - //server_log("Socket.IO Version: "+require("socket.io").version); - S=result.data; - G=result.game; - server_log("Game Version: "+G.version,1); - D=result.dynamics; - if(S) init_server_data(S); - if(G) sprocess_game_data(); - try{ - app.listen(port); - init_io(); - // app2.listen(parseInt(port)+40); - } - catch(e){ - server.exists=true; - server.started=true; - log_trace("port",e); - return shutdown_routine(); - } - //create_instance("main2","main2"); - var start=new Date(); - if(gameplay=="normal" || gameplay=="hardcore" || gameplay=="test") - { - create_instance("main"); - create_instance("tunnel"); - create_instance("cave"); - create_instance("tavern"); - create_instance("resort_e"); create_instance("resort"); - create_instance("mansion"); - create_instance("woffice"); - create_instance("halloween"); - create_instance("spookytown"); - if(gameplay=="normal") create_instance("test"); - create_instance("arena","arena",{pvp:1}); - create_instance("bank"); - create_instance("bank_b"); - create_instance("bank_u"); - //create_instance("batcave","batcave"); - create_instance("winterland"); - create_instance("winter_inn"); create_instance("winter_inn_rooms"); - create_instance("winter_cave"); create_instance("winter_cove"); - create_instance("desertland"); - create_instance("level1"); - create_instance("level2"); - create_instance("level2n"); - create_instance("level2e"); - create_instance("level2s"); - create_instance("level2w"); - create_instance("level3"); - create_instance("level4"); - create_instance("cyberland"); - create_instance("hut"); - create_instance("mtunnel"); - create_instance("ship0"); - create_instance("goobrawl"); - create_instance("d_e"); create_instance("d_g"); create_instance("d_b1"); create_instance("d_a1"); create_instance("d_a2"); - server_bfs("crypt"); - server_bfs("winter_instance"); - server_bfs("tomb"); - server_bfs("dungeon0"); - server_bfs("cgallery"); - } - else if(gameplay=="dungeon") - { - for(var name in G.maps) - if(G.maps[name].world=="dungeon") - create_instance(name); - } - create_instance("jail"); - console.log("Calculations took: "+mssince(start)+"ms"); - shuffle(hiding_places); - init_tavern(); - init_server(); - server.started=true; - server.live=true; - server.last_update=new Date(); - } - catch(e) - { - log_trace("init",e); - } - },function(){ - console.log("#X init_game's create_server call failed"); - }); +function init_game() { + appengine_call( + "create_server", + { + keyword: variables.keyword, + port: port, + region: region, + name: server_name, + pvp: is_pvp || "", + gameplay: gameplay, + }, + function (result) { + try { + if (result.exists) { + return [console.log("Server Exists!"), process.exit()]; + } + // console.log(result); + server_id = result.id; + server_auth = result.auth; + server_name = result.name; + server_log("Server Live: " + server_name + " " + server_id, 1); + server_log("Node Version: " + process.version, 1); + //server_log("Socket.IO Version: "+require("socket.io").version); + S = result.data; + G = result.game; + server_log("Game Version: " + G.version, 1); + D = result.dynamics; + if (S) { + init_server_data(S); + } + if (G) { + sprocess_game_data(); + } + try { + app.listen(port); + init_io(); + // app2.listen(parseInt(port)+40); + } catch (e) { + server.exists = true; + server.started = true; + log_trace("port", e); + return shutdown_routine(); + } + //create_instance("main2","main2"); + var start = new Date(); + if (gameplay == "normal" || gameplay == "hardcore" || gameplay == "test") { + create_instance("main"); + create_instance("tunnel"); + create_instance("cave"); + create_instance("tavern"); + create_instance("resort_e"); + create_instance("resort"); + create_instance("mansion"); + create_instance("woffice"); + create_instance("halloween"); + create_instance("spookytown"); + if (gameplay == "normal") { + create_instance("test"); + } + create_instance("arena", "arena", { pvp: 1 }); + create_instance("bank"); + create_instance("bank_b"); + create_instance("bank_u"); + //create_instance("batcave","batcave"); + create_instance("winterland"); + create_instance("winter_inn"); + create_instance("winter_inn_rooms"); + create_instance("winter_cave"); + create_instance("winter_cove"); + create_instance("desertland"); + create_instance("level1"); + create_instance("level2"); + create_instance("level2n"); + create_instance("level2e"); + create_instance("level2s"); + create_instance("level2w"); + create_instance("level3"); + create_instance("level4"); + create_instance("cyberland"); + create_instance("hut"); + create_instance("mtunnel"); + create_instance("ship0"); + create_instance("goobrawl"); + create_instance("d_e"); + create_instance("d_g"); + create_instance("d_b1"); + create_instance("d_a1"); + create_instance("d_a2"); + server_bfs("crypt"); + server_bfs("winter_instance"); + server_bfs("tomb"); + server_bfs("dungeon0"); + server_bfs("cgallery"); + } else if (gameplay == "dungeon") { + for (var name in G.maps) { + if (G.maps[name].world == "dungeon") { + create_instance(name); + } + } + } + create_instance("jail"); + console.log("Calculations took: " + mssince(start) + "ms"); + shuffle(hiding_places); + init_tavern(); + init_server(); + server.started = true; + server.live = true; + server.last_update = new Date(); + } catch (e) { + log_trace("init", e); + } + }, + function () { + console.log("#X init_game's create_server call failed"); + }, + ); } init_game(); -function reload_server(to_broadcast,change) -{ - appengine_call("reload_server",{keyword:variables.keyword,port:port,region:region,pvp:is_pvp||'',gameplay:gameplay},function(result){ - try{ - G=result.game; - D=result.dynamics; - sprocess_game_data(); - prop_cache={}; - if(to_broadcast) broadcast("reloaded",{change:change||""}); - } - catch(e) - { - broadcast("notice",{message:"Server Live Reload Failed"}); - log_trace("#X live_reload",e); - } - },function(){ - // setTimeout(init_game,1000); - server_log("#X live_reload failed",1); - broadcast("notice",{message:"Live Reload Failed"}); - }); +function reload_server(to_broadcast, change) { + appengine_call( + "reload_server", + { keyword: variables.keyword, port: port, region: region, pvp: is_pvp || "", gameplay: gameplay }, + function (result) { + try { + G = result.game; + D = result.dynamics; + sprocess_game_data(); + prop_cache = {}; + if (to_broadcast) { + broadcast("reloaded", { change: change || "" }); + } + } catch (e) { + broadcast("notice", { message: "Server Live Reload Failed" }); + log_trace("#X live_reload", e); + } + }, + function () { + // setTimeout(init_game,1000); + server_log("#X live_reload failed", 1); + broadcast("notice", { message: "Live Reload Failed" }); + }, + ); } -function decode_http_data(data) -{ - // #TODO: Find a better way to transfer data [28/09/17] - var result=decodeURIComponent(data).replace_all("u'","'").replace_all("'",'"').replace_all("+"," ").replace_all("%2B","+"); - return result; +function decode_http_data(data) { + // #TODO: Find a better way to transfer data [28/09/17] + var result = decodeURIComponent(data) + .replace_all("u'", "'") + .replace_all("'", '"') + .replace_all("+", " ") + .replace_all("%2B", "+"); + return result; } -function parse_http_json(data) -{ - var result=decode_http_data(data),json={}; - try{ - json=JSON.parse(result); - } - catch(e){ - console.log("\n"+data); - log_trace("parse_http_json",e); - } - return json; +function parse_http_json(data) { + var result = decode_http_data(data); + var json = {}; + try { + json = JSON.parse(result); + } catch (e) { + console.log("\n" + data); + log_trace("parse_http_json", e); + } + return json; } -function http_handler(request,response) -{ - var body = []; - request.on('error', function(err) { - server_log("http_err: "+err,1); - }).on('data', function(chunk) { - body.push(chunk); - }).on('end', function() { - body = Buffer.concat(body).toString(); - try{ - // server_log("http_handle's end"); - var url_parts = url.parse(request.url,true),args=url_parts.query,output=""; - //console.log(body); - (body||"").split("&").forEach(function(pv){ - var pvp=pv.split("="); - args[pvp[0]]=pvp[1]; - }); - if(args.checkin) - { - // console.log(JSON.stringify(args)); - // safe_search(request,"::1"); - // console.log(JSON.stringify(request.headers)); - console.log("start"); req=request; - console.log(req.connection ? req.connection.remoteAddress : null); - console.log(req.socket ? req.socket.remoteAddress : null); - console.log((req.connection && req.connection.socket) ? req.connection.socket.remoteAddress : null); - console.log(req.info ? req.info.remoteAddress : null); - var ip=getClientIp(request),id=id_to_id[args.id]; - // console.log(ip); - if(players[id] && players[id].ipass==args.ipass) - { - players[id].last_ip=ip; - players[id].last_ipass=new Date(); - // server_log("ipass for "+players[id].name); - } - } - if(args.spass!=variables.access_master) - { - response.writeHead(200); - response.end(output); - return; - } - if(args.aevent=="shutdown") - { - shutdown_routine(); - } - if(args.aevent=="cupdate") - { - var id=id_to_id[args.id]; - server_log("cupdate for "+args.id+" socket.id: "+id); - if(players[id]) - { - var player=players[id]; - player.cash=args.cash; - if(args.ncash && args.ncash!="0") player.socket.emit("game_log",{message:"Received "+args.ncash+" shells",color:colors.cash}); - - resend(player,"reopen+nc"); - } - output="yes"; - } - if(args.aevent=="new_friend") - { - var id=id_to_id[args.id]; - server_log("new_friend for "+args.id+" socket.id: "+id,1); - if(players[id]) - { - var player=players[id]; - player.friends=parse_http_json(args.friends); - player.socket.emit("friend",{event:"new",name:args.name,friends:player.friends}); - resend(player,"redata"); - } - output="yes"; - } - if(args.aevent=="lost_friend") - { - var id=id_to_id[args.id]; - server_log("lost_friend for "+args.id+" socket.id: "+id,1); - if(players[id]) - { - var player=players[id]; - player.friends=parse_http_json(args.friends); - player.socket.emit("friend",{event:"lost",friends:player.friends}); // ,name:args.name - resend(player,"redata"); - } - output="yes"; - } - if(args.aevent=="eval") - { - var data=parse_http_json(args.data); - try{ - eval(decode_http_data(args.code)); - } - catch(e){ - console.log("\n"+args.code); - log_trace("chttp_eval",e); - } - output=JSON.stringify(output); - } - response.writeHead(200); - response.end(output); - }catch(e){ - log_trace("chttp_err",e); - } - }); +function http_handler(request, response) { + var body = []; + request + .on("error", function (err) { + server_log("http_err: " + err, 1); + }) + .on("data", function (chunk) { + body.push(chunk); + }) + .on("end", function () { + body = Buffer.concat(body).toString(); + try { + // server_log("http_handle's end"); + var url_parts = url.parse(request.url, true); + var args = url_parts.query; + var output = ""; + //console.log(body); + (body || "").split("&").forEach(function (pv) { + var pvp = pv.split("="); + args[pvp[0]] = pvp[1]; + }); + if (args.checkin) { + // console.log(JSON.stringify(args)); + // safe_search(request,"::1"); + // console.log(JSON.stringify(request.headers)); + console.log("start"); + req = request; + console.log(req.connection ? req.connection.remoteAddress : null); + console.log(req.socket ? req.socket.remoteAddress : null); + console.log(req.connection && req.connection.socket ? req.connection.socket.remoteAddress : null); + console.log(req.info ? req.info.remoteAddress : null); + var ip = getClientIp(request); + var id = id_to_id[args.id]; + // console.log(ip); + if (players[id] && players[id].ipass == args.ipass) { + players[id].last_ip = ip; + players[id].last_ipass = new Date(); + // server_log("ipass for "+players[id].name); + } + } + if (args.spass != variables.access_master) { + response.writeHead(200); + response.end(output); + return; + } + if (args.aevent == "shutdown") { + shutdown_routine(); + } + if (args.aevent == "cupdate") { + var id = id_to_id[args.id]; + server_log("cupdate for " + args.id + " socket.id: " + id); + if (players[id]) { + var player = players[id]; + player.cash = args.cash; + if (args.ncash && args.ncash != "0") { + player.socket.emit("game_log", { message: "Received " + args.ncash + " shells", color: colors.cash }); + } + + resend(player, "reopen+nc"); + } + output = "yes"; + } + if (args.aevent == "new_friend") { + var id = id_to_id[args.id]; + server_log("new_friend for " + args.id + " socket.id: " + id, 1); + if (players[id]) { + var player = players[id]; + player.friends = parse_http_json(args.friends); + player.socket.emit("friend", { event: "new", name: args.name, friends: player.friends }); + resend(player, "redata"); + } + output = "yes"; + } + if (args.aevent == "lost_friend") { + var id = id_to_id[args.id]; + server_log("lost_friend for " + args.id + " socket.id: " + id, 1); + if (players[id]) { + var player = players[id]; + player.friends = parse_http_json(args.friends); + player.socket.emit("friend", { event: "lost", friends: player.friends }); // ,name:args.name + resend(player, "redata"); + } + output = "yes"; + } + if (args.aevent == "eval") { + var data = parse_http_json(args.data); + try { + eval(decode_http_data(args.code)); + } catch (e) { + console.log("\n" + args.code); + log_trace("chttp_eval", e); + } + output = JSON.stringify(output); + } + response.writeHead(200); + response.end(output); + } catch (e) { + log_trace("chttp_err", e); + } + }); } -function player_to_server(player,place) -{ - var char={}; - for(prop in player) - if(!in_arr(prop,["auth","last_sync","socket","character","last_upush","push","last","last_u","width","height","u"])) - char[prop]=player[prop]; - if(place=="sync" && !Object.keys(player.q).length && player.type!="merchant") delete char.p; // ~20KB - too much [19/11/18] - return char; +function player_to_server(player, place) { + var char = {}; + for (prop in player) { + if ( + !in_arr(prop, [ + "auth", + "last_sync", + "socket", + "character", + "last_upush", + "push", + "last", + "last_u", + "width", + "height", + "u", + ]) + ) { + char[prop] = player[prop]; + } + } + if (place == "sync" && !Object.keys(player.q).length && player.type != "merchant") { + delete char.p; + } // ~20KB - too much [19/11/18] + return char; } -function player_to_client(player,stranger) -{ - var data={}; - ["hp","max_hp","mp","max_mp","xp","attack","heal","frequency","speed","range","armor","resistance","level","party", - "rip","npc","allow","code","afk","target","focus","role","s","c","q","b","age","pdps", - "id","x","y","moving","going_x","going_y","abs","move_num","angle","cid","guild","team"].forEach(function(p){ - // removed "vx","vy" - if(player[p]!==undefined) data[p]=player[p]; - }); - ["stand"].forEach(function(p){ - if(player.p && player.p[p]!==undefined) data[p]=player.p[p]; - }); - if(player.afk=="code") data.controller=player.controller; - if(player.rip && (player.tcx&&player.tcx.gravestone||player.cx.gravestone)) player.rip=(player.tcx&&player.tcx.gravestone||player.cx.gravestone); - data.skin=player.tskin||player.skin; - data.cx=player.tcx||player.cx; - data.slots=player.cslots; - if(player.tp) data.tp=true; - data.ctype=player.type; - data.owner=!player.private&&player.owner||""; - - if(player.is_npc) - { - // data.id="$"+data.id; - data.name=player.name; - if(player.direction!==undefined) data.direction=player.direction,data.npc=player.npc; - } - - if(!stranger) - { - ["int","str","dex","vit","for","mp_cost","mp_reduction","max_xp","goldm","xpm","luckm","map","in","isize","esize","gold","cash","targets","m","evasion","miss","reflection","lifesteal","manasteal","rpiercing","apiercing","crit","critdamage","dreturn","tax","xrange","pnresistance","firesistance","fzresistance","phresistance","stresistance","incdmgamp","stun","blast","explosion","courage","mcourage","pcourage","fear"].forEach(function(p){ //"vision", - data[p]=player[p]; - }); - data.items=player.citems; - if(player.user!==undefined) - { - data.user=player.cuser; - if(player.user) data.user.gold=player.user.gold; - } - if(player.socket) data.cc=get_call_cost(player); - } - return data; +function player_to_client(player, stranger) { + var data = {}; + [ + "hp", + "max_hp", + "mp", + "max_mp", + "xp", + "attack", + "heal", + "frequency", + "speed", + "range", + "armor", + "resistance", + "level", + "party", + "rip", + "npc", + "allow", + "code", + "afk", + "target", + "focus", + "role", + "s", + "c", + "q", + "b", + "age", + "pdps", + "id", + "x", + "y", + "moving", + "going_x", + "going_y", + "abs", + "move_num", + "angle", + "cid", + "guild", + "team", + ].forEach(function (p) { + // removed "vx","vy" + if (player[p] !== undefined) { + data[p] = player[p]; + } + }); + ["stand"].forEach(function (p) { + if (player.p && player.p[p] !== undefined) { + data[p] = player.p[p]; + } + }); + if (player.afk == "code") { + data.controller = player.controller; + } + if (player.rip && ((player.tcx && player.tcx.gravestone) || player.cx.gravestone)) { + player.rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone; + } + data.skin = player.tskin || player.skin; + data.cx = player.tcx || player.cx; + data.slots = player.cslots; + if (player.tp) { + data.tp = true; + } + data.ctype = player.type; + data.owner = (!player.private && player.owner) || ""; + + if (player.is_npc) { + // data.id="$"+data.id; + data.name = player.name; + if (player.direction !== undefined) { + data.direction = player.direction; + data.npc = player.npc; + } + } + + if (!stranger) { + [ + "int", + "str", + "dex", + "vit", + "for", + "mp_cost", + "mp_reduction", + "max_xp", + "goldm", + "xpm", + "luckm", + "map", + "in", + "isize", + "esize", + "gold", + "cash", + "targets", + "m", + "evasion", + "miss", + "reflection", + "lifesteal", + "manasteal", + "rpiercing", + "apiercing", + "crit", + "critdamage", + "dreturn", + "tax", + "xrange", + "pnresistance", + "firesistance", + "fzresistance", + "phresistance", + "stresistance", + "incdmgamp", + "stun", + "blast", + "explosion", + "courage", + "mcourage", + "pcourage", + "fear", + ].forEach(function (p) { + //"vision", + data[p] = player[p]; + }); + data.items = player.citems; + if (player.user !== undefined) { + data.user = player.cuser; + if (player.user) { + data.user.gold = player.user.gold; + } + } + if (player.socket) { + data.cc = get_call_cost(player); + } + } + return data; } -function monster_to_client(monster,events) -{ - var data={},def=G.monsters[monster.type]; - ["speed","hp","mp","max_mp","attack","xp","frequency","armor","resistance","1hp","skin","cooperative","drops"].forEach(function(p){ //same array as game.js adopt_soft_properties - if(p in monster && monster[p]!=def[p]) data[p]=monster[p]; - }); - if(monster.max_hp!=def.hp) data.max_hp=monster.max_hp; - ["id","x","y","moving","going_x","going_y","abs","move_num","angle","type","cid","target","focus","s"].forEach(function(p){ // removed "vx","vy" from both datasets [16/04/18] - if(monster[p]!==undefined) data[p]=monster[p]; - }); - if(monster.level>1) data.level=monster.level; - if(monster.pet) data.pet=true,data.owner=monster.owner,data.name=monster.name; - if(monster.trap) data.trap=true,data.owner=monster.owner; - - if(events && events.length) data.events=events; - return data; +function monster_to_client(monster, events) { + var data = {}; + var def = G.monsters[monster.type]; + [ + "speed", + "hp", + "mp", + "max_mp", + "attack", + "xp", + "frequency", + "armor", + "resistance", + "1hp", + "skin", + "cooperative", + "drops", + ].forEach(function (p) { + //same array as game.js adopt_soft_properties + if (p in monster && monster[p] != def[p]) { + data[p] = monster[p]; + } + }); + if (monster.max_hp != def.hp) { + data.max_hp = monster.max_hp; + } + [ + "id", + "x", + "y", + "moving", + "going_x", + "going_y", + "abs", + "move_num", + "angle", + "type", + "cid", + "target", + "focus", + "s", + ].forEach(function (p) { + // removed "vx","vy" from both datasets [16/04/18] + if (monster[p] !== undefined) { + data[p] = monster[p]; + } + }); + if (monster.level > 1) { + data.level = monster.level; + } + if (monster.pet) { + data.pet = true; + data.owner = monster.owner; + data.name = monster.name; + } + if (monster.trap) { + data.trap = true; + data.owner = monster.owner; + } + + if (events && events.length) { + data.events = events; + } + return data; } -function player_to_summary(player) -{ - var summary={skin:player.tskin||player.skin,level:player.level,type:player.type,x:player.x,y:player.y,in:player.in,map:player.map,name:player.name,hp:player.hp,max_hp:player.max_hp}; - if(player.rip) summary.rip=(player.tcx&&player.tcx.gravestone||player.cx.gravestone)||true; - summary.cx=(player.tcx||player.cx||{}); - return summary; +function player_to_summary(player) { + var summary = { + skin: player.tskin || player.skin, + level: player.level, + type: player.type, + x: player.x, + y: player.y, + in: player.in, + map: player.map, + name: player.name, + hp: player.hp, + max_hp: player.max_hp, + }; + if (player.rip) { + summary.rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone || true; + } + summary.cx = player.tcx || player.cx || {}; + return summary; } -function save_state(player) -{ - player.state={"map":player.map,"in":player.in,"hp":player.hp,"mp":player.mp,"s":player.s,"x":player.x,"y":player.y,"restored":false}; +function save_state(player) { + player.state = { + map: player.map, + in: player.in, + hp: player.hp, + mp: player.mp, + s: player.s, + x: player.x, + y: player.y, + restored: false, + }; } -function clean_slate(player) -{ - restore_state(player); - save_state(player); - player.hp=player.max_hp; - player.mp=player.max_mp; - player.s={}; - player.c={}; +function clean_slate(player) { + restore_state(player); + save_state(player); + player.hp = player.max_hp; + player.mp = player.max_mp; + player.s = {}; + player.c = {}; } -function restore_state(player,dc) -{ - if(player.state && !player.state.restored) - { - player.hp=player.state.hp; - player.mp=player.state.mp; - player.s=player.state.s; - player.rip=false; if(player.state.rip) player.rip=true; - if(dc) - { - player.map=player.state.map; - player.in=player.state.in; - player.x=player.state.x; - player.y=player.state.y; - } - player.state.restored=true; - } - delete player.team; - delete player.duel; +function restore_state(player, dc) { + if (player.state && !player.state.restored) { + player.hp = player.state.hp; + player.mp = player.state.mp; + player.s = player.state.s; + player.rip = false; + if (player.state.rip) { + player.rip = true; + } + if (dc) { + player.map = player.state.map; + player.in = player.state.in; + player.x = player.state.x; + player.y = player.state.y; + } + player.state.restored = true; + } + delete player.team; + delete player.duel; } -function party_to_client(oname) -{ - var list=parties[oname]; - var party={},output=0,length=0,newbies=0,odps={},c={},add=36000; - calculate_party(oname); - list.forEach(function(name){ - var player=players[name_to_id[name]],dps_multiplier=1; if(!player) return; - if(player.type=="merchant") return; - if(player.type=="priest") dps_multiplier=1.36; - length+=1; - if(player.level<60) newbies+=1; - output+=player.pdps*dps_multiplier+add; - odps[player.owner]=(odps[player.owner]||0)+player.pdps*dps_multiplier+add; - c[player.owner]=(c[player.owner]||0)+1; - }); - list.forEach(function(name){ - var player=players[name_to_id[name]],dps_multiplier=1; if(!player) return; - if(player.type=="priest") dps_multiplier=1.36; - player.share=0; - if(!output) // to handle an all merchant party - { - player.share=1.0/(max(1,list.length)+EPS); - } - else if(player.type!="merchant") - { - player.share=odps[player.owner]/(c[player.owner]+EPS)/(output+EPS); - player.share=min(1,player.share); - } - player.party_length=length; - player.party_gold=5; - player.party_luck=newbies*10; - player.party_xp=[0,0,10,16,20,24,25,30,36,40,40,40,40][length]||0; - party[name]={skin:player.tskin||player.skin,level:player.level,type:player.type,x:player.x,y:player.y,in:player.in,map:player.map,share:player.share,pdps:player.pdps,l:length,xp:player.party_xp,luck:player.party_luck,gold:player.party_gold}; - if(player.rip) party[name].rip=(player.tcx&&player.tcx.gravestone||player.cx.gravestone)||true; - if(Object.keys(player.tcx||player.cx).length) party[name].cx=(player.tcx||player.cx); - }); - // git test - return party; +function party_to_client(oname) { + var list = parties[oname]; + var party = {}; + var output = 0; + var length = 0; + var newbies = 0; + var odps = {}; + var c = {}; + var add = 36000; + calculate_party(oname); + list.forEach(function (name) { + var player = players[name_to_id[name]]; + var dps_multiplier = 1; + if (!player) { + return; + } + if (player.type == "merchant") { + return; + } + if (player.type == "priest") { + dps_multiplier = 1.36; + } + length += 1; + if (player.level < 60) { + newbies += 1; + } + output += player.pdps * dps_multiplier + add; + odps[player.owner] = (odps[player.owner] || 0) + player.pdps * dps_multiplier + add; + c[player.owner] = (c[player.owner] || 0) + 1; + }); + list.forEach(function (name) { + var player = players[name_to_id[name]]; + var dps_multiplier = 1; + if (!player) { + return; + } + if (player.type == "priest") { + dps_multiplier = 1.36; + } + player.share = 0; + if (!output) { + // to handle an all merchant party + player.share = 1.0 / (max(1, list.length) + EPS); + } else if (player.type != "merchant") { + player.share = odps[player.owner] / (c[player.owner] + EPS) / (output + EPS); + player.share = min(1, player.share); + } + player.party_length = length; + player.party_gold = 5; + player.party_luck = newbies * 10; + player.party_xp = [0, 0, 10, 16, 20, 24, 25, 30, 36, 40, 40, 40, 40][length] || 0; + party[name] = { + skin: player.tskin || player.skin, + level: player.level, + type: player.type, + x: player.x, + y: player.y, + in: player.in, + map: player.map, + share: player.share, + pdps: player.pdps, + l: length, + xp: player.party_xp, + luck: player.party_luck, + gold: player.party_gold, + }; + if (player.rip) { + party[name].rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone || true; + } + if (Object.keys(player.tcx || player.cx).length) { + party[name].cx = player.tcx || player.cx; + } + }); + // git test + return party; } -function send_party_update(oname) -{ - var party=party_to_client(oname); - parties[oname].forEach(function(name){ - var player=players[name_to_id[name]]; - if(!player) { console.log("#X party player not found: "+oname); leave_party(oname,{name:name}); return; } - players[name_to_id[name]].socket.emit("party_update",{list:parties[oname],party:party}); - }); +function send_party_update(oname) { + var party = party_to_client(oname); + parties[oname].forEach(function (name) { + var player = players[name_to_id[name]]; + if (!player) { + console.log("#X party player not found: " + oname); + leave_party(oname, { name: name }); + return; + } + players[name_to_id[name]].socket.emit("party_update", { list: parties[oname], party: party }); + }); } -function calculate_party(oname) -{ - var list=parties[oname]; - list.forEach(function(name){ - var player=players[name_to_id[name]]; if(!player) return; - if(player.type=="merchant") player.party_weight=0; - player.party_weight=20; - }); +function calculate_party(oname) { + var list = parties[oname]; + list.forEach(function (name) { + var player = players[name_to_id[name]]; + if (!player) { + return; + } + if (player.type == "merchant") { + player.party_weight = 0; + } + player.party_weight = 20; + }); } -var stat_to_attr={ - "str":"str", - "int":"int", - "dex":"dex", - "vit":"vit", - "for":"for", - "armor":"armor", - "resistance":"resistance", - "pnresistance":"pnresistance", - "firesistance":"firesistance", - "fzresistance":"fzresistance", - "phresistance":"phresistance", - "stresistance":"stresistance", - "incdmgamp":"incdmgamp", - "stun":"stun", - "blast":"blast", - "explosion":"explosion", - "evasion":"evasion", - "cuteness":"cuteness", - "bling":"bling", - "dreturn":"dreturn", - "reflection":"reflection", - "crit":"crit", - "critdamage":"critdamage", - "miss":"miss", - "avoidance":"avoidance", - "hp":"max_hp", - "mp":"max_mp", - "speed":"speed", - "lifesteal":"lifesteal", - "manasteal":"manasteal", - "apiercing":"apiercing", - "rpiercing":"rpiercing", - "output":"output", - "attack":"a_attack", - "mp_cost":"a_mp_cost", - "mp_reduction":"mp_reduction", - "xp":"xxp", - "luck":"xluck", - "gold":"xgold", - "range":"range", - "courage":"courage", - "mcourage":"mcourage", - "pcourage":"pcourage", +var stat_to_attr = { + str: "str", + int: "int", + dex: "dex", + vit: "vit", + for: "for", + armor: "armor", + resistance: "resistance", + pnresistance: "pnresistance", + firesistance: "firesistance", + fzresistance: "fzresistance", + phresistance: "phresistance", + stresistance: "stresistance", + incdmgamp: "incdmgamp", + stun: "stun", + blast: "blast", + explosion: "explosion", + evasion: "evasion", + cuteness: "cuteness", + bling: "bling", + dreturn: "dreturn", + reflection: "reflection", + crit: "crit", + critdamage: "critdamage", + miss: "miss", + avoidance: "avoidance", + hp: "max_hp", + mp: "max_mp", + speed: "speed", + lifesteal: "lifesteal", + manasteal: "manasteal", + apiercing: "apiercing", + rpiercing: "rpiercing", + output: "output", + attack: "a_attack", + mp_cost: "a_mp_cost", + mp_reduction: "mp_reduction", + xp: "xxp", + luck: "xluck", + gold: "xgold", + range: "range", + courage: "courage", + mcourage: "mcourage", + pcourage: "pcourage", +}; + +function apply_stats(player, prop, args) { + for (var stat in prop) { + if (args && args.no_range && stat == "range") { + continue; + } + if (stat_to_attr[stat]) { + player[stat_to_attr[stat]] = player[stat_to_attr[stat]] + prop[stat]; + } else if (stat == "frequency") { + player.frequency += prop[stat] / 100.0; + } + } } -function apply_stats(player,prop,args) -{ - for(var stat in prop) - { - if(args && args.no_range && stat=="range") continue; - if(stat_to_attr[stat]) - player[stat_to_attr[stat]]=player[stat_to_attr[stat]]+prop[stat]; - else if(stat=="frequency") player.frequency+=prop[stat]/100.0; - - } +function calculate_player_stats(player) { + if (player.is_npc) { + return; + } + player.max_xp = G.levels[player.level + ""]; + var level_up = false; + while (player.xp >= player.max_xp) { + level_up = true; + player.xp -= player.max_xp; + player.level++; + player.max_xp = G.levels[player.level + ""]; + player.hp = 0; + achievement_logic_level(player); + } + if (level_up) { + if (player.level >= 80) { + realm_broadcast("server_message", { message: player.name + " is now level " + player.level, color: "#968CFA" }); + } else if (player.level >= 70) { + broadcast("server_message", { message: player.name + " is now level " + player.level + "!", color: "#968CFA" }); + } + xy_emit(player, "ui", { type: "level_up", name: player.name }); + } + // disappearing_text(player.socket,player,"LEVEL UP!",{xy:1,size:"huge",color:"#724A8F"}); + if (player.xp < 0) { + player.xp = 0; + player.warnings = (player.warnings || 0) + 1; + if (player.warnings == 2) { + console.log("'Your monster!' logged out ->"); + player.socket.emit("ui_log", "You monster!"); + player.socket.disconnect(); + return; + } + } + var class_def = G.classes[player.type]; + var item_attack = 0; + var the_date = new Date(); + if (!class_def) { + class_def = G.classes.merchant; + } // when npc's get resend't + player.range = class_def.range; + player.max_hp = class_def.hp; + player.max_mp = class_def.mp; + ["stealth"].forEach(function (prop) { + player[prop] = false; + }); + [ + "a_mp_cost", + "a_attack", + "stones", + "lifesteal", + "manasteal", + "incdmgamp", + "mp_reduction", + "stun", + "blast", + "explosion", + "cuteness", + "bling", + ].forEach(function (prop) { + player[prop] = 0; + }); + [ + "speed", + "attack", + "frequency", + "mp_cost", + "armor", + "resistance", + "apiercing", + "rpiercing", + "a", + "aura", + "evasion", + "miss", + "reflection", + "crit", + "critdamage", + "dreturn", + "computer", + "xxp", + "xluck", + "xgold", + "output", + "courage", + "mcourage", + "pcourage", + "pnresistance", + "firesistance", + "fzresistance", + "phresistance", + "stresistance", + ].forEach(function (prop) { + player[prop] = class_def[prop] || 0; + }); + if (!player.a) { + player.a = {}; + } //abilities + if (!player.aura) { + player.aura = {}; + } + if (player.paura) { + for (var id in player.paura) { + player.aura[id] = player.paura[id]; + } + } + for (stat in class_def.stats) { + player[stat] = class_def.stats[stat] + player.level * class_def.lstats[stat]; + if (player.level > 40) { + player[stat] += (player.level - 40) * class_def.lstats[stat]; + } + if (player.level > 55) { + player[stat] += (player.level - 55) * class_def.lstats[stat]; + } + if (player.level > 65) { + player[stat] += (player.level - 65) * class_def.lstats[stat]; + } + if (player.level > 80) { + player[stat] -= (player.level - 80) * class_def.lstats[stat]; + } + player[stat] = floor(player[stat]); + } + if (!player.slots) { + player.slots = {}; + } + // players.citems[27]=null; //- to reproduce the bug + while (player.items.length > 42 && !player.items[player.items.length - 1]) { + player.items.splice(player.items.length - 1); + } + while (player.citems.length > 42 && !player.citems[player.citems.length - 1]) { + player.citems.splice(player.citems.length - 1); + } + player.isize = 42; + player.sets = {}; + player.esize = player.isize - player.items.length; + player.xpm = player.goldm = player.luckm = 1; + for (var i = 0; i < player.items.length; i++) { + var current = player.items[i]; + if (!current) { + player.esize++; + } else if (current.name == "supercomputer") { + player.tracker = player.computer = player.supercomputer = true; + } else if (current.name == "computer") { + player.computer = true; + } else if (current.name == "tracker") { + player.tracker = true; + } else if (current.expires) { + // &&!current.ex = ex=elixier + if (the_date > current.expires) { + player.items[i] = null; + player.citems[i] = null; + } else if (G.items[current.name].gain) { + var prop = calculate_item_properties(current); + player.stones++; + player["x" + G.items[current.name].gain] = prop[G.items[current.name].gain]; + } + } + } + player.monster_stats = {}; + if (player.tracker) { + for (var name in G.monsters) { + var mx = max( + (player.p.stats.monsters[name] || 0) + (player.p.stats.monsters_diff[name] || 0), + (player.max_stats.monsters[name] || [0, 0])[0], + ); + if (!mx || !G.monsters[name].achievements) { + continue; + } + G.monsters[name].achievements.forEach(function (def) { + if (mx < def[0]) { + return; + } + if (def[1] == "stat") { + player.monster_stats[def[2]] = (player.monster_stats[def[2]] || 0) + def[3]; + } + }); + } + } + character_slots.forEach(function (slot) { + var current = player.slots[slot]; + if (!current) { + return; + } + var def = G.items[current.name]; + if (!def) { + console.log("#X Undefined item: " + current.name + " (" + slot + ")"); + return; + } + var prop = calculate_item_properties(current, { class: player.type, map: player.map }); + if (prop.class && !prop.class.includes(player.type)) { + return; + } + + apply_stats(player, prop, { no_range: slot == "offhand" && def.type == "weapon" }); + if (prop.attack) { + if (slot == "offhand") { + item_attack += prop.attack * 0.7; + } else { + item_attack += prop.attack; + } + } + if (def.ability) { + player.a[def.ability] = { + attr0: ((player.a[def.ability] && player.a[def.ability].attr0) || 0) + prop.attr0, + attr1: ((player.a[def.ability] && player.a[def.ability].attr1) || 0) + prop.attr1, + }; + } + if (def.aura) { + player.aura[def.aura] = { attr0: prop.attr0 || 0, attr1: prop.attr1 || 0 }; + } + if (slot == "mainhand") { + apply_stats(player, class_def.doublehand[def.wtype] || class_def.mainhand[def.wtype] || {}); + } + if (slot == "offhand") { + apply_stats( + player, + class_def.offhand[def.wtype] || + class_def.offhand[def.type] || { + no_range: !player.slots.mainhand, + }, + ); + } + if (prop.set) { + player.sets[def.set] = (player.sets[def.set] || 0) + 1; + } + }); + for (var set in player.sets) { + var prop = G.sets[set] && G.sets[set][player.sets[set]]; + if (!prop) { + continue; + } + apply_stats(player, prop); + } + for (var condition in player.s) { + var prop = G.conditions[condition]; + apply_stats(player, player.s[condition]); + if (!prop) { + continue; + } + apply_stats(player, prop); + } + apply_stats(player, player.monster_stats); + if ( + player.slots.mainhand && + player.slots.offhand && + G.items[player.slots.mainhand.name].wtype == "stars" && + G.items[player.slots.offhand.name].wtype != "stars" + ) { + item_attack /= 3.0; + } + if (player.slots.cape && player.slots.cape.name == "stealthcape") { + player.stealth = true; + } + item_attack = max(item_attack, 5); + if (player.type == "paladin") { + player.attack += item_attack * (player.str / 20.0 + player.int / 40.0); + } else { + player.attack += item_attack * (player[class_def.main_stat] / 20.0); + } + player.attack += player.a_attack; + if (player.type == "priest") { + player.attack *= 1.6; + } + if (player.type == "warrior") { + player.courage += round(player.str / 30); + } + if (player.type == "priest") { + player.mcourage += round(player.int / 30); + } + if (player.type == "paladin") { + player.pcourage += round(player.str / 30 + player.int / 30); + } + //console.log(player.speed) + //player.speed+=player.dex/20.0+player.str/40.0+min(player.level,40)/4.0+max(0,min(player.level-40,20))/5.0+max(0,player.level-60)/7.0 + player.speed += + min(player.dex, 256) / 32.0 + + min(player.str, 256) / 64.0 + + min(player.level, 40) / 10.0 + + max(0, min(player.level - 40, 20)) / 15.0 + + max(0, min(86, player.level) - 60) / 16.0; + + player.aggro_diff = player.bling / 100 - player.cuteness / 100; + + //console.log(player.speed) + //player.max_hp+=player.str*5+player.vit*player.level/2; //player.str*10.0+player.level*10+player.vit*25 + player.max_hp += player.str * 21 + player.vit * (48 + player.level / 3.0); + player.max_hp = max(1, player.max_hp); + player.max_mp += player.int * 15.0 + player.level * 5; + //player.armor+=player.str/2.0; + player.armor += min(player.str, 160) + max(0, player.str - 160) * 0.25; + player.resistance += min(player.int, 180) + max(0, player.int - 180) * 0.25; + player.frequency += + min(player.level, 80) / 164.0 + + min(160, player.dex) / 640.0 + + max(player.dex - 160, 0) / 925.0 + + player.int / 1575.0; // 120 635 1275 is the original mix + // player.frequency=9000; + player.attack_ms = round(1000.0 / player.frequency); + if (player.last_attack_ms && player.attack_ms != player.last_attack_ms) { + // server_log("skill_timeout_correction: "+player.last_attack_ms+" to "+player.attack_ms+" timeout: "+(player.attack_ms-mssince(player.last.attack))) + player.socket.emit("skill_timeout", { + name: "attack", + ms: player.attack_ms - mssince(player.last.attack), + reason: "attack_ms", + }); + } + player.last_attack_ms = player.attack_ms; + player.mp_cost += + min(player.level, 80) * (player.mp_cost / 10.0) + + player.a_mp_cost + + player.crit * 1.25 + + player.lifesteal * 1.5 + + player.manasteal / 5.0; + if (player.damage_type == "physical") { + player.mp_cost += player.apiercing / 15.0; + } else { + player.mp_cost += player.rpiercing / 15.0; + } + player.mp_cost = max(1, player.mp_cost); + if (!player.hp && !player.rip) { + player.hp = player.max_hp; + player.mp = player.max_mp; + } // used for level-ups + if ((player.gold || 0) <= 0) { + player.gold = 0; + } + player.heal = 0; + if (player.type == "priest") { + player.heal = player.attack; + } + player.output = max(5, player.output); + if (player.output) { + player.attack = (player.attack * player.output) / 100.0; + } + if (player.s.damage_received) { + player.attack += (player.s.damage_received.amount * 4) / 100; + } + ["attack", "heal", "hp", "mp", "max_hp", "max_mp", "range", "mp_cost", "resistance", "armor"].forEach( + function (prop) { + player[prop] = round(player[prop]); + }, + ); + player.hp = max(0, min(player.hp, player.max_hp)); + player.mp = max(0, min(player.mp, player.max_mp)); + + if (player.party && parties[player.party]) { + if (player.party_xp) { + player.xxp += player.party_xp; + } + if (player.party_luck) { + player.xluck += player.party_luck; + } + if (player.party_gold) { + player.xgold += player.party_gold; + } + } + + if (goldm != 1) { + player.xgold += (goldm - 1) * 100.0; + player.xluck += (luckm - 1) * 100.0; + player.xxp += (xpm - 1) * 100.0; + } + player.luckm = 1 + player.xluck / 100.0; + player.xpm = 1 + player.xxp / 100.0; + player.goldm = 1 + player.xgold / 100.0; + ["luckm", "xpm", "goldm"].forEach(function (p) { + player[p] = max(0.01, player[p]); + }); + + if (player.tskin == "konami") { + player.range = 120; + player.frequency += 0.25; + player.goldm *= 0.25; + player.luckm += 0.25; + } + + if (player.s.invis) { + player.speed = max(player.speed * 0.6, player.speed - 25); + player.attack = round(player.attack * 1.25); + } + if (player.s.invincible) { + player.attack = round(player.attack * 0.45); + } + if (player.s.dash) { + player.speed = 500; + } + calculate_common_stats(player); + + player.tax = + (player.level > 80 && 0.01) || + (player.level > 80 && 0.012) || + (player.level > 70 && 0.02) || + (player.level > 60 && 0.025) || + (player.level > 50 && 0.03) || + (player.level > 20 && 0.04) || + 0.05; + + var excess = max( + 0, + max(player.targets_p - player.courage, max(player.targets_m - player.mcourage, player.targets_u - player.pcourage)), + ); + var sredux = [0, 20, 40, 70, 80, 90, 100]; + player.speed -= sredux[min(sredux.length - 1, excess)]; + if (excess > 2) { + player.attack = round(player.attack * 0.2); + } else if (excess > 1) { + player.attack = round(player.attack * 0.4); + } else if (excess) { + player.attack = round(player.attack * 0.6); + } + player.fear = excess; + + if (player.map == "winterland") { + player.speed *= 0.95; + } + + if (player.p.stand || player.s.hardshell) { + player.speed = 10; + } + player.evasion = min(50, player.evasion); + player.reflection = min((player.s.reflection && 50) || 30, player.reflection); + player.speed = min(player.speed, player.cruise || 200000); + player.speed = round(max(5, player.speed)); + if (!player.gold && player.gold !== 0) { + player.gold = 0; + server_log("#X - GOLD BUG calculate", 1); + } + recalculate_vxy(player); + perfc.cps += 1; } -function calculate_player_stats(player) -{ - if(player.is_npc) return; - player.max_xp=G.levels[player.level+""]; - var level_up=false; - while(player.xp>=player.max_xp) - { - level_up=true; - player.xp-=player.max_xp; - player.level++; - player.max_xp=G.levels[player.level+""]; - player.hp=0; - achievement_logic_level(player); - } - if(level_up) - { - if(player.level>=80) realm_broadcast("server_message",{message:player.name+" is now level "+player.level,color:"#968CFA"}); - else if(player.level>=70) broadcast("server_message",{message:player.name+" is now level "+player.level+"!",color:"#968CFA"}); - xy_emit(player,"ui",{type:"level_up",name:player.name}); - } - // disappearing_text(player.socket,player,"LEVEL UP!",{xy:1,size:"huge",color:"#724A8F"}); - if(player.xp<0) - { - player.xp=0; - player.warnings=(player.warnings||0)+1; - if(player.warnings==2) - { - console.log("'Your monster!' logged out ->"); - player.socket.emit("ui_log","You monster!"); - player.socket.disconnect(); - return; - } - } - var class_def=G.classes[player.type],item_attack=0,the_date=new Date(); - if(!class_def) class_def=G.classes.merchant; // when npc's get resend't - player.range=class_def.range; - player.max_hp=class_def.hp; - player.max_mp=class_def.mp; - ["stealth"].forEach(function(prop){player[prop]=false;}); - ["a_mp_cost","a_attack","stones","lifesteal","manasteal","incdmgamp","mp_reduction","stun","blast","explosion","cuteness","bling"].forEach(function(prop){player[prop]=0;}); - ["speed","attack","frequency","mp_cost","armor","resistance","apiercing","rpiercing","a","aura","evasion","miss","reflection","crit","critdamage","dreturn","computer","xxp","xluck","xgold","output","courage","mcourage","pcourage","pnresistance","firesistance","fzresistance","phresistance","stresistance"].forEach(function(prop){ - player[prop]=class_def[prop]||0; - }); - if(!player.a) player.a={}; //abilities - if(!player.aura) player.aura={}; - if(player.paura) for(var id in player.paura) player.aura[id]=player.paura[id]; - for(stat in class_def.stats) - { - player[stat]=class_def.stats[stat]+player.level*class_def.lstats[stat]; - if(player.level>40) player[stat]+=(player.level-40)*class_def.lstats[stat]; - if(player.level>55) player[stat]+=(player.level-55)*class_def.lstats[stat]; - if(player.level>65) player[stat]+=(player.level-65)*class_def.lstats[stat]; - if(player.level>80) player[stat]-=(player.level-80)*class_def.lstats[stat]; - player[stat]=floor(player[stat]); - } - if(!player.slots) player.slots={}; - // players.citems[27]=null; //- to reproduce the bug - while(player.items.length>42 && !player.items[player.items.length-1]) player.items.splice(player.items.length-1); - while(player.citems.length>42 && !player.citems[player.citems.length-1]) player.citems.splice(player.citems.length-1); - player.isize=42; player.sets={}; - player.esize=player.isize-player.items.length; - player.xpm=player.goldm=player.luckm=1; - for(var i=0;icurrent.expires) - { - player.items[i]=null; player.citems[i]=null; - } - else if(G.items[current.name].gain) - { - var prop=calculate_item_properties(current); - player.stones++; - player["x"+G.items[current.name].gain]=prop[G.items[current.name].gain]; - } - } - } - player.monster_stats={}; - if(player.tracker) - { - for(var name in G.monsters) - { - var mx=max((player.p.stats.monsters[name]||0)+(player.p.stats.monsters_diff[name]||0),(player.max_stats.monsters[name]||[0,0])[0]); - if(!mx || !G.monsters[name].achievements) continue; - G.monsters[name].achievements.forEach(function(def){ - if(mx80)&&0.010||(player.level>80)&&0.012||(player.level>70)&&0.020||(player.level>60)&&0.025||(player.level>50)&&0.03||(player.level>20)&&0.04||0.05; - - var excess=max(0,max(player.targets_p-player.courage,max(player.targets_m-player.mcourage,player.targets_u-player.pcourage))); - var sredux=[0,20,40,70,80,90,100]; - player.speed-=sredux[min(sredux.length-1,excess)]; - if(excess>2) player.attack=round(player.attack*0.2); - else if(excess>1) player.attack=round(player.attack*0.4); - else if(excess) player.attack=round(player.attack*0.6); - player.fear=excess; - - if(player.map=="winterland") player.speed*=0.95; - - if(player.p.stand || player.s.hardshell) player.speed=10; - player.evasion=min(50,player.evasion); - player.reflection=min(player.s.reflection&&50||30,player.reflection); - player.speed=min(player.speed,player.cruise||200000); - player.speed=round(max(5,player.speed)); - if(!player.gold && player.gold!==0) player.gold=0,server_log("#X - GOLD BUG calculate",1); - recalculate_vxy(player); - perfc.cps+=1; +function calculate_common_stats(entity) { + if (entity.s.poisoned) { + entity.frequency *= 0.9; + } + if (entity.s.frozen) { + entity.frequency *= 0.3; + entity.speed -= 40; + } + if (entity.speed < 1) { + entity.speed = 1; + } + if (entity.s.tangled) { + entity.speed = min(entity.speed, 24); + } } -function calculate_common_stats(entity) -{ - if(entity.s.poisoned) entity.frequency*=0.9; - if(entity.s.frozen) entity.frequency*=0.3,entity.speed-=40; - if(entity.speed<1) entity.speed=1; - if(entity.s.tangled) entity.speed=min(entity.speed,24); +function calculate_monster_stats(monster) { + var def = G.monsters[monster.type]; + ["attack", "speed", "frequency", "armor", "resistance", "output", "incdmgamp", "avoidance"].forEach(function (p) { + monster[p] = def[p] || 0; + }); + monster.output = 100; + if (monster.target || monster.focus) { + monster.speed = G.monsters[monster.type].charge; + } + for (var name in monster.s) { + // if(G.conditions[name]) + // { + // for(var p in G.conditions[name]) + // if(p in monster) + // monster[p]+=G.conditions[name][p]; + // } + + var prop = G.conditions[name]; + apply_stats(monster, monster.s[name]); + if (!prop) { + continue; + } + apply_stats(monster, prop); + } + monster.attack = round((monster.attack * monster.output) / 100.0); + if (monster.focus && instances[monster.in].monsters[monster.focus]) { + monster.speed = min(monster.speed, instances[monster.in].monsters[monster.focus].speed + 4); + } + if (monster.level > 1) { + var att_mult = 0.125; + var freq_mult = 0.034; + var speed_mult = 0.24; // speed originally 0.34 + var mlevel = min(monster.level, 12); + if (monster.map_def.grow) { + att_mult = 0.05; + freq_mult = 0.008; + speed_mult = 0.16; + } + monster.attack = parseInt(monster.attack * (1 + mlevel * att_mult)); + monster.speed += mlevel * speed_mult; + monster.frequency += mlevel * freq_mult; + } + if (E.schedule.night) { + monster.speed = ceil(monster.speed * 0.7); + } + calculate_common_stats(monster); + recalculate_vxy(monster); } - -function calculate_monster_stats(monster) -{ - var def=G.monsters[monster.type]; - ["attack","speed","frequency","armor","resistance","output","incdmgamp","avoidance"].forEach(function(p){ - monster[p]=def[p]||0; - }); - monster.output=100; - if(monster.target || monster.focus) monster.speed=G.monsters[monster.type].charge; - for(var name in monster.s) - { - // if(G.conditions[name]) - // { - // for(var p in G.conditions[name]) - // if(p in monster) - // monster[p]+=G.conditions[name][p]; - // } - - var prop=G.conditions[name]; - apply_stats(monster,monster.s[name]) - if(!prop) continue; - apply_stats(monster,prop); - } - monster.attack=round(monster.attack*monster.output/100.0) - if(monster.focus && instances[monster.in].monsters[monster.focus]) monster.speed=min(monster.speed,instances[monster.in].monsters[monster.focus].speed+4); - if(monster.level>1) - { - var att_mult=0.125,freq_mult=0.034,speed_mult=0.24; // speed originally 0.34 - var mlevel=min(monster.level,12); - if(monster.map_def.grow) att_mult=0.05,freq_mult=0.008,speed_mult=0.16; - monster.attack=parseInt(monster.attack*(1+mlevel*att_mult)); - monster.speed+=mlevel*speed_mult; - monster.frequency+=mlevel*freq_mult; - } - if(E.schedule.night) monster.speed=ceil(monster.speed*0.7); - calculate_common_stats(monster); - recalculate_vxy(monster); +function ccms(monster) { + if (Object.keys(monster.s).length) { + calculate_monster_stats(monster); + } } -function ccms(monster) -{ - if(Object.keys(monster.s).length) calculate_monster_stats(monster); +function can_equip_item(player, item, slot) { + var class_def = G.classes[player.type]; + if (!slot) { + slot = item.type; + } + if (slot == "offhand" && item.type != "weapon") { + slot = item.type; + } //Easiest solution to the slot:"offhand" challenge [15/11/16] + if (item.type == "tool") { + slot = "mainhand"; + } + if (item.type == "test" && slot != "test") { + return slot; + } + if ( + !in_arr(item.type, [ + "helmet", + "pants", + "chest", + "weapon", + "amulet", + "earring", + "shoes", + "gloves", + "ring", + "shield", + "belt", + "source", + "orb", + "quiver", + "cape", + "misc_offhand", + "tool", + ]) + ) { + return "no"; + } + if (slot != item.type && in_arr(item.type, ["shield", "source", "quiver", "misc_offhand"]) && slot != "offhand") { + return "no"; + } + if (in_arr(slot, ["weapon", "mainhand", "offhand", "tool"])) { + if ( + slot == "weapon" && + player.slots.mainhand && + class_def.mainhand[G.items[player.slots.mainhand.name].wtype] && + !player.slots.offhand && + class_def.offhand[item.wtype] + ) { + slot = "offhand"; + } else if ( + slot == "offhand" && + (!player.slots.mainhand || + (player.slots.mainhand && class_def.mainhand[G.items[player.slots.mainhand.name].wtype])) && + class_def.offhand[item.wtype] + ) { + slot = "offhand"; + } else if (slot == "weapon" || slot == "mainhand") { + if (class_def.doublehand[item.wtype] && !player.slots.offhand) { + } else if (!class_def.mainhand[item.wtype]) { + return "no"; + } + slot = "mainhand"; + } else { + return "no"; + } + } else if (item.type == "shield" || item.type == "misc_offhand") { + if ( + class_def.offhand[item.type] && + !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype]) + ) { + slot = "offhand"; + } else { + return "no"; + } + } else if (item.type == "quiver") { + if ( + class_def.offhand.quiver && + !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype]) + ) { + slot = "offhand"; + } else { + return "no"; + } + } else if (item.type == "source") { + if ( + class_def.offhand.source && + !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype]) + ) { + slot = "offhand"; + } else { + return "no"; + } + } else if (item.type == "ring") { + if (slot == "ring1" || slot == "ring2") { + } else if (!player.slots["ring1"]) { + slot = "ring1"; + } else { + slot = "ring2"; + } + } else if (item.type == "earring") { + if (slot == "earring1" || slot == "earring2") { + } else if (!player.slots["earring1"]) { + slot = "earring1"; + } else { + slot = "earring2"; + } + } else if (slot != item.type) { + return "no"; + } + return slot; } -function can_equip_item(player,item,slot) -{ - var class_def=G.classes[player.type]; - if(!slot) slot=item.type; - if(slot=="offhand" && item.type!="weapon") slot=item.type; //Easiest solution to the slot:"offhand" challenge [15/11/16] - if(item.type=="tool") slot="mainhand"; - if(item.type=="test" && slot!="test") return slot; - if(!in_arr(item.type,["helmet","pants","chest","weapon","amulet","earring","shoes","gloves","ring","shield","belt","source","orb","quiver","cape","misc_offhand","tool"])) return "no"; - if(slot!=item.type && in_arr(item.type,["shield","source","quiver","misc_offhand"]) && slot!="offhand") return "no"; - if(in_arr(slot,["weapon","mainhand","offhand","tool"])) - { - if(slot=="weapon" && player.slots.mainhand && class_def.mainhand[G.items[player.slots.mainhand.name].wtype] && !player.slots.offhand && class_def.offhand[item.wtype]) - { - slot="offhand"; - } - else if(slot=="offhand" && (!player.slots.mainhand || player.slots.mainhand && class_def.mainhand[G.items[player.slots.mainhand.name].wtype]) && class_def.offhand[item.wtype]) - { - slot="offhand"; - } - else if(slot=="weapon" || slot=="mainhand") - { - if(class_def.doublehand[item.wtype] && !player.slots.offhand); - else if(!class_def.mainhand[item.wtype]) return "no"; - slot="mainhand"; - } - else {return "no";} - } - else if(item.type=="shield" || item.type=="misc_offhand") - { - if(class_def.offhand[item.type] && !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])) slot="offhand"; - else {return "no";} - } - else if(item.type=="quiver") - { - if(class_def.offhand.quiver && !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])) slot="offhand"; - else {return "no";} - } - else if(item.type=="source") - { - if(class_def.offhand.source && !(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])) slot="offhand"; - else {return "no";} - } - else if(item.type=="ring") - { - if(slot=="ring1" || slot=="ring2"); - else if(!player.slots["ring1"]) slot="ring1"; - else slot="ring2"; - } - else if(item.type=="earring") - { - if(slot=="earring1" || slot=="earring2"); - else if(!player.slots["earring1"]) slot="earring1"; - else slot="earring2"; - } - else if(slot!=item.type) - { - return "no"; - } - return slot; +function consume(player, num, quantity) { + var available = player.items[num].q || 1; + if (quantity > available) { + exception = not_enough_items; + } + if (available == quantity) { + player.items[num] = null; + player.esize++; + } else { + player.items[num].q -= quantity; + } + player.citems[num] = cache_item(player.items[num]); } -function consume(player,num,quantity) -{ - var available=player.items[num].q||1; - if(quantity>available) exception=not_enough_items; - if(available==quantity) player.items[num]=null,player.esize++; - else player.items[num].q-=quantity; - player.citems[num]=cache_item(player.items[num]); +function consume_one(player, num) { + consume(player, num, 1); } -function consume_one(player,num) -{ - consume(player,num,1); +function consume_one_by_id(player, id) { + for (var i = 0; i < player.items.length; i++) { + if (player.items[i] && player.items[i].name == id) { + consume_one(player, i); + return true; + } + } + return false; } -function consume_one_by_id(player,id) -{ - for(var i=0;i 0.00001) && + Math.random() / share / player.luckm / monster.level / monster_mult < item[0]) || + mode.drop_all + ) { + // /hp_mult - removed [13/07/18] + drop_item_logic(drop, item, is_in_pvp(player, 1)); + } + }); + } + if (monster.drops) { + monster.drops.forEach(function (item) { + if ( + ((!monster.temp || item[0] > 0.00001) && + Math.random() / share / player.luckm / monster.level / monster_mult < item[0]) || + mode.drop_all + ) { + // /hp_mult - removed [13/07/18] + drop_item_logic(drop, item, is_in_pvp(player, 1)); + } + }); + } + if (player.tskin == "konami") { + D.drops.konami.forEach(function (item) { + if (Math.random() / share / player.luckm / monster.level < item[0] || mode.drop_all) { + drop_item_logic(drop, item, is_in_pvp(player, 1)); + } + }); + } + if (player.p.first && !player.p.first_drop) { + player.p.first_drop = true; + drop.gold += 100000; + drop.items.push(create_new_item("ringsj")); + drop.items.push(create_new_item("ringsj")); + drop.items.push(create_new_item("ringsj")); + drop.items.push(create_new_item("hpbelt")); + drop.items.push(create_new_item("gem0")); + } + if (Math.random() < D.drops.gold.x10) { + drop.gold *= 10; + chest = "chest4"; + } // previously 12 + if (Math.random() < D.drops.gold.x50) { + drop.gold *= 50; + chest = "chest5"; + } // previously 200 + if (drop.items.length || drop.cash) { + chest = "chest6"; + } + drop.date = new Date(); + if (player.party) { + var owners = []; + parties[player.party].forEach(function (name) { + var current = players[name_to_id[name]]; + if (current && !owners.includes(current.owner)) { + owners.push(current.owner); + } + }); + party_emit( + player.party, + "drop", + { + x: drop.x, + y: drop.y, + items: drop.items.length, + chest: chest, + id: drop_id, + party: player.party, + map: drop.map, + owners: owners, + }, + { instance: player.in }, + ); + } else { + player.socket.emit("drop", { + x: drop.x, + y: drop.y, + items: drop.items.length, + chest: chest, + id: drop_id, + map: drop.map, + owners: [player.owner], + }); + } } -function drop_something(player,monster,share) -{ - if(monster.pet || monster.trap) return; - achievement_logic_monster_kill(player,monster); - share=(share===undefined)&&1||(share||0); - // console.log("share: "+share); - var drop_id=randomStr(30),drop,chest="chest3",hp_mult=1,drop_norm=1000,global_mult=monster.mult,monster_mult=monster.mult; // originally: G.maps[player.map] && G.maps[player.map].drop_norm [31/01/18] - var GOLD=D.monster_gold[monster.type]; - if(B.use_pack_golds && monster.gold) GOLD=monster.gold; - if(drop_norm) hp_mult=monster.max_hp/drop_norm; - - drop=chests[drop_id]={items:[],cash:0}; - drop.gold=round(1+GOLD*D.drops.gold.base*share+Math.random()*GOLD*D.drops.gold.random*share)*monster.level*monster.mult||0; // previously 0.75 - if(monster.extra_gold) drop.egold=(drop.egold||0)+max(0,monster.extra_gold); - if(monster.outgoing) drop.egold=(drop.egold||0)+min(monster.outgoing*B.m_outgoing_gmult,G.monsters[monster.type].hp*0.048*(gameplay=="hardcore"&&50||1)); - if(drop.egold) drop.egold*=share; - if(monster.difficulty===0) drop.gold=drop.egold=0; - drop.x=monster.x; - drop.y=monster.y; - drop.map=monster.map; - if(monster["global"]) drop.x=player.x,drop.y=player.y,drop.map=player.map; - // if(player.level<50 && mode.low49_20xglobal) global_mult=20; - Commented out after SpadarFaar discovered/used it [16/04/19] - // console.log(global_mult); - if(monster["1hp"]) global_mult*=1000; - if(D.drops.maps.global_static && player.tskin!="konami" && B.global_drops) D.drops.maps.global_static.forEach(function(item){ - if(Math.random()/share/player.luckm/monster.luckx/global_mult0.00001) && Math.random()/share/player.luckm/monster.level/monster_mult0.00001) && Math.random()/share/player.luckm/monster.level/monster_mult 1) { + // && !m.target - it was possible to keep a high level monsters aggroed and abuse the system [21/07/23] + player.s.monsterhunt.dl = true; + level_monster(m, { delevel: true }); + } + }); + } } -function monster_hunt_logic(player,monster) -{ - var target=monster; - if(!player.s.monsterhunt || player.s.monsterhunt.sn!=region+" "+server_name) return; - if(player.s.monsterhunt.id==monster.type && player.s.monsterhunt.c) player.s.monsterhunt.c--; - if(target.level==1 && player.s.monsterhunt.id==target.type && player.s.monsterhunt.dl) - { - player.s.monsterhunt.dl=false; - get_monsters(player.s.monsterhunt.id).forEach(function(m){ - if(!player.s.monsterhunt.dl && m.level>1) // && !m.target - it was possible to keep a high level monsters aggroed and abuse the system [21/07/23] - { - player.s.monsterhunt.dl=true; - level_monster(m,{delevel:true}); - } - }); - } +function calculate_monster_score(player, monster, share) { + var score = min(1, monster.mult * 2.2); + var divider = 1; + if (!share) { + share = 0; + } + if (monster.cooperative) { + divider = 2; + } + for (var id in players) { + var current = players[id]; + if (current.id == player.id) { + continue; + } + if (current.owner == player.owner && current.type == "merchant" && simple_distance(current, player) < 600) { + score -= 0.2 / divider; + } + if ( + current.party && + current.party == player.party && + current.type == "merchant" && + simple_distance(current, player) < 600 + ) { + score -= 0.1 / divider; + } else if ( + current.owner == player.owner && + current.party == player.party && + simple_distance(current, player) < 600 && + current.type != "merchant" + ) { + score += 0.3 / divider; + } else if ( + current.owner == player.owner && + current.party && + current.party == player.party && + current.type != "merchant" + ) { + score += 0.3 / divider; + } // originally 0.25 + } + if (simple_distance(player, monster) > 600 && share < 0.01) { + score -= 0.3 / divider; + } + if (player.type == "merchant" && player.party) { + score /= 2; + } + if (score < 0) { + score = 0; + } + if (gameplay == "hardcore") { + score *= 10000; + } + return score; } -function calculate_monster_score(player,monster,share) -{ - var score=min(1,monster.mult*2.2); - var divider=1; - if(!share) share=0; - if(monster.cooperative) divider=2; - for(var id in players) - { - var current=players[id]; - if(current.id==player.id) continue; - if(current.owner==player.owner && current.type=="merchant" && simple_distance(current,player)<600) score-=0.2/divider; - if(current.party && current.party==player.party && current.type=="merchant" && simple_distance(current,player)<600) score-=0.1/divider; - else if(current.owner==player.owner && current.party==player.party && simple_distance(current,player)<600 && current.type!="merchant") score+=0.3/divider; - else if(current.owner==player.owner && current.party && current.party==player.party && current.type!="merchant") score+=0.3/divider; // originally 0.25 - } - if(simple_distance(player,monster)>600 && share<0.01) - { - score-=0.3/divider; - } - if(player.type=="merchant" && player.party) score/=2; - if(score<0) score=0; - if(gameplay=="hardcore") score*=10000; - return score; +function issue_monster_awards(monster) { + var total = 0.1; + for (var name in monster.points) { + var current = players[name_to_id[name]]; + if (current) { + // && current.map==monster.map + total += max(0, monster.points[name]); + } + } + for (var name in monster.points) { + var current = players[name_to_id[name]]; + var share = max(0, monster.points[name]) / total; + if (current && share > 0.0025) { + // && current.map==monster.map + if (monster.rbuff && G.conditions[monster.rbuff]) { + current.s[monster.rbuff] = { ms: G.conditions[monster.rbuff].duration }; + } + if (monster.cbuff) { + for (var i = 0; i < monster.cbuff.length; i++) { + if (current.level <= monster.cbuff[i][0] && G.conditions[monster.cbuff[i][1]]) { + current.s[monster.cbuff[i][1]] = { ms: G.conditions[monster.cbuff[i][1]].duration }; + break; + } + } + } + if (G.monsters[monster.type]["1hp"]) { + drop_something(current, monster); + } else { + drop_something(current, monster, share); + } + var score = calculate_monster_score(current, monster, share); + current.p.stats.monsters[monster.type] = (current.p.stats.monsters[monster.type] || 0) + 1; + current.p.stats.monsters_diff[monster.type] = (current.p.stats.monsters_diff[monster.type] || 0) + (score - 1); + monster_hunt_logic(current, monster, share); + if (current.type == "merchant") { + continue; + } + current.xp += round(monster.xp * share * current.xpm); + if (current.t) { + current.t.xp += round(monster.xp * share * current.xpm); + } + delete current.s.coop; + resend(current, "u+cid"); + } + } } -function issue_monster_awards(monster) -{ - var total=0.1; - for(var name in monster.points) - { - var current=players[name_to_id[name]]; - if(current) // && current.map==monster.map - total+=max(0,monster.points[name]); - } - for(var name in monster.points) - { - var current=players[name_to_id[name]]; - var share=max(0,monster.points[name])/total; - if(current && share>0.0025) // && current.map==monster.map - { - if(monster.rbuff && G.conditions[monster.rbuff]) current.s[monster.rbuff]={ms:G.conditions[monster.rbuff].duration}; - if(monster.cbuff) for(var i=0;i= 10) { + lost_gold = 1000; + } + if (target.level >= 20) { + lost_gold = 5000; + } + if (target.level >= 30) { + lost_gold = 12500; + } + if (target.level >= 40) { + lost_gold = 25000; + } + if (target.level >= 50) { + lost_gold = 50000; + } + if (target.level >= 55) { + lost_gold = 75000; + } + if (target.level >= 60) { + lost_gold = 125000; + } + if (target.level >= 65) { + lost_gold = 250000; + } + if (target.level >= 70) { + lost_gold = 500000; + } + if (target.level >= 75) { + lost_gold = 1000000; + } + + lost_gold = min(lost_gold, max(attacker.gold, 10000) * 4) || 0; + lost_xp = round(min(lost_xp, max(attacker.xp / 15.0, 50000))) || 0; + + if (G.maps[attacker.map].safe_pvp && !is_pvp) { + lost_gold = 0; + lost_xp = 0; + } + + if (gameplay == "hardcore") { + lost_gold = round(target.gold * 0.8); + lost_xp = target.xp; + for (var i = 1; i <= B.hlevel_loss; i++) { + if (target.level - i > 0) { + lost_xp += G.levels[target.level - i + ""]; + } + } + target.level = max(1, target.level - B.hlevel_loss); + } + + lost_gold = min(target.gold, lost_gold); + gain_gold = round(lost_gold * 0.9); + + if (target.type == "merchant") { + lost_xp = 0; + } + if (gameplay != "hardcore" && gameplay != "test" && is_same(attacker, target, 1)) { + lost_gold = gain_gold = 0; + lost_xp = 0; + } + + if (!is_same(attacker, target, 1)) { + attacker.kills++; + } + + if (mode.log_pvp) { + appengine_log("pvp", attacker.name + " pwned " + target.name + " For " + lost_gold + " Gold " + lost_xp + " XP"); + } + pwns[pend++ % 200] = [attacker.name, target.name]; + + target.socket.emit("game_log", { message: "Slain by " + attacker.name, color: "#F12F02" }); + var lost_shells = 0; + var psize = 1; + if (lost_xp) { + for (var i = 0; i < target.isize; i++) { + if (target.items[i] && target.items[i].name == "xptome") { + lost_shells = 2; + lost_xp = floor(lost_xp / 50); + consume_one(target, i); + target.to_reopen = true; + target.socket.emit("game_log", { message: "A tome fades away", color: "#B5C09C" }); + break; + } + } + } + + target.gold -= lost_gold; + target.xp -= lost_xp; + if (target.xp < 0) { + target.xp = 0; + } + target.socket.emit("game_log", "Lost " + to_pretty_num(lost_gold) + " gold"); + target.socket.emit("game_log", "Lost " + to_pretty_num(lost_xp) + " experience"); + target.socket.emit("disappearing_text", { + message: "-" + lost_xp, + x: target.x, + y: target.y - 30, + args: { color: colors.party_xp }, + }); + + if (gameplay == "hardcore") { + drop_something_hardcore(attacker, target); + } + if (is_in_pvp(target)) { + drop_something_pvp(attacker, target); + } + + if (!attacker.party) { + if (attacker.type == "merchant") { + lost_xp = 0; + } + attacker.gold += gain_gold; + attacker.xp += round(lost_xp * 0.95); + attacker.socket.emit("game_log", { message: "Pwned " + target.name, color: "#67C051" }); + attacker.socket.emit("game_log", "Looted " + to_pretty_num(gain_gold) + " gold"); + attacker.socket.emit("disappearing_text", { + message: "+" + gain_gold, + x: target.x, + y: target.y - 40, + args: { color: "+gold", size: "large" }, + }); + attacker.socket.emit("game_log", "Gained " + to_pretty_num(round(lost_xp * 0.95)) + " experience"); + attacker.socket.emit("disappearing_text", { + message: "+" + lost_xp, + x: target.x, + y: target.y - 30, + args: { color: colors.party_xp }, + }); + if (lost_shells) { + add_shells(attacker, lost_shells, "xptome"); + } + } else { + var name = attacker.name; + lost_xp = floor((lost_xp * 0.92) / parties[attacker.party].length); + gain_gold = floor(gain_gold / parties[attacker.party].length); + if (lost_shells) { + add_shells(attacker, lost_shells, "xptome"); + } + // ,lost_shells=ceil(lost_shells/parties[attacker.party].length) + parties[attacker.party].forEach(function (a_name) { + var attacker = players[name_to_id[a_name]]; + attacker.gold += gain_gold; + if (attacker.type != "merchant") { + attacker.xp += lost_xp; + } + attacker.socket.emit("game_log", { message: name + " pwned " + target.name, color: "#67C051" }); + attacker.socket.emit("game_log", "Looted " + to_pretty_num(gain_gold) + " gold"); + attacker.socket.emit("disappearing_text", { + message: "+" + gain_gold, + x: target.x, + y: target.y - 40, + args: { color: "+gold", size: "large" }, + }); + if (attacker.type != "merchant") { + attacker.socket.emit("game_log", "Gained " + to_pretty_num(lost_xp) + " experience"); + attacker.socket.emit("disappearing_text", { + message: "+" + lost_xp, + x: target.x, + y: target.y - 30, + args: { color: colors.party_xp }, + }); + } + }); + } } -function issue_player_award(attacker,target) -{ - if(attacker.map=="duelland") - { - if(target.duel) duel_defeat(target); - return; - } - if(attacker.map=="abtesting" && attacker.team!=target.team) - { - if(E.abtesting) - { - E.abtesting[attacker.team]++; - item_achievement_increment(attacker,attacker.slots.orb,"abtesting"); - // instance_emit(attacker.map,"game_event",{name:"ab_score",A:E.abtesting.A,B:E.abtesting.B,win:attacker.name,lose:target.name,color:attacker.team}); - broadcast_e(); - }; - return; - } - var lost_xp=floor(min(max(target.max_xp*0.01/10,target.xp*0.02/10),target.xp)); - - var lost_gold=100,gain_gold=100; - if(target.level>=10) lost_gold=1000; - if(target.level>=20) lost_gold=5000; - if(target.level>=30) lost_gold=12500; - if(target.level>=40) lost_gold=25000; - if(target.level>=50) lost_gold=50000; - if(target.level>=55) lost_gold=75000; - if(target.level>=60) lost_gold=125000; - if(target.level>=65) lost_gold=250000; - if(target.level>=70) lost_gold=500000; - if(target.level>=75) lost_gold=1000000; - - lost_gold=min(lost_gold, max(attacker.gold,10000)*4 )||0; - lost_xp=round(min(lost_xp, max(attacker.xp/15.0,50000) ))||0; - - if(G.maps[attacker.map].safe_pvp && !is_pvp) - { - lost_gold=0; - lost_xp=0; - } - - - if(gameplay=="hardcore") - { - lost_gold=round(target.gold*0.8); - lost_xp=target.xp; - for(var i=1;i<=B.hlevel_loss;i++) - if(target.level-i>0) - lost_xp+=G.levels[(target.level-i)+""]; - target.level=max(1,target.level-B.hlevel_loss); - } - - lost_gold=min(target.gold,lost_gold); - gain_gold=round(lost_gold*0.9); - - if(target.type=="merchant") lost_xp=0; - if(gameplay!="hardcore" && gameplay!="test" && is_same(attacker,target,1)) lost_gold=gain_gold=0,lost_xp=0; - - if(!is_same(attacker,target,1)) attacker.kills++; - - if(mode.log_pvp) appengine_log("pvp",attacker.name+" pwned "+target.name+" For "+lost_gold+" Gold "+lost_xp+" XP"); - pwns[(pend++)%200]=[attacker.name,target.name]; - - - target.socket.emit("game_log",{message:"Slain by "+attacker.name,color:"#F12F02"}); - var lost_shells=0,psize=1; - if(lost_xp) - for(var i=0;i 10 + ) { + attacker.socket.emit("game_response", { response: "attack_failed", id: target.id, reason: "level" }); + return { failed: true, reason: "level_gap", place: atype, id: target.id }; + } + + var dist = distance(attacker, target); + var range = 0; + var def = { hid: attacker.id, source: atype, projectile: null }; + + // PROJECTILE LOGIC + if (attacker.is_monster) { + def.projectile = G.monsters[attacker.type].projectile || "stone"; + } + if (attacker.is_player && G.classes[attacker.type].projectile) { + def.projectile = G.classes[attacker.type].projectile; + } + if (attacker.projectile) { + def.projectile = attacker.projectile; + } + if (attacker.slots && attacker.slots.mainhand && G.items[attacker.slots.mainhand.name].projectile) { + def.projectile = G.items[attacker.slots.mainhand.name].projectile; + } + if (attacker.tskin == "konami") { + def.projectile = "stone_k"; + } + if ((atype != "attack" || !def.projectile || atype == "heal") && G.skills[atype].projectile) { + def.projectile = G.skills[atype].projectile; + } + + // DAMAGE TYPE LOGIC + if (attacker.is_monster && G.monsters[attacker.type].damage_type) { + info.damage_type = G.monsters[attacker.type].damage_type; + } + if (attacker.is_player && G.classes[attacker.type].damage_type) { + info.damage_type = G.classes[attacker.type].damage_type; + } + if (attacker.is_player && attacker.slots.mainhand && G.items[attacker.slots.mainhand.name].damage_type) { + info.damage_type = G.items[attacker.slots.mainhand.name].damage_type; + } + if (atype != "attack" && G.skills[atype].damage_type) { + info.damage_type = G.skills[atype].damage_type; + } + + // PROCS + if (!attacker.is_player || G.skills[atype].procs) { + info.procs = true; + } + + // HEAL / POSITIVE + if ( + G.skills[atype].heal || + (attacker.is_player && attacker.slots.mainhand && attacker.slots.mainhand.name == "cupid") + ) { + info.heal = true; + info.positive = true; + } + + if ((attacker.is_player && target.is_player && !is_in_pvp(target, true) && !info.positive) || target.npc) { + attacker.socket.emit("game_response", { response: "attack_failed", id: target.id }); + return { failed: true, reason: "no_pvp", place: atype, id: target.id }; + } + + // COMMON RANGE CHECKS + if (G.skills[atype].use_range) { + range = attacker.range; + } + if (G.skills[atype].range) { + range = G.skills[atype].range; + } + + // DAMAGE + if (G.skills[atype].damage) { + attack = G.skills[atype].damage; + } + + // SKILL MP + if (G.skills[atype].mp) { + mp_cost = G.skills[atype].mp; + } + + if (attacker.is_player && range && !attacker.is_npc) { + if (dist > range + attacker.xrange) { + attacker.socket.emit("game_response", { response: "too_far", id: target.id, dist: dist }); + return { failed: true, reason: "too_far", place: atype, id: target.id, dist: dist }; + } + if (dist > range) { + attacker.xrange += range - dist; + } + } + + if (info.procs && attacker.s.poisonous) { + info.conditions.push("poisoned"); + } + + if (atype == "heal") { + attack = attacker.heal || attacker.attack; + mp_cost = attacker.mp_cost; + } else if ( + attacker.is_player && + (atype == "attack" || atype == "3shot" || atype == "5shot" || atype == "cleave" || atype == "shadowstrike") + ) { + var mp_mult = 1; + var att_mult = 1; + if (atype == "3shot") { + mp_mult = 0; + att_mult = 0.7; + } + if (atype == "5shot") { + mp_mult = 0; + att_mult = 0.5; + } + if (atype == "cleave") { + mp_mult = 0.02; + info.lines = false; + att_mult = 0.1 + Math.random() * 0.8; + def.aoe = true; + attacker.first = true; + } + if (atype == "shadowstrike") { + mp_mult = 0; + info.lines = false; + att_mult = (0.2 + Math.random() * 1.2) * ((Math.random() < 0.05 && 12) || 1); + def.aoe = true; + } + if (attacker.mp < attacker.mp_cost * mp_mult) { + attacker.socket.emit("game_response", { response: "no_mp" }); + return { failed: true, reason: "no_mp", place: atype, id: target.id }; + } + mp_cost = parseInt(attacker.mp_cost * mp_mult); + attack = attacker.attack * att_mult; + } else if (atype == "piercingshot") { + info.apiercing = 500; + attack = attacker.attack * 0.75; + } else if (atype == "partyheal") { + if (attacker.level >= 80) { + attack = 800; + } else if (attacker.level >= 72) { + attack = 720; + } else if (attacker.level >= 60) { + attack = 600; + } else { + attack = 400; + } + } else if (atype == "selfheal") { + if (attacker.level >= 80) { + attack = 800; + } else if (attacker.level >= 72) { + attack = 720; + } else if (attacker.level >= 60) { + attack = 600; + } else { + attack = 400; + } + } else if (atype == "taunt") { + if ( + target.is_monster && + target.target && + get_player(target.target) && + is_same(attacker, get_player(target.target), 1) + ) { + stop_pursuit(target, { redirect: true, cause: "taunt redirect" }); + target_player(target, attacker); + } + } else if (atype == "curse") { + if (distance(attacker, target) > min(200, attacker.range * 5 + 20)) { + attacker.socket.emit("game_response", { response: "too_far", id: target.id, dist: dist }); + return { failed: true, reason: "too_far", place: atype, id: target.id, dist: dist }; + } + attack = 0; + info.conditions.push("cursed"); + } else if (atype == "burst") { + if (attacker.mp < 1) { + attacker.socket.emit("game_response", { response: "no_mp" }); + return { failed: true, reason: "no_mp", place: atype, id: target.id }; + } + attack = attacker.mp * G.skills.burst.ratio; + mp_cost = attacker.mp; + } else if (atype == "cburst") { + var mp_cutoff = attacker.next_mp; + var mp = attacker.next_mp; + if (atype == "cburst") { + mp = mp_cutoff = attacker.next_mp; + } + if (attacker.mp < mp_cutoff) { + attacker.socket.emit("game_response", { response: "no_mp" }); + return { failed: true, reason: "no_mp", place: atype, id: target.id }; + } + attack = mp * G.skills.cburst.ratio; + mp_cost = mp; + attacker.first = true; + } else if (atype == "purify") { + def.purify = true; + } else if (atype == "frostball") { + info.conditions.push("frozen"); + } else if (atype == "fireball") { + info.conditions.push("burned"); + } else if (atype == "supershot") { + if (attacker.in != target.in || distance(attacker, target, true) > 3 * attacker.range + 20) { + attacker.socket.emit("game_response", { response: "too_far", id: target.id, dist: dist }); + return { failed: true, reason: "too_far", place: atype, id: target.id, dist: dist }; + } + attack = attacker.attack * 1.5; + // if(attacker.slots.mainhand && attacker.slots.mainhand.name=="cupid") info.heal=info.positive=true,def.projectile=G.items.cupid.projectile; + } else if (atype == "snowball") { + if (target.is_monster && attacker.a.freeze && attacker.a.freeze.attr0) { + attack += 10 * attacker.a.freeze.attr0 * attacker.a.freeze.attr0; + } + if (target.is_monster || is_in_pvp(attacker, true)) { + info.conditions.push("frozen"); + } + } else if (atype == "quickpunch" || atype == "quickstab" || atype == "smash") { + attack = attacker.attack * G.skills[atype].damage_multiplier; + } else if (atype == "mentalburst") { + if ( + attacker.in != target.in || + distance(attacker, target, true) > + attacker.range * G.skills.mentalburst.range_multiplier + G.skills.mentalburst.range_bonus + ) { + attacker.socket.emit("game_response", { response: "too_far", id: target.id, dist: dist }); + return { failed: true, reason: "too_far", place: atype, id: target.id, dist: dist }; + } + attack = attacker.attack * G.skills[atype].damage_multiplier; + } else if (atype == "poisonarrow") { + info.conditions.push("poisoned"); + } else if (attacker.is_monster) { + var rng = parseInt(Math.random() * 100 - 50); + if (attacker.s.poisonous) { + info.conditions.push("poisoned"); + } + attacker.last.attack = future_ms(rng); + } + + if (atype != "attack" && target.immune && (!G.skills[atype] || !G.skills[atype].pierces_immunity)) { + disappearing_text(target.socket, target, "IMMUNE!", { xy: 1, color: "evade", nv: 1, from: attacker.id }); + return { failed: true, reason: "skill_immune", place: atype, id: target.id }; + } + + if ( + (!info.positive && + ((attacker.is_player && G.maps[attacker.map].safe) || + (!mode.friendly_fire && + (!attacker.team || attacker.team != target.team) && + ((attacker.party && attacker.party == target.party) || + (attacker.guild && attacker.guild == target.guild))))) || + (attacker.map == "duelland" && (!attacker.duel || !target.duel)) + ) { + attacker.socket.emit("game_response", { response: "friendly", id: target.id }); + return { failed: true, reason: "friendly", place: atype, id: target.id }; + } + + direction_logic(attacker, target); + + if (mp_cost && attacker.first && !attacker.is_npc) { + consume_mp(attacker, mp_cost, atype != "attack" && atype != "heal" && target); + } + attacker.first = false; + + if (attacker.is_player) { + attacker.c = {}; + attacker.to_resend = "u+cid"; + if (attacker.p && attacker.p.stand) { + attacker.p.stand = false; + } + step_out_of_invis(attacker); + if (attacker.type == "merchant") { + var gold = parseInt(attack * 2); + attack = parseInt(min(attack, attacker.gold / 2)); + attacker.gold = max(0, attacker.gold - gold); + attacker.to_resend += "+reopen"; + } + } + + if (info.procs) { + ["crit", "critdamage", "explosion", "blast", "lifesteal", "manasteal"].forEach(function (p) { + if (attacker[p]) { + info[p] = attacker[p]; + } + }); + } + ["apiercing", "rpiercing", "miss"].forEach(function (p) { + if (attacker[p]) { + info[p] = attacker[p]; + } + }); + if ( + info.procs && + attacker.a.freeze && + Math.random() < (attacker.a.freeze.attr0 * (G.maps[attacker.map].freeze_multiplier || 1)) / 100.0 + ) { + info.conditions.push("frozen"); + } + if ( + info.procs && + attacker.a.burn && + info.procs && + Math.random() < (attacker.a.burn.attr0 * (G.maps[attacker.map].burn_multiplier || 1)) / 100.0 + ) { + info.conditions.push("burned"); + } + if (info.procs && attacker.a.weave && info.procs) { + info.conditions.push("woven"); + } + if (info.procs && attacker.stun && Math.random() < attacker.stun / 100.0 && info.damage_type == "physical") { + info.conditions.push("stunned"); + } + + var pid = randomStr(6); + var eta = 0; + info.first_attack = info.attack = attack; + info.def = def; + def.damage_type = info.damage_type; + def.pid = pid; + info.attacker = attacker; + info.target = target; + info.atype = atype; + projectiles[pid] = info; + + var action = { + attacker: attacker.id, + target: target.id, + type: atype, + source: atype, + x: target.x, + y: target.y, + eta: 400, + m: target.m, + pid: pid, + }; + + if (def.projectile) { + action.projectile = def.projectile; + if (target.is_monster && G.monsters[target.type].escapist) { + var dampened = false; + for (var id in instances[target.in].monsters) { + var m = instances[target.in].monsters[id]; + if (m.type == "fieldgen0" && point_distance(target.x, target.y, m.x, m.y) < 300) { + target.s.dampened = { ms: 2000 }; + add_condition(target, "dampened", { ms: 2000 }); + dampened = true; + } + } + if (!dampened) { + port_monster(target, random_place(target.map)); + } + } + } + + if (!(G.projectiles[def.projectile] && G.projectiles[def.projectile].instant)) { + eta = (1000 * dist) / G.projectiles[def.projectile].speed; + } else { + action.instant = true; + } + + info.eta = future_ms(eta); + + if (info.heal) { + action.heal = attack; + } else if (attack) { + action.damage = attack; + } + if (info.positive) { + action.positive = true; + } + if (info.conditions.length) { + action.conditions = info.conditions; + } + info.action = action; + xy_emit(attacker, "action", action, target.id); + + if (!eta) { + projectiles_loop(); + } + + action.response = "data"; + action.place = atype; + + return action; } -function commence_attack(attacker,target,atype) -{ - var attack=attacker.attack,mp_cost=0; - var info={apiercing:0,damage_type:attacker.damage_type,heal:false,lines:true,positive:false,first_attack:0,procs:false,conditions:[]}; // server projectile - - if(!G.skills[atype].hostile) info.positive=true; - - // TARGET CHANGE - if(attacker.is_player) attacker.target=target.id; - - // FAILURE SCENARIOS - if(attacker.type=="merchant" && !G.skills[atype].merchant_use && !(atype=="attack" && attacker.slots.mainhand && G.items[attacker.slots.mainhand.name].wtype=="dartgun")) - { - attacker.socket.emit("game_response",{response:"attack_failed",id:target.id}); - return {failed:true,reason:"merchant",place:atype,id:target.id} - } - if(mode.pvp_level_gap && attacker.is_player && target.is_player && !info.positive && abs(attacker.level-target.level)>10) - { - attacker.socket.emit("game_response",{response:"attack_failed",id:target.id,reason:"level"}); - return {failed:true,reason:"level_gap",place:atype,id:target.id}; - } - - var dist=distance(attacker,target),range=0; - var def={hid:attacker.id,source:atype,projectile:null}; - - // PROJECTILE LOGIC - if(attacker.is_monster) def.projectile=G.monsters[attacker.type].projectile||"stone"; - if(attacker.is_player && G.classes[attacker.type].projectile) def.projectile=G.classes[attacker.type].projectile; - if(attacker.projectile) def.projectile=attacker.projectile; - if(attacker.slots && attacker.slots.mainhand && G.items[attacker.slots.mainhand.name].projectile) def.projectile=G.items[attacker.slots.mainhand.name].projectile; - if(attacker.tskin=="konami") def.projectile="stone_k"; - if((atype!="attack" || !def.projectile || atype=="heal") && G.skills[atype].projectile) def.projectile=G.skills[atype].projectile; - - // DAMAGE TYPE LOGIC - if(attacker.is_monster && G.monsters[attacker.type].damage_type) info.damage_type=G.monsters[attacker.type].damage_type; - if(attacker.is_player && G.classes[attacker.type].damage_type) info.damage_type=G.classes[attacker.type].damage_type; - if(attacker.is_player && attacker.slots.mainhand && G.items[attacker.slots.mainhand.name].damage_type) info.damage_type=G.items[attacker.slots.mainhand.name].damage_type; - if(atype!="attack" && G.skills[atype].damage_type) info.damage_type=G.skills[atype].damage_type; - - // PROCS - if(!attacker.is_player || G.skills[atype].procs) info.procs=true; - - // HEAL / POSITIVE - if(G.skills[atype].heal || attacker.is_player && attacker.slots.mainhand && attacker.slots.mainhand.name=="cupid") - info.heal=true,info.positive=true; - - if(attacker.is_player && target.is_player && !is_in_pvp(target,true) && !info.positive || target.npc) - { - attacker.socket.emit("game_response",{response:"attack_failed",id:target.id}); - return {failed:true,reason:"no_pvp",place:atype,id:target.id} - } - - // COMMON RANGE CHECKS - if(G.skills[atype].use_range) range=attacker.range; - if(G.skills[atype].range) range=G.skills[atype].range; - - // DAMAGE - if(G.skills[atype].damage) attack=G.skills[atype].damage; - - // SKILL MP - if(G.skills[atype].mp) mp_cost=G.skills[atype].mp; - - if(attacker.is_player && range && !attacker.is_npc) - { - if(dist>range+attacker.xrange) - { - attacker.socket.emit("game_response",{response:"too_far",id:target.id,dist:dist}); - return {failed:true,reason:"too_far",place:atype,id:target.id,dist:dist}; - } - if(dist>range) attacker.xrange+=range-dist; - } - - if(info.procs && attacker.s.poisonous) info.conditions.push("poisoned"); - - if(atype=="heal") - { - attack=attacker.heal||attacker.attack; - mp_cost=attacker.mp_cost; - } - else if(attacker.is_player && (atype=="attack" || atype=="3shot" || atype=="5shot" || atype=="cleave" || atype=="shadowstrike")) - { - var mp_mult=1,att_mult=1; - if(atype=="3shot") mp_mult=0,att_mult=0.7; - if(atype=="5shot") mp_mult=0,att_mult=0.5; - if(atype=="cleave") mp_mult=0.02,info.lines=false,att_mult=0.1+Math.random()*0.8,def.aoe=true,attacker.first=true; - if(atype=="shadowstrike") mp_mult=0,info.lines=false,att_mult=(0.2+Math.random()*1.2)*((Math.random()<0.05)&&12||1),def.aoe=true; - if(attacker.mp=80) attack=800; - else if(attacker.level>=72) attack=720; - else if(attacker.level>=60) attack=600; - else attack=400; - } - else if(atype=="selfheal") - { - if(attacker.level>=80) attack=800; - else if(attacker.level>=72) attack=720; - else if(attacker.level>=60) attack=600; - else attack=400; - } - else if(atype=="taunt") - { - if(target.is_monster && target.target && get_player(target.target) && is_same(attacker,get_player(target.target),1)) - { - stop_pursuit(target,{redirect:true,cause:"taunt redirect"}); - target_player(target,attacker); - } - } - else if(atype=="curse") - { - if(distance(attacker,target)>min(200,attacker.range*5+20)) - { - attacker.socket.emit("game_response",{response:"too_far",id:target.id,dist:dist}); - return {failed:true,reason:"too_far",place:atype,id:target.id,dist:dist} - } - attack=0; - info.conditions.push("cursed"); - } - else if(atype=="burst") - { - if(attacker.mp<1) - { - attacker.socket.emit("game_response",{response:"no_mp"}); - return {failed:true,reason:"no_mp",place:atype,id:target.id} - } - attack=attacker.mp*G.skills.burst.ratio; - mp_cost=attacker.mp; - } - else if(atype=="cburst") - { - var mp_cutoff=attacker.next_mp,mp=attacker.next_mp; - if(atype=="cburst") mp=mp_cutoff=attacker.next_mp; - if(attacker.mp3*attacker.range+20) - { - attacker.socket.emit("game_response",{response:"too_far",id:target.id,dist:dist}); - return {failed:true,reason:"too_far",place:atype,id:target.id,dist:dist}; - } - attack=attacker.attack*1.5; - // if(attacker.slots.mainhand && attacker.slots.mainhand.name=="cupid") info.heal=info.positive=true,def.projectile=G.items.cupid.projectile; - } - else if(atype=="snowball") - { - if(target.is_monster && attacker.a.freeze && attacker.a.freeze.attr0) - attack+=10*attacker.a.freeze.attr0*attacker.a.freeze.attr0; - if(target.is_monster || is_in_pvp(attacker,true)) - info.conditions.push("frozen"); - } - else if(atype=="quickpunch" || atype=="quickstab" || atype=="smash") - { - attack=attacker.attack*G.skills[atype].damage_multiplier; - } - else if(atype=="mentalburst") - { - if(attacker.in!=target.in || distance(attacker,target,true)>attacker.range*G.skills.mentalburst.range_multiplier+G.skills.mentalburst.range_bonus) - { - attacker.socket.emit("game_response",{response:"too_far",id:target.id,dist:dist}); - return {failed:true,reason:"too_far",place:atype,id:target.id,dist:dist} - } - attack=attacker.attack*G.skills[atype].damage_multiplier; - } - else if(atype=="poisonarrow") - { - info.conditions.push("poisoned"); - } - else if(attacker.is_monster) - { - var rng=parseInt(Math.random()*100-50); - if(attacker.s.poisonous) info.conditions.push("poisoned"); - attacker.last.attack=future_ms(rng); - } - - if(atype!="attack" && target.immune && (!G.skills[atype] || !G.skills[atype].pierces_immunity)) - { - disappearing_text(target.socket,target,"IMMUNE!",{xy:1,color:"evade",nv:1,from:attacker.id}); - return {failed:true,reason:"skill_immune",place:atype,id:target.id}; - } - - if(!info.positive && (attacker.is_player && G.maps[attacker.map].safe || !mode.friendly_fire && (!attacker.team || attacker.team!=target.team) && (attacker.party && attacker.party==target.party || attacker.guild && attacker.guild==target.guild)) || attacker.map=="duelland" && (!attacker.duel || !target.duel)) - { - attacker.socket.emit("game_response",{response:"friendly",id:target.id}); - return {failed:true,reason:"friendly",place:atype,id:target.id} - } - - direction_logic(attacker,target); - - if(mp_cost && attacker.first && !attacker.is_npc) consume_mp(attacker,mp_cost,atype!="attack"&&atype!="heal"&&target); - attacker.first=false; - - if(attacker.is_player) - { - attacker.c={}; - attacker.to_resend="u+cid"; - if(attacker.p && attacker.p.stand) attacker.p.stand=false; - step_out_of_invis(attacker); - if(attacker.type=="merchant") - { - var gold=parseInt(attack*2); - attack=parseInt(min(attack,attacker.gold/2)); - attacker.gold=max(0,attacker.gold-gold); - attacker.to_resend+="+reopen"; - } - } - - if(info.procs) - ["crit","critdamage","explosion","blast","lifesteal","manasteal"].forEach(function(p){if(attacker[p]) info[p]=attacker[p];}); - ["apiercing","rpiercing","miss"].forEach(function(p){if(attacker[p]) info[p]=attacker[p];}); - if(info.procs && attacker.a.freeze && Math.random() 0 && !info.heal) { + add_pdps(target, attacker, attack * B.dps_tank_mult); // "tank" + if (attacker.cooperative) { + add_coop_points(attacker, target, attack * B.dps_tank_mult); + } + } + if (attacker.is_monster && attack > 0 && !info.heal) { + attacker.outgoing += min(target.hp, attack); + } + + if ( + target.reflection && + defense == "resistance" && + Math.random() * 100 < target.reflection && + !info.heal && + attacker != target + ) { + var pid = randomStr(6); + var eta = 0; + var opid = info.action.pid; + // info.attack=ceil(attack*damage_multiplier(attacker.resistance||0))||1; + info.attack = ceil(attack * (0.9 + ((attack && Math.random() * 0.2) || 0))); // A pure reflection was requested [29/03/22] + if (!info.action.instant) { + eta = (1000 * distance(target, attacker, true)) / G.projectiles[info.action.projectile].speed; + } + info.target = attacker; + info.attacker = target; + info.eta = future_ms(eta); + projectiles[pid] = info; + info.action.pid = pid; + info.action.target = attacker.id; + info.action.attacker = target.id; + info.action.x = attacker.x; + info.action.y = attacker.y; + info.action.reflect = info.attack; + info.action.m = attacker.m; + + if ((attacker.is_player && target.is_monster) || info.reflections) { + info.reflections = (info.reflections || 0) + 1; + } + if (info.reflections >= 4 && attacker.is_player) { + item_achievement_increment(attacker, attacker.slots.chest, "reflector"); + } + if (info.reflections >= 4 && target.is_player) { + item_achievement_increment(target, target.slots.chest, "reflector"); + } + + xy_emit( + target, + "hit", + { pid: def.pid, hid: attacker.id, id: target.id, damage: 0, reflect: info.attack }, + attacker.id, + ); + return xy_emit(target, "action", info.action, attacker.id); + } + + if (attacker == target && !target.dead) { + } // reflect after dead fix [04/02/23] + else if (target.evasion && defense == "armor" && Math.random() * 100 < target.evasion) { + return xy_emit( + info.action, + "hit", + { pid: def.pid, hid: attacker.id, id: target.id, damage: 0, evade: true }, + attacker.id, + ); + } else if ( + target.dc || + target.dead || + (attacker.miss && Math.random() * 100 < attacker.miss) || + (target.avoidance && Math.random() * 100 < target.avoidance) + ) { + return xy_emit( + info.action, + "hit", + { pid: def.pid, hid: attacker.id, id: target.id, damage: 0, miss: true }, + attacker.id, + ); + } else if ( + target.m != info.action.m || + point_distance(target.x, target.y, info.action.x, info.action.y) > 72 * ((info.heal && 1.5) || 1) + ) { + return xy_emit( + info.action, + "hit", + { + pid: def.pid, + hid: attacker.id, + id: target.id, + damage: 0, + avoid: true, + x: info.action.x, + y: info.action.y, + map: info.attacker.map, + in: info.attacker.in, + }, + attacker.id, + ); + } + + if (info.positive || !info.procs) { + combo = 0; + } + if (combo && target.targets > 3) { + if (!target.last_combo || ssince(target.last_combo) > 5) { + target.combo = 1; + } + combo += target.combo; + target.last_combo = new Date(); + target.combo += 1; + def.mobbing = target.combo; + } + if ( + combo && + target.is_player && + instances[target.in].pmap[target.last_hash] && + Object.keys(instances[target.in].pmap[target.last_hash]).length > 1 + ) { + var combo_check = false; + for (var id in instances[target.in].pmap[target.last_hash]) { + var ntarget = instances[target.in].pmap[target.last_hash][id]; + if ( + target.id == ntarget.id || + is_invinc(ntarget) || + (!is_same(target, ntarget, 2) && !is_in_pvp(target)) || + attacker.id == ntarget.id + ) { + continue; + } //is_same(attacker,ntarget,1) + //!is_same(target,ntarget,1) && !is_in_pvp(target) to prevent people killing other people in non-pvp servers + //!is_same(target,ntarget,2) makes people kill others in coop fights + combo_check = true; + break; + } + if (combo_check) { + targets = []; + combo = 0; + for (var id in instances[target.in].pmap[target.last_hash]) { + var ntarget = instances[target.in].pmap[target.last_hash][id]; + if (is_invinc(ntarget) || attacker.id == ntarget.id || !is_same(target, ntarget, 2)) { + continue; + } //is_same(attacker,ntarget,1) + targets.push([ntarget, "stack"]); + if (!ntarget.last_combo || ssince(ntarget.last_combo) > 5) { + ntarget.combo = 1; + } + combo += ntarget.combo; + ntarget.last_combo = new Date(); + ntarget.combo += 1; + } + if (targets.length > 1) { + def.stacked = []; + targets.forEach(function (t) { + def.stacked.push(t[0].id); + }); + } + } + } + + if (combo > 10) { + combo_m = combo / 4.0; + } else if (combo > 1) { + combo_m = [1, 1.6, 1.62, 1.64, 1.7, 1.72, 1.75, 1.8, 1.9, 2, 2, 2, 2][combo]; + } + combo_m = min(combo_m, max(300 / attack, 1.2)); // previously 2.4 + + if ( + !info.positive && + ((info.explosion && info.damage_type == "physical") || (info.blast && info.damage_type == "magical")) + ) { + var intensity = info.blast; + var defense = "resistance"; + if (info.damage_type == "physical") { + intensity = info.explosion; + defense = "armor"; + } + var radius = intensity / 3.6; + if (is_in_pvp(attacker) || attacker.is_monster) { + for (var id in instances[target.in].players) { + var target = instances[target.in].players[id]; + if (target.npc) { + continue; + } + if (target.id != otarget.id && distance(target, otarget) < radius) { + targets.push([target, "splash", (damage_multiplier(target[defense] || 0) * intensity) / 100.0]); + } + } + } + if (attacker.is_player) { + for (var id in instances[target.in].monsters) { + var target = instances[target.in].monsters[id]; + if (target.avoidance && Math.random() * 100 < target.avoidance) { + xy_emit( + info.action, + "hit", + { pid: def.pid, hid: attacker.id, id: target.id, damage: 0, miss: true }, + attacker.id, + ); + continue; + } + if (target.id != otarget.id && distance(target, otarget) < radius) { + targets.push([target, "splash", (damage_multiplier(target[defense] || 0) * intensity) / 100.0]); + } + } + } + } + + targets.forEach(function (target_def) { + delete def.splash; + delete def.dreturn; + delete def.kill; + delete def.unintentional; + + var target = target_def[0]; + if (target_def[1] != "normal") { + def.unintentional = true; + } + if (target_def[1] == "splash") { + def.splash = true; + } + + def.id = target.id; + + if (info.heal) { + if (first) { + o_attack = attack = -ceil( + B.heal_multiplier * + attack * + (0.9 + Math.random() * 0.2) * + damage_multiplier(((target[defense] || 0) - (attacker[pierce] || 0)) / 2.0), + ); + if (target.s.poisoned) { + attack = round(attack * 0.25); + } + } else { + attack = o_attack; + } + def.heal = -attack; + if (attacker.is_player && target.type == "ghost" && !target.s.healed) { + target.s.healed = { ms: 960 * 60 * 60 * 1000 }; + drop_one_thing(attacker, "essenceoflife", { x: target.x, y: target.y, chest: "chestp" }); + } + if (G.monsters[attacker.type] && G.monsters[attacker.type].goldsteal && target.is_player) { + var gold = -88; + target.gold -= gold; + def.goldsteal = gold; + } + } else { + if (evade) { + return; + } + target.hits++; + if (first) { + if (info.crit && Math.random() * 100 < info.crit) { + var cmult = 2 + (info.critdamage || 0) / 100.0; + def.crit = cmult; + attack *= cmult; + } + if (attacker.type == "rogue") { + var maxd = G.skills.stack.max; + // if(G.monsters[target.type] && G.monsters[target.type].stationary) maxd=9999999999; + target.s.stack = { ms: 10000, s: min(maxd, (target.s.stack && target.s.stack.s + 1) || 1) }; + attack += target.s.stack.s; + } + if (def.purify) { + for (var name in target.s) { + if ( + (G.conditions[name] && + (G.conditions[name].buff || G.conditions[name].debuff) && + !G.conditions[name].persistent) || + target.s[name].citizens + ) { + delete target.s[name]; + attack += 400; + info.first_attack += 400; + target.cid++; + target.u = true; + } + } + } + var dmg_mult = 1; + if (attacker.is_player && target["for"]) { + dmg_mult = damage_multiplier(target["for"] * 5); + } + i_attack = attack = ceil(combo_m * attack * (0.9 + ((attack && Math.random() * 0.2) || 0))); + attack = + ceil( + attack * dmg_mult * damage_multiplier((target[defense] || 0) - (attacker[pierce] || 0) - info.apiercing), + ) || 0; + + if (target.incdmgamp) { + attack = round((attack * (100 + target.incdmgamp)) / 100.0); + } + + if (target["1hp"]) { + attack = (def.crit && 2) || 1; + } + if (attack > 0 && target.s.invincible) { + attack = 0; + } + if (attacker.tskin == "konami" && target.type != attacker.p.target_lock) { + attack = 0; + } + + o_attack = attack = ceil(attack); + + if (info.conditions.includes("frozen") && !target.immune && target.hp > attack) { + if (Math.random() < target.fzresistance / 100.0) { + xy_emit(target, "ui", { type: "freeze_resist", id: target.id }); + } else { + add_condition(target, "frozen"); + disappearing_text(target.socket, target, "FREEZE!", { xy: 1, size: "huge", color: "freeze", nv: 1 }); //target.is_player&&"huge"||undefined + } + } + + if (info.conditions.includes("burned") && !target.immune && target.hp > attack) { + add_condition(target, "burned", { + divider: attacker.a && attacker.a.burn && attacker.a.burn.unlimited && 1.5, + fid: attacker.id, + f: attacker.name || G.monsters[attacker.type].name, + attack: attack, + }); + } + + if (info.conditions.includes("woven") && !target.immune) { + add_condition(target, "woven"); + } + + if ( + info.conditions.includes("stunned") && + target.hp > attack && + add_condition(target, "stunned", { duration: 2000 }) + ) { + disappearing_text(target.socket, target, "STUN!", { xy: 1, size: "huge", color: "stun", nv: 1 }); + } //target.is_player&&"huge"||undefined + + if (info.procs && target.a.putrid) { + add_condition(attacker, "poisoned"); + add_condition(attacker, "cursed"); + change = true; + } + info.conditions.forEach(function (c) { + if (["frozen", "burned", "woven", "stunned"].includes(c)) { + return; + } + if (target.hp > attack && !target.immune) { + add_condition(target, c); + } + }); + if (info.procs && attacker.a.sugarrush && Math.random() < 0.0025) { + def.trigger = "sugarrush"; + add_condition(attacker, "sugarrush"); + disappearing_text(attacker.socket, attacker, "SUGAR RUSH!", { xy: 1, size: "huge", color: "sugar", nv: 1 }); //target.is_player&&"huge"||undefined + } + if (attacker.s.invis) { + // && target.is_player + def.sneak = true; + disappearing_text(target.socket, target, "SNEAK!", { xy: 1, size: "huge", color: "sneak", nv: 1 }); + } + if (info.damage_type == "pure") { + attack = ceil(info.first_attack); + } + if (target["1hp"]) { + attack = (def.crit && 2) || 1; + } + } else { + attack = o_attack; + if (target_def[1] == "splash") { + attack = ceil(attack * target_def[2]); + } + if (target["1hp"]) { + def.damage = o_attack = attack = 1; + } + } + if (attack >= 1) { + target.last.attacked = new Date(); + if (target.is_monster && attacker.is_player) { + target.points[attacker.name] = (target.points[attacker.name] || 0) + 1; + } + } + if (atype == "deepfreeze" && add_condition(target, "deepfreezed")) { + def.deepfreeze = true; + } + + def.damage = attack; + if (info.lifesteal) { + var hp = ceil((min(attack, target.hp) * info.lifesteal) / 100.0); + attacker.hp = min(attacker.max_hp, attacker.hp + hp); + change = true; + if (hp) { + def.lifesteal = hp; + } + } + if (info.manasteal) { + var mp = ceil((min(attack, target.hp) * info.manasteal) / 100.0); + if (target.mp !== undefined) { + mp = min(target.mp, mp); + target.mp = max(0, target.mp - mp); + } else { + mp = 0; + } + if (attacker.mp !== undefined) { + attacker.mp = min(attacker.max_mp || 0, attacker.mp + mp); + change = true; + } + if (mp) { + def.manasteal = mp; + } + } + if (G.monsters[attacker.type] && G.monsters[attacker.type].goldsteal && target.gold) { + //var gold=min(target.gold,parseInt(ceil(target.level*target.level/10))); + var gold = parseInt(Math.random() * 12) + 1; + target.gold = max(0, target.gold - gold); + attacker.extra_gold = (attacker.extra_gold || 0) + gold; + def.goldsteal = gold; + } + } + + var original = target.hp; + if (target.s.mshield && target.mp > 200 && attack > 0) { + // console.log("HERE MPSHIELD!") + var max_mp = target.mp - 200; + var damage_per_mp = 1.5; + if (target.level > 99) { + damage_per_mp = 3; + } else if (target.level > 89) { + damage_per_mp = 2.4; + } else if (target.level > 79) { + damage_per_mp = 2; + } else if (target.level > 69) { + damage_per_mp = 1.75; + } + var max_hp = ceil(max_mp * damage_per_mp); + if (max_hp < attack) { + def.mp_damage = max_mp; + attack -= max_hp; + target.mp = 200; + } else { + def.mp_damage = parseInt(attack / damage_per_mp); + attack = 0; + target.mp -= def.mp_damage; + } + change = true; + } + target.hp = min(target.hp - attack, target.max_hp); // both for damage and heal + var net = original - max(0, target.hp); + if (target.hp <= 0) { + def.kill = true; + if (G.skills[atype].kill_buff) { + add_condition(attacker, G.skills[atype].kill_buff); + } + } + + if ( + target.dreturn && + i_attack > 0 && + first && + attacker.range < 75 && + info.damage_type == "physical" && + !attacker["1hp"] + ) { + // dreturn happens at every hit + def.dreturn = ceil((i_attack * target.dreturn) / 100.0); + if (attacker.is_monster) { + attacker.u = true; + attacker.cid++; + } + attacker.hp = max(attacker.hp - def.dreturn, 0); + } + + // if(target.is_player && net>0) target.s.damage_received={amount:target.s.damage_received&&(target.s.damage_received.amount+net)||net,ms:10000}; + + if (attacker.is_player) { + if (net > 0) { + // "attack" + if (attacker.t) { + attacker.t.mdamage += net; + } + add_pdps(attacker, target, net); + } // "heal" + else { + add_pdps(attacker, target, -net * B.dps_heal_mult); + } + var m = target; + var mnet = net; + if (net < 0 && target.s && target.s.coop) { + m = instances[attacker.in].monsters[target.s.coop.id]; + mnet = -net * B.dps_heal_mult; + if (m && m.attack < 120) { + mnet /= 100; + } // dirty fix + // console.log("coop heal: "+m) + } + if (target.master) { + m = instances[attacker.in].monsters[target.master]; + } + if (m && m.is_monster && m.cooperative) { + add_coop_points(m, attacker, mnet); + } + } + + if (mode.instant_monster_attacks || attacker.is_player) { + xy_emit(target, "hit", def, attacker.id); + } // always sends the event to attacker.id + else { + events.push(["hit", def]); + } + + if (attacker.is_monster) { + //monster attacks player + achievement_logic_monster_hit(attacker, target, attack); + if (target.hp <= 0 && !target.rip) { + if (target.a && target.a.secondchance && Math.random() * 100 < target.a.secondchance.attr0) { + target.hp = target.max_hp; + disappearing_text(target.socket, target, "SECOND CHANCE!", { xy: 1, size: "huge", color: "green", nv: 1 }); + } else { + if (target.s.block && get_player(target.s.block.f)) { + pwn_routine(get_player(target.s.block.f), target); + } else { + defeated_by_a_monster(attacker, target); + } + } + } + target.c = {}; + } else if (target.is_monster) { + //player attacks monster + achievement_logic_monster_damage(attacker, target, net); + target.u = true; + target.cid++; + ccms(target); + if (target.hp <= 0) { + if (atype == "mentalburst") { + attacker.mp += net; + } + achievement_logic_monster_last_hit(attacker, target); + kill_monster(attacker, target); + } else { + if (target.a.warp_on_hit && Math.random() < target.a.warp_on_hit.attr0 && !is_disabled(target)) { + var point = random_place(target.map); + transport_monster_to(target, target.in, target.map, point.x, point.y); + } + if (target.drop_on_hit) { + drop_something(attacker, target, 1); + } + if ( + !attacker.is_npc && + !target.target && + !evade && + atype != "shadowstrike" && + !G.monsters[target.type].passive + ) { + target_player(target, attacker); + } + } + } else if (target.is_player) { + //player attacks player + if (!info.positive && !target.s.invincible) { + if (mode.dpvpblock) { + attacker.socket.emit("eval", { code: "pvp_timeout(3600,1)" }); + attacker.s.block = { ms: 3600, f: (attacker.s.block && attacker.s.block.f) || target.name }; + change = true; + } + target.socket.emit("eval", { code: "pvp_timeout(3600)" }); + target.s.block = { ms: 3600, f: (target.s.block && target.s.block.f) || attacker.name }; + if (!is_same(target, attacker, 1)) { + target.s.block.f = attacker.name; + } + target.c = {}; + } + + if (target.hp <= 0 && !target.rip) { + if (atype == "mentalburst") { + attacker.mp += net; + change = true; + } + if (target.a && target.a.secondchance && Math.random() * 100 < target.a.secondchance.attr0) { + target.hp = target.max_hp; + disappearing_text(target.socket, target, "SECOND CHANCE!", { xy: 1, size: "huge", color: "green", nv: 1 }); + } else { + var victor = attacker; + if (target.s.block && get_player(target.s.block.f)) { + victor = get_player(target.s.block.f); + } + pwn_routine(victor, target); + } + } + } + + first = false; + + if (target.is_player) { + resend(target, "u+cid"); + } + }); + + if (attacker.hp <= 0 && !attacker.dead && !attacker.rip) { + // dreturn + if (attacker.is_monster) { + kill_monster((attacker.target && get_player(attacker.target)) || target, attacker); + } // monster to player + else if (target.is_monster) { + defeated_by_a_monster(target, attacker); + change = true; + } // player to monster + else { + pwn_routine(target, attacker); + } // player to player + } + + if ((change || attacker.to_resend) && attacker.is_player) { + resend(attacker, "u+cid"); + } + if (change && attacker.is_monster && !attacker.dead) { + attacker.u = true; + attacker.cid++; + ccms(attacker); + } } -function complete_attack(attacker,target,info) -{ - var defense="armor",pierce="apiercing"; - var combo=1,combo_m=1; - var atype=info.atype; - var evade=false,first=true; - var attack=info.attack,o_attack,i_attack=info.attack,def=info.def; - if(G.monsters[attacker.type] && G.monsters[attacker.type].good) info.heal=info.positive=true; - var change=false; - var targets=[[target,"normal"]],otarget=target; - var events=[]; - info.action.map=attacker.map; - info.action.in=attacker.in; - - if(info.damage_type=="pure" || target===attacker) defense="none_existent",pierce="non_existent",info.apiercing=0; - else if(info.damage_type=="magical") defense="resistance",pierce="rpiercing",info.apiercing=0; - else info.damage_type="physical"; - - if(target.is_player && attack>0 && !info.heal) - { - add_pdps(target,attacker,attack*B.dps_tank_mult); // "tank" - if(attacker.cooperative) add_coop_points(attacker,target,attack*B.dps_tank_mult); - } - if(attacker.is_monster && attack>0 && !info.heal) - { - attacker.outgoing+=min(target.hp,attack); - } - - if(target.reflection && defense=="resistance" && Math.random()*100=4 && attacker.is_player) item_achievement_increment(attacker,attacker.slots.chest,"reflector"); - if(info.reflections>=4 && target.is_player) item_achievement_increment(target,target.slots.chest,"reflector"); - - xy_emit(target,"hit",{pid:def.pid,hid:attacker.id,id:target.id,damage:0,reflect:info.attack},attacker.id); - return xy_emit(target,"action",info.action,attacker.id); - } - - if(attacker==target && !target.dead); // reflect after dead fix [04/02/23] - else if(target.evasion && defense=="armor" && Math.random()*10072*(info.heal&&1.5||1)) - return xy_emit(info.action,"hit",{pid:def.pid,hid:attacker.id,id:target.id,damage:0,avoid:true,x:info.action.x,y:info.action.y,map:info.attacker.map,in:info.attacker.in},attacker.id); - - if(info.positive || !info.procs) combo=0; - if(combo && target.targets>3) - { - if(!target.last_combo || ssince(target.last_combo)>5) target.combo=1; - combo+=target.combo; - target.last_combo=new Date(); - target.combo+=1; - def.mobbing=target.combo; - } - if(combo && target.is_player && instances[target.in].pmap[target.last_hash] && Object.keys(instances[target.in].pmap[target.last_hash]).length>1) - { - var combo_check=false; - for(var id in instances[target.in].pmap[target.last_hash]) - { - var ntarget=instances[target.in].pmap[target.last_hash][id]; - if(target.id==ntarget.id || is_invinc(ntarget) || !is_same(target,ntarget,2) && !is_in_pvp(target) || attacker.id==ntarget.id) continue; //is_same(attacker,ntarget,1) - //!is_same(target,ntarget,1) && !is_in_pvp(target) to prevent people killing other people in non-pvp servers - //!is_same(target,ntarget,2) makes people kill others in coop fights - combo_check=true; break; - } - if(combo_check) - { - targets=[]; combo=0; - for(var id in instances[target.in].pmap[target.last_hash]) - { - var ntarget=instances[target.in].pmap[target.last_hash][id]; - if(is_invinc(ntarget) || attacker.id==ntarget.id || !is_same(target,ntarget,2)) continue; //is_same(attacker,ntarget,1) - targets.push([ntarget,"stack"]); - if(!ntarget.last_combo || ssince(ntarget.last_combo)>5) ntarget.combo=1; - combo+=ntarget.combo; - ntarget.last_combo=new Date(); - ntarget.combo+=1; - } - if(targets.length>1) - { - def.stacked=[]; - targets.forEach(function(t){ def.stacked.push(t[0].id); }) - } - } - } - - if(combo>10) combo_m=combo/4.0; - else if(combo>1) combo_m=[1,1.6,1.62,1.64,1.70,1.72,1.75,1.8,1.9,2,2,2,2][combo]; - combo_m=min(combo_m,max(300/attack,1.2)); // previously 2.4 - - if(!info.positive && (info.explosion && info.damage_type=="physical" || info.blast && info.damage_type=="magical")) - { - var intensity=info.blast,defense="resistance"; - if(info.damage_type=="physical") intensity=info.explosion,defense="armor"; - var radius=intensity/3.6; - if(is_in_pvp(attacker) || attacker.is_monster) - for(var id in instances[target.in].players) - { - var target=instances[target.in].players[id]; - if(target.npc) continue; - if(target.id!=otarget.id && distance(target,otarget)0 && target.s.invincible) attack=0; - if(attacker.tskin=="konami" && target.type!=attacker.p.target_lock) attack=0; - - o_attack=attack=ceil(attack); - - if(info.conditions.includes("frozen") && !target.immune && target.hp>attack) - { - if(Math.random()attack) - add_condition(target,"burned",{divider:attacker.a && attacker.a.burn && attacker.a.burn.unlimited && 1.5, - fid:attacker.id,f:attacker.name||G.monsters[attacker.type].name,attack:attack}) - - if(info.conditions.includes("woven") && !target.immune) - add_condition(target,"woven"); - - if(info.conditions.includes("stunned") && target.hp>attack && add_condition(target,"stunned",{duration:2000})) - disappearing_text(target.socket,target,"STUN!",{xy:1,size:"huge",color:"stun",nv:1}); //target.is_player&&"huge"||undefined - - if(info.procs && target.a.putrid) - { - add_condition(attacker,"poisoned"); - add_condition(attacker,"cursed"); - change=true; - } - info.conditions.forEach(function(c){ - if(["frozen","burned","woven","stunned"].includes(c)) return; - if(target.hp>attack && !target.immune) - add_condition(target,c); - }) - if(info.procs && attacker.a.sugarrush && Math.random()<0.0025) - { - def.trigger="sugarrush"; - add_condition(attacker,"sugarrush"); - disappearing_text(attacker.socket,attacker,"SUGAR RUSH!",{xy:1,size:"huge",color:"sugar",nv:1}); //target.is_player&&"huge"||undefined - } - if(attacker.s.invis) // && target.is_player - { - def.sneak=true; - disappearing_text(target.socket,target,"SNEAK!",{xy:1,size:"huge",color:"sneak",nv:1}); - } - if(info.damage_type=="pure") attack=ceil(info.first_attack); - if(target["1hp"]) attack=def.crit&&2||1; - } - else - { - attack=o_attack; - if(target_def[1]=="splash") attack=ceil(attack*target_def[2]); - if(target["1hp"]) def.damage=o_attack=attack=1; - } - if(attack>=1) - { - target.last.attacked=new Date(); - if(target.is_monster && attacker.is_player) target.points[attacker.name]=(target.points[attacker.name]||0)+1; - } - if(atype=="deepfreeze" && add_condition(target,"deepfreezed")) - def.deepfreeze=true; - - def.damage=attack; - if(info.lifesteal) - { - var hp=ceil(min(attack,target.hp)*info.lifesteal/100.0); - attacker.hp=min(attacker.max_hp,attacker.hp+hp); change=true; - if(hp) def.lifesteal=hp; - } - if(info.manasteal) - { - var mp=ceil(min(attack,target.hp)*info.manasteal/100.0); - if(target.mp!==undefined) - { - mp=min(target.mp,mp); - target.mp=max(0,target.mp-mp); - } - else - mp=0; - if(attacker.mp!==undefined) - { - attacker.mp=min(attacker.max_mp||0,attacker.mp+mp); change=true; - } - if(mp) def.manasteal=mp; - } - if(G.monsters[attacker.type] && G.monsters[attacker.type].goldsteal && target.gold) - { - //var gold=min(target.gold,parseInt(ceil(target.level*target.level/10))); - var gold=parseInt(Math.random()*12)+1; - target.gold=max(0,target.gold-gold); - attacker.extra_gold=(attacker.extra_gold||0)+gold; - def.goldsteal=gold; - } - } - - var original=target.hp; - if(target.s.mshield && target.mp>200 && attack>0) - { - // console.log("HERE MPSHIELD!") - var max_mp=target.mp-200,damage_per_mp=1.5; - if(target.level>99) damage_per_mp=3; - else if(target.level>89) damage_per_mp=2.4; - else if(target.level>79) damage_per_mp=2; - else if(target.level>69) damage_per_mp=1.75; - var max_hp=ceil(max_mp*damage_per_mp); - if(max_hp0 && first && attacker.range<75 && info.damage_type=="physical" && !attacker["1hp"]) // dreturn happens at every hit - { - def.dreturn=ceil(i_attack*target.dreturn/100.0); - if(attacker.is_monster) attacker.u=true,attacker.cid++; - attacker.hp=max(attacker.hp-def.dreturn,0); - } - - // if(target.is_player && net>0) target.s.damage_received={amount:target.s.damage_received&&(target.s.damage_received.amount+net)||net,ms:10000}; - - if(attacker.is_player) - { - if(net>0) // "attack" - { - if(attacker.t) attacker.t.mdamage+=net; - add_pdps(attacker,target,net); - } - else // "heal" - add_pdps(attacker,target,-net*B.dps_heal_mult); - var m=target,mnet=net; - if(net<0 && target.s && target.s.coop) - { - m=instances[attacker.in].monsters[target.s.coop.id]; - mnet=-net*B.dps_heal_mult; - if(m && m.attack<120) mnet/=100; // dirty fix - // console.log("coop heal: "+m) - } - if(target.master) - { - m=instances[attacker.in].monsters[target.master]; - } - if(m && m.is_monster && m.cooperative) add_coop_points(m,attacker,mnet); - } - - if(mode.instant_monster_attacks || attacker.is_player) - xy_emit(target,"hit",def,attacker.id); // always sends the event to attacker.id - else - events.push(["hit",def]); - - if(attacker.is_monster) //monster attacks player - { - achievement_logic_monster_hit(attacker,target,attack); - if(target.hp<=0 && !target.rip) - { - if(target.a && target.a.secondchance && Math.random()*100 6000) { + var ms = max(0, -mssince(player.last.attack)) + 3200; + var EV = ""; + EV = "skill_timeout('attack'," + ms + "); "; + player.last.attack = future_ms(ms); + for (var i in player.last) { + if (G.skills[i] && player.last[i] > future_ms(-3000)) { + player.last[i] = future_ms(3200); + EV += "skill_timeout('" + i + "'," + (3200 + (G.skills[i].cooldown || 0)) + "); "; + } + } + } + if (effect) { + player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 812, 120000) }; + } else { + player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 3200, 120000) }; + } + player.socket.emit("new_map", { + name: instance.map, + in: name, + x: player.x, + y: player.y, + direction: direction, + effect: effect || 0, + info: instance.info, + m: player.m, + entities: send_all_xy(player, { raw: true }), + eval: EV, + }); + player.last.transport = new Date(); + resend(player, "u+cid"); } -function transport_player_to(player,name,point,effect) -{ - // if((player.duel || player.team) && player.in!=name) restore_state(player); // PROBLEMATIC [29/07/22] - if(!instances[name]) name="main"; - var instance=instances[name]; - var new_map=G.maps[instance.map],direction=0,scatter=0; - var data={id:player.id,reason:"transport"}; - if(!is_invis(player) && !player.stealth && G.maps[name]) data.to=name,data.s=point; - if(effect) data.effect=effect; - xy_emit(player,"disappear",data); - - player.map=instance.map; - if(player.in!=name) - { - delete instances[player.in].players[player.id]; - if(instances[player.in].solo==player.id) - { - destroy_instance(player.in); - } - } - pmap_remove(player); - player.in=name; - resume_instance(instances[player.in]); - instances[player.in].players[player.id]=player; - if(!instances[player.in].mount && player.user && !player.mounting && !player.unmounting) player.unmounting=new Date(),sync_loop(); // patches the exit by jail loophole [20/08/18] - if(Object.keys(player.bets).length && name!="tavern") - { - for(var bid in player.bets) player.gold+=player.bets[bid].gold; - player.bets={}; - resend(player,"reopen+nc"); - } - - player.m++; - if(is_array(point)) - { - player.x=point[0]; - player.y=point[1]; - direction=point[2]||0; - scatter=point[3]||0; - } - else - { - if(!new_map.spawns[point||0]) point=0;player.x=new_map.spawns[point||0][0]; - player.y=new_map.spawns[point||0][1]; - direction=new_map.spawns[point||0][2]; - scatter=new_map.spawns[point||0][3]||0; - } - - if(scatter) - { - player.x+=Math.random()*scatter-scatter/2; - player.y+=Math.random()*scatter-scatter/2; - } - // server_log(effect); - if(effect) - { - // server_log("here"); - player.tp=effect; //appear - setTimeout(function(){ - try{ - if(!check_player(player)) return; - player.tp=false; - }catch(e){log_trace("#X Critical-tp",e);} - },500); - } - player.moving=false; player.vx=player.vy=0; - player.u=true; player.cid++; - calculate_player_stats(player); - pmap_add(player); - add_call_cost(player,8,"transport"); // New - to offset send_all_xy [26/01/20] - if(0 && !effect && mssince(player.last.transport)>6000) - { - var ms=max(0,-mssince(player.last.attack))+3200,EV=""; - EV="skill_timeout('attack',"+ms+"); "; - player.last.attack=future_ms(ms); - for(var i in player.last) - { - if(G.skills[i] && player.last[i]>future_ms(-3000)) - player.last[i]=future_ms(3200),EV+="skill_timeout('"+i+"',"+(3200+(G.skills[i].cooldown||0))+"); "; - } - } - if(effect) player.s.penalty_cd={ms:min(((player.s.penalty_cd&&player.s.penalty_cd.ms)||0)+812,120000)}; - else player.s.penalty_cd={ms:min(((player.s.penalty_cd&&player.s.penalty_cd.ms)||0)+3200,120000)}; - player.socket.emit("new_map",{name:instance.map,in:name,x:player.x,y:player.y,direction:direction,effect:effect||0,info:instance.info,m:player.m,entities:send_all_xy(player,{raw:true}),eval:EV}); - player.last.transport=new Date(); - resend(player,"u+cid"); +function add_shells(player, amount, reason, announce, override) { + if (gameplay == "hardcore" || gameplay == "test") { + return; + } + var phrase = "Received"; + if (reason == "xptome") { + phrase = "Earned"; + } + player.cash += amount; + player.socket.emit("game_log", { message: phrase + " " + to_pretty_num(amount) + " SHELLS", color: "green" }); + disappearing_text(player.socket, player, "+" + to_pretty_num(amount), { + color: colors.cash, + xy: 1, + s: "cash", + size: "huge", + }); + appengine_call( + "bill_user", + { auth: player.auth, amount: -parseInt(amount), reason: reason + "_drop", name: player.name, override: override }, + function (result) { + if (result.failed || !result.done) { + return; + } + player.cash = result.cash; + resend(player, "reopen"); + }, + ); + if (announce) { + broadcast("server_message", { + message: player.name + " found " + to_pretty_num(amount) + " shells", + color: "#85C76B", + type: "server_found", + shells: amount, + name: player.name, + }); + } } -function add_shells(player,amount,reason,announce,override) -{ - if(gameplay=="hardcore" || gameplay=="test") return; - var phrase="Received"; - if(reason=="xptome") phrase="Earned"; - player.cash+=amount; - player.socket.emit("game_log",{message:phrase+" "+to_pretty_num(amount)+" SHELLS",color:"green"}); - disappearing_text(player.socket,player,"+"+to_pretty_num(amount),{color:colors.cash,xy:1,s:"cash",size:"huge"}); - appengine_call("bill_user",{auth:player.auth,amount:-parseInt(amount),reason:reason+"_drop",name:player.name,override:override},function(result){ - if(result.failed || !result.done) return; - player.cash=result.cash; - resend(player,"reopen"); - }); - if(announce) broadcast("server_message",{"message":player.name+" found "+to_pretty_num(amount)+" shells","color":"#85C76B","type":"server_found",shells:amount,name:player.name}); +function is_socket_allowed(socket) { + var loose = 0; + for (var id in sockets) { + if (!players[id] && get_ip(sockets[id]) == get_ip(socket)) { + loose++; + } + } + if (loose > 5) { + return false; + } + return true; } -function is_socket_allowed(socket) -{ - var loose=0; - for(var id in sockets) - { - if(!players[id] && get_ip(sockets[id])==get_ip(socket)) loose++; - } - if(loose>5) return false; - return true; +function disconnect_old_sockets(socket) { + for (var id in sockets) { + if (id != socket.id && !players[id] && get_ip(sockets[id]) == get_ip(socket)) { + sockets[id].emit("disconnect_reason", "Too many loose connections from your network. Simply reload to play."); + if (sockets[id]) { + sockets[id].disconnect(); + } // emit can trigger a disconnect too, so this would throw an exception, bring down the server + } + } } -function disconnect_old_sockets(socket) -{ - for(var id in sockets) - { - if(id!=socket.id && !players[id] && get_ip(sockets[id])==get_ip(socket)) - { - sockets[id].emit("disconnect_reason","Too many loose connections from your network. Simply reload to play."); - if(sockets[id]) sockets[id].disconnect(); // emit can trigger a disconnect too, so this would throw an exception, bring down the server - } - } +function init_io() { + io.on("connection", function (socket) { + if (socket.handshake.query.server_method) { + if (0 && socket.handshake.query.server_master == variables.server_master) { + // this was to make servers communicate with each other and disconnect overflows immediately [28/10/23] + if (socket.handshake.query.server_method == "players") { + socket.emit("players"); // decided to make the existing cron more aggressive [26/09/21] + } + } + socket.disconnect(); + return; + } + sockets[socket.id] = socket; + socket.total_calls = 0; + socket.calls = []; + socket.fs = {}; // function list + + if (!is_socket_allowed(socket)) { + disconnect_old_sockets(socket); + } + + var original_on = socket.on; + socket.on = function (method, f) { + // takes the "f" function, the function thats sent to socket.on, wraps it into a "g" function + var g = function (data) { + ls_method = method; + if (mode.log_all) { + console.log("'" + method + "': " + JSON.stringify(data)); + } + try { + var climit = limits.calls; + var name = "_observer"; + if (data === undefined) { + data = {}; + } // data normalisation [28/08/18] + socket.total_calls++; + add_call_cost(-1); + current_socket = socket; + call_modifier = { open_chest: 0.1, skill: 0.05, target: 0.5 }[method] || 1; + if (players[socket.id]) { + name = players[socket.id].name; + // if(players[socket.id].type=="merchant") climit=round(climit/3); + // Merchants are first class citizens now! [14/01/18] + } else { + climit = round(climit / 4); + } + if (method == "cm") { + var add = 1; + var len = data.message.length; + var mult = 1; + if (len > 100) { + add = 2; + } else if (len > 1000) { + add = 3; + } else if (len > 10000) { + add = 10; + } else if (len > 50000) { + add = 20; + } + // console.log("add: "+add+"len: "+len); + if (data.to.length > 1) { + mult = 0.8; + } + add_call_cost(add * data.to.length * mult, undefined, "cm_data"); + } else { + if (CC[method]) { + add_call_cost(CC[method] || 0); + } + } + if (get_call_cost() > climit && method != "disconnect") { + server_log(">>> LIMITDC " + name, 1); + socket.emit("limitdcreport", { calls: socket.calls, climit: climit, total: socket.total_calls }); + socket.emit("disconnect_reason", "limitdc"); + socket.disconnect(); + } else { + f(data); + } + } catch (e) { + try { + var climit = limits.calls; + add_call_cost(16); + log_trace("socket.on: " + method.substr(0, 200), e); + if (get_call_cost() > climit) { + server_log(">>> LIMITDC2 " + name, 1); + socket.emit("limitdcreport", { + calls: socket.calls, + climit: climit, + total: socket.total_calls, + method: method.substr(0, 200), + }); + socket.emit("disconnect_reason", "limitdc"); + socket.disconnect(); + } else { + try { + socket.emit("game_error", "ERROR!"); + } catch (e) {} + } + } catch (e) { + log_trace("limit_calls", e); + } + } + current_socket = false_socket; + }; + socket.fs[method] = f; + original_on.apply(socket, [method, g]); + }; + + var data = { + region: region, + name: server_name, + pvp: is_pvp, + gameplay: gameplay, + info: (instances[socket.first_map] && instances[socket.first_map].info) || {}, + }; + socket.first_map = socket.first_in = observer_map; + socket.first_x = observer_x; + socket.first_y = observer_y + 120; + socket.desktop = true; + if (socket.request && socket.request._query && socket.request._query.secret) { + for (var id in players) { + var player = players[id]; + if (player.secret == socket.request._query.secret) { + socket.player = player; + data.character = player_to_client(player); + data.character.id = data.character.name = player.name; + socket.first_map = player.map; + socket.first_in = player.in; + socket.first_x = player.x; + socket.first_y = player.y; + if (socket.request._query.desktop) { + socket.desktop = true; + socket.first_y += 120; + } else { + socket.desktop = false; + } + } + } + } + data.x = socket.first_x; + data.y = socket.first_y; + data.map = socket.first_map; + data.in = socket.first_in; + broadcast_e(true); + data.S = E; + socket.emit("welcome", data); + socket.on("send_updates", function () { + if (observers[socket.id]) { + send_all_xy(observers[socket.id]); + } + if (players[socket.id]) { + send_all_xy(players[socket.id]); + } + }); + socket.on("loaded", function (data) { + var observer = (observers[socket.id] = { + socket: socket, + x: socket.first_x, + y: socket.first_y, + // vision:[round((data.width/2)/data.scale)+B.ext_vision,round((data.height/2)/data.scale)+B.ext_vision], + map: socket.first_map, + in: socket.first_in, + observer: 1, + id: "o" + socket.id, + s: {}, + }); + if (socket.player) { + observer.player = socket.player; + } + // observer.vision[0]=min(1000,observer.vision[0]); observer.vision[1]=min(700,observer.vision[1]); + observer.vision = B.vision; + // socket.emit("observing",{map:observer.map,x:observer.x,y:observer.y}); + resume_instance(instances[observer.in]); + instances[observer.in].observers[observer.id] = observer; + send_all_xy(observer); + }); + socket.on("o:home", function (data) { + var observer = observers[socket.id]; + if (!observer) { + return; + } + var player = observer.player; + if (!player || player.dc) { + return; + } + transport_observer_to(observer, player.in, player.map, player.x, player.y + ((socket.desktop && 120) || 0)); + }); + socket.on("o:command", function (data) { + var observer = observers[socket.id]; + if (!observer) { + return; + } + var player = observer.player; + if (!player || player.dc) { + return; + } + player.socket.emit("code_eval", data); + }); + socket.on("cm", function (data) { + var player = players[socket.id]; + var receivers = []; + if (!player || player.s.mute) { + return fail_response("muted"); + } + data.to.forEach(function (name) { + var p = players[name_to_id[name]]; + if (p) { + p.socket.emit("cm", { name: player.name, message: data.message || "" }); + receivers.push(name); + } + }); + success_response("data", { locals: [], receivers: receivers }); + }); + socket.on("say", function (data) { + var player = players[socket.id]; + var message = strip_string(data.message).substr(0, 1200); + if (!player || player.s.mute) { + return fail_response("muted"); + } + if (data.code && player.last_say && ssince(player.last_say) < 15) { + return fail_response("chat_slowdown"); + } + if (player.last_say && mssince(player.last_say) < 400) { + return fail_response("chat_slowdown"); + } + if (!message || !message.length) { + return fail_response("invalid"); + } + player.last_say = new Date(); + if (data.party) { + if (!player.party) { + return fail_response("not_in_a_party"); + } + party_emit(player.party, "partym", { owner: player.name, message: message, id: player.id, p: true }); + } else if (data.name) { + var target = get_player(data.name); + if (!target) { + player.socket.emit("pm", { + owner: player.name, + to: data.name, + message: message, + id: player.id, + xserver: true, + }); + appengine_call( + "log_chat", + { to: ["", data.name], type: "xprivate", message: message, fro: player.name, author: player.owner }, + function (result) { + if (result.failed && players[socket.id]) { + player.socket.emit("pm", { + owner: player.name, + to: data.name, + message: "(FAILED)", + id: player.id, + xserver: true, + }); + } + }, + ); + } else { + if (target.name == player.name) { + return fail_response("invalid"); + } + player.socket.emit("pm", { owner: player.name, to: data.name, message: message, id: player.id }); + target.socket.emit("pm", { owner: player.name, message: message, id: player.id }); + appengine_call("log_chat", { + to: [target.owner, target.name], + type: "private", + message: message, + fro: player.name, + author: player.owner, + }); + } + } else { + if (1) { + broadcast("chat_log", { owner: player.name, message: message, id: player.id, p: true }); + var owners = {}; + for (var id in players) { + var p = players[id]; + owners[p.owner] = owners[p.owner] || []; + owners[p.owner].push(p.name); + } + appengine_call("log_chat", { + to: Object.entries(owners), + type: "ambient", + message: message, + fro: player.name, + author: player.owner, + }); + } else { + xy_emit(player, "chat_log", { owner: player.name, message: message, id: player.id, p: true }); + } + appengine_call("log_chat", { type: "server", message: message, fro: player.name, author: player.owner }); + } + if (player.s.typing) { + delete player.s.typing; + resend(player, "u+cid+nc"); + } + success_response(); + }); + socket.on("ping_trig", function (data) { + socket.emit("ping_ack", data); + }); + socket.on("target", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + reduce_call_cost(); + player.target = data.id; + player.focus = data.xid; + var target = instances[player.in].monsters[data.id]; + if (!target) { + target = instances[player.in].players[data.id]; + } + if (!target) { + target = instances[player.in].players[NPC_prefix + data.id]; + } + if (!target) { + player.target = null; + } + var focus = instances[player.in].monsters[data.xid]; + if (!focus) { + focus = instances[player.in].players[data.xid]; + } + if (!focus) { + focus = instances[player.in].players[NPC_prefix + data.xid]; + } + if (!focus) { + player.focus = null; + } + if (focus && focus.screenshot) { + target.going_x = player.x; + target.going_y = player.y; + target.u = true; + start_moving_element(focus); + } + if (focus && player.map == "cgallery" && focus.npc) { + // target.going_x=player.x; + // target.going_y=player.y; + // target.u=true; + // start_moving_element(target); + if (!player.tcx) { + player.tcx = {}; + } + if (focus.ctype == "body") { + player.tskin = focus.skin; + } else if (player.cx.length > 5) { + return fail_response("invalid"); + } + player.tcx[focus.ctype] = focus.cx[focus.ctype]; + // player.tcx=target.cx; + resend(player, "u+cid"); + } + // server_log(player.name+" target: "+player.target+" focus: "+player.focus); + resend(player, "u+nc"); + success_response({}); + }); + socket.on("ureward", function (data) { + if (!player || player.user || !data.name || !G.docs.rewards[data.name]) { + return; + } + if (!player.verified || !player.auth_id) { + return socket.emit("game_response", "reward_notverified"); + } + if (!player.user.rewards) { + player.user.rewards = []; + } + if (player.user.rewards.includes(data.name)) { + return socket.emit("game_response", "reward_already"); + } + player.user.rewards.push(data.name); + exchange(player, G.docs.rewards[data.name], { name: "reward_" + data.name }); + socket.emit("game_response", { response: "reward_received", rewards: player.user.rewards }); + }); + socket.on("creward", function (data) { + if (!player || !data.name || !G.classes[player.type].rewards[data.name]) { + return; + } + if (!player.verified || !player.auth_id) { + return socket.emit("game_response", "reward_notverified"); + } + if (!player.p.rewards) { + player.p.rewards = []; + } + if (player.p.rewards.includes(data.name)) { + return socket.emit("game_response", "reward_already"); + } + player.p.rewards.push(data.name); + exchange(player, G.classes[player.type].rewards[data.name].reward, { name: "reward_" + data.name }); + socket.emit("game_response", { response: "reward_received", rewards: player.p.rewards }); + }); + socket.on("cx", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + // if(player.role!="gm") return socket.emit('game_log',"Cosmetics system is out of the test phase for now"); + // console.log(data); + var cx = player.cx; + var cxl = all_cx(player); + // if(!player.tcx) player.tcx=clone(player.cx); + if (data.slot && !data.name) { + if (data.slot == "back") { + delete cx.tail; + } // synced with render_cgallery + if (data.slot == "face") { + delete cx.makeup; + } + delete cx[data.slot]; + } else { + if (!T[data.name] || (!cxl[data.name] && player.role != "cx")) { + return fail_response("cx_not_found"); + } + if ((T[data.name] == "body" || T[data.name] == "armor" || T[data.name] == "character") && data.slot == "skin") { + player.skin = data.name; + } else if ((T[data.name] == "body" || T[data.name] == "armor") && data.slot == "upper") { + cx.upper = data.name; + } else if (cxtype_to_slot[T[data.name]] && cxtype_to_slot[T[data.name]] != "skin") { + cx[cxtype_to_slot[T[data.name]]] = data.name; + } + } + prune_cx(player.cx, player.skin); + resend(player, "u+cid"); + success_response(); + }); + socket.on("gm", function (data) { + var player = players[socket.id]; + if (!player || player.role != "gm") { + return; + } + var target = players[id_to_id[data.id]]; + var action = data.action; + if (action == "mute") { + if (!target) { + return socket.emit("game_log", "Player not found: " + data.id); + } + if (!target.s.mute) { + target.s.mute = { ms: 48 * 60 * 60 * 1000 }; + return socket.emit("game_chat", "Muted " + target.name); + } else { + target.s.mute = { ms: 0 }; + return socket.emit("game_chat", "Unmuted " + target.name); + } + } else if (action == "jail") { + if (!target) { + return socket.emit("game_log", "Player not found: " + data.id); + } + transport_player_to(target, "jail"); + } else if (action == "ban") { + appengine_call("ban_user", { name: data.id }, function (result) { + socket.emit("game_log", "Ban: " + ((result && result.result) || "No result")); + }); + } else if (action == "invincible") { + player.s.invincible = { ms: 24 * 60 * 60 * 1000 }; + resend(player, "u+cid"); + } else if (action == "jump") { + if (!target) { + return socket.emit("game_log", "Player not found: " + data.id); + } + var spot = safe_xy_nearby(target.map, target.x - 8, target.y - 6); + if (spot) { + pulled = player; + pulled.s.magiport = { ms: 400 }; + pulled.s.magiport.x = spot.x; + pulled.s.magiport.y = spot.y; + pulled.s.magiport.f = target.name; + pulled.s.magiport.in = target.in; + pulled.s.magiport.map = target.map; + resend(pulled, "u+cid"); + } else { + return socket.emit("game_log", "No safe spot near: " + data.id); + } + } else if (action == "mjump") { + target = get_monsters(data.monster)[0]; + if (!target) { + return socket.emit("game_log", "Monster not found: " + data.monster); + } + var spot = safe_xy_nearby(target.map, target.x - 8, target.y - 6); + if (spot) { + pulled = player; + pulled.s.magiport = { ms: 400 }; + pulled.s.magiport.x = spot.x; + pulled.s.magiport.y = spot.y; + pulled.s.magiport.f = target.name; + pulled.s.magiport.in = target.in; + pulled.s.magiport.map = target.map; + resend(pulled, "u+cid"); + } else { + return socket.emit("game_log", "No safe spot near: " + data.monster); + } + } else if (action == "jump_list") { + var ids = []; + for (var id in players) { + ids.push(players[id].name); + } + socket.emit("gm", { action: "jump_list", ids: ids }); + } else if (action == "server_info") { + var info = []; + for (var id in players) { + info.push({ name: players[id].name, owner: players[id].owner, ip: get_ip(players[id]) }); + } + //socket.emit("gm",{action:"server_info",info:info}); + } + }); + socket.on("monsterhunt", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (simple_distance(G.maps.main.ref.monsterhunter, player, true) > B.sell_dist) { + return fail_response("distance"); + } + var hunted = []; + for (var id in server.s) { + if (server.s[id].type == "monsterhunt") { + hunted.push(server.s[id].id); + } + } + if (player.s.monsterhunt && player.s.monsterhunt.c) { + return fail_response("monsterhunt_already"); + } else if (player.s.monsterhunt) { + delete server.s["monsterhunt_" + player.s.monsterhunt.id]; + delete player.s.monsterhunt; + add_item(player, "monstertoken", { log: true, q: (gameplay == "hardcore" && 100) || 1 }); + resend(player, "u+cid+reopen"); + return success_response({ completed: true }); + } + if (player.type == "merchant") { + return socket.emit("game_response", "monsterhunt_merchant"); + } + var mmax = -1; + var name = "goo"; + var count = 100; + var times = 0; + var the_hp = 0; + for (var id in instances) { + if (instances[id].name != id || !G.maps[id] || G.maps[id].irregular) { + continue; + } + for (var mid in instances[id].monsters) { + var monster = instances[id].monsters[mid]; + if (monster.level > mmax && !in_arr(monster.type, hunted) && !monster.target) { + // added the target condition [21/07/23] + name = monster.type; + mmax = monster.level; + the_hp = monster.max_hp / 1000.0; + } + } + } + for (var id in G.maps) { + if (G.maps[id].irregular || !G.maps[id].monsters) { + continue; + } + G.maps[id].monsters.forEach(function (p) { + if (p.type == name) { + times += p.count; + } + }); + } + // console.log(times); + count = max(1, min(500, parseInt((20 * 60 * max(1, times)) / the_hp / (G.monsters[name].respawn + 0.25)))); + if (gameplay == "hardcore") { + count = max(1, parseInt(count / 10)); + } + player.s.monsterhunt = { sn: region + " " + server_name, id: name, c: count, ms: 30 * 60 * 1000, dl: true }; + server.s["monsterhunt_" + name] = { name: player.name, id: name, ms: 20 * 60 * 1000, type: "monsterhunt" }; + player.hitchhikers.push(["game_response", "monsterhunt_started"]); + resend(player, "u+cid"); + success_response({ started: true }); + }); + socket.on("ccreport", function () { + socket.emit("ccreport", { calls: socket.calls, climit: limits.calls, total: socket.total_calls }); + }); + socket.on("tracker", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!player.tracker) { + return; + } + var data = { + monsters: player.p.stats.monsters, + monsters_diff: player.p.stats.monsters_diff, + exchanges: player.p.stats.exchanges, + maps: D.drops.maps, + tables: {}, + max: player.max_stats, + }; // ,computer:false + function register_table(table) { + if (table) { + table.forEach(function (drop) { + if (drop[1] == "open" && !data.tables[drop[2]]) { + data.tables[drop[2]] = D.drops[drop[2]]; + register_table(D.drops[drop[2]]); + } + }); + } + } + if (player.computer || 1) { + // data.computer=true; + data.drops = D.drops.monsters; + } else { + data.drops = {}; + for (var name in data.monsters) { + if (data.monsters[name] >= 100 && D.drops.monsters[name]) { + data.drops[name] = D.drops.monsters[name]; + } + } + } + if (D.drops.maps.global) { + data.global = D.drops.maps.global; + register_table(data.global); + } + if (D.drops.maps.global_static) { + data.global_static = D.drops.maps.global_static; + register_table(data.global_static); + } + for (var name in data.drops) { + register_table(data.drops[name]); + } + for (var name in data.maps) { + register_table(data.maps[name]); + } + for (var name in G.items) { + if ( + G.items[name].e && + (player.computer || + 1 || + (player.p.stats.exchanges && player.p.stats.exchanges[name] && player.p.stats.exchanges[name] >= 100)) + ) { + if (G.items[name].upgrade || G.items[name].compound) { + for (var i = 0; i < 13; i++) { + if (D.drops[name + i]) { + data.tables[name + i] = D.drops[name + i]; + register_table(data.tables[name + i]); + } + } + } else if (D.drops[name]) { + data.tables[name] = D.drops[name]; + register_table(data.tables[name]); + } + } + } + socket.emit("tracker", data); + }); + socket.on("set_home", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (player.p.dt.last_homeset && hsince(player.p.dt.last_homeset) < 36) { + return fail_response("sh_time", { hours: 36 - hsince(player.p.dt.last_homeset) }); + } + player.p.dt.last_homeset = new Date(); + player.p.home = region + server_name; + delete player.s.hopsickness; + success_response("home_set", { home: player.p.home }); + }); + socket.on("code", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.run) { + players[socket.id].code = true; + } else { + players[socket.id].code = false; + } + resend(players[socket.id], "u+cid"); + }); + socket.on("property", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.typing) { + if (!player.s.typing || player.s.typing.ms < 3000) { + reduce_call_cost(); + } + player.s.typing = { ms: 4000 }; + resend(player, "u+cid+nc"); + } else { + var original = player.afk; + if (player.afk == "bot" || player.afk == "code") { + } else if (data.afk === true) { + player.afk = true; + } else if (data.afk === false) { + if (player.afk !== undefined) { + player.afk = false; + } + } + if (original !== player.afk) { + reduce_call_cost(); + resend(player, "u+cid+nc"); + } + } + }); + socket.on("cruise", function (speed) { + var player = players[socket.id]; + if (!player) { + return; + } + player.cruise = parseInt(speed); + resend(player, "u+cid"); + success_response("cruise", { speed: player.cruise }); + }); + socket.on("test", function (data) { + if (is_sdk) { + console.log(data.test); + } + socket.emit("test", { date: new Date() }); + }); + socket.on("blocker", function (data) { + if (data.type == "pvp") { + if (instances["arena"].allow) { + socket.emit("blocker", { type: "pvp", allow: 1 }); + } else { + socket.emit("blocker", { type: "pvp" }); + } + } + }); + socket.on("mail_take_item", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (mode.prevent_external) { + return socket.emit("game_response", { response: "not_in_this_server" }); + } + if (!player.esize) { + return socket.emit("game_response", "inv_size"); + } + appengine_call( + "take_item_from_mail", + { owner: player.owner, mid: data.id }, + function (result) { + var player = players[socket.id]; + if (result.failed) { + return socket.emit("game_response", { response: "mail_item_already_taken" }); + } + var item = JSON.parse(result.item); + add_item(player, item, { announce: false }); + resend(player, "reopen"); + socket.emit("game_response", { response: "mail_item_taken" }); + }, + function () { + socket.emit("game_response", { response: "mail_take_item_failed" }); + }, + ); + }); + socket.on("mail", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (mode.prevent_external) { + return fail_response("not_in_this_server"); + } + var item = null; + var retries = 1; + if (player.gold < 48000) { + return fail_response("gold_not_enough"); + } + player.gold -= 48000; + if (data.item && player.items[0] && player.items[0].name != "placeholder") { + if (player.items[0].l) { + return fail_response("item_locked"); + } + if (player.items[0].b || player.items[0].v) { + return fail_response("item_blocked"); + } + if (player.gold < 312000) { + return fail_response("gold_not_enough"); + } + player.gold -= 312000; + item = JSON.stringify(player.items[0]); + player.items[0] = player.citems[0] = null; + retries = 3; + } + appengine_call( + "send_mail", + { + fro: player.name, + to: data.to, + subject: data.subject || "", + message: data.message || "", + rid: randomStr(50), + retries: retries, + item: item, + }, + function (result) { + var player = players[socket.id]; + if (result.failed) { + if (player) { + socket.emit("game_response", { + response: "mail_failed", + to: data.to, + reason: result.reason, + cevent: "mail_failed", + }); + } + if (player && item && result.return && player.esize) { + var r = JSON.parse(item); + add_item(player, r); + resend(player, "reopen"); + } else { + console.log("#M unsent mail, lost item: " + item); + } + return; + } + if (player) { + socket.emit("game_response", { response: "mail_sent", to: data.to, cevent: "mail_sent" }); + } + }, + function () { + var player = players[socket.id]; + if (player) { + socket.emit("game_response", { response: "mail_failed", reason: "coms_failure" }); + } + if (item) { + console.log("#M unsent mail, lost item: " + item); + } + }, + ); + resend(player, "reopen"); + success_response("mail_sending", { sucess: false, in_progress: true, received: "unknown" }); + }); + socket.on("leave", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!can_walk(player)) { + return fail_response("transport_failed"); + } + if ( + (0 && player.s.block) || + player.targets > 5 || + !(player.map == "jail" || player.map == "cyberland" || instances[player.in].solo) + ) { + return fail_response("cant_escape"); + } + transport_player_to(player, B.start_map); + success_response(); + }); + socket.on("transport", function (data) { + var player = players[socket.id]; + var can_reach = false; + if (!player) { + return; + } // this was missing, probably caused a production exception - should be added everywhere [04/09/16] + if (!can_walk(player) || player.map == "jail") { + return fail_response("transport_failed"); + } + var new_map = G.maps[data.to]; + var s = data.s || 0; + var the_door = null; + if (!new_map || !instances[data.to] || !instances[data.to].allow) { + return fail_response("cant_enter"); + } + if ((0 && player.s.block) || player.targets > 5) { + return fail_response("cant_escape"); + } + + (G.maps[player.map].doors || []).forEach(function (door) { + if ( + !can_reach && + door[4] == data.to && + s == (door[5] || 0) && + simple_distance( + { map: player.map, x: G.maps[player.map].spawns[door[6]][0], y: G.maps[player.map].spawns[door[6]][1] }, + player, + ) < B.door_dist + ) { + can_reach = "door"; + the_door = door; + } + }); + if ((player.map == "woffice" && gameplay == "hardcore") || player.role == "gm") { + can_reach = "transport"; + } + if ( + !can_reach && + G.maps[player.map].ref.transporter && + simple_distance(G.maps[player.map].ref.transporter, player) < B.transporter_dist && + G.npcs.transporter.places[data.to] === s + ) { + can_reach = "transport"; + } + if (can_reach == "transport" && player.s.dampened) { + return fail_response("transport_cant_dampened"); + } + if (!can_reach) { + return fail_response("transport_cant_reach"); + } + if (the_door && the_door[7] == "protected") { + var protected = false; + for (var x in instances[player.in].monsters || {}) { + if (instances[player.in].monsters[x].map_def.gatekeeper) { + protected = true; + } + } + if (protected) { + return fail_response("transport_cant_protection"); + } + } + if ( + the_door && + the_door[7] == "ulocked" && + G.maps[data.to].mount && + player.user && + !player.user.unlocked[data.to] + ) { + return fail_response("transport_cant_locked"); + } + + if (instances[data.to].mount && !player.user) { + if (player.mounting || player.unmounting) { + return fail_response("bank_opi"); + } + add_call_cost(32, undefined, "bank"); + player.mounting = new Date(); + player.mount_to = data.to; + player.mount_s = s; + sync_loop(); + return success_response({ success: false, in_progress: true }); + } else if (!instances[data.to].mount && player.user) { + // previously handled at transport_player_to + if (player.mounting || player.unmounting) { + return fail_response("bank_opi"); + } + add_call_cost(16, undefined, "bank"); + player.unmounting = new Date(); + player.unmount_to = data.to; + player.unmount_s = s; + sync_loop(); + return success_response({ success: false, in_progress: true }); + } else if (0 && instances[data.to].mount && player.user && data.to != player.map) { + // an easy prevention for the bank re-entry nuisance, bank can be re-entered physically but not digitally [03/11/16] + // commented out for bank_u / bank_b [06/05/20] + return fail_response("transport_failed"); + } else { + decay_s(player, 5200); + transport_player_to(player, data.to, s); + return success_response(); + } + }); + socket.on("enter", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (gameplay != "normal") { + return fail_response("transport_failed"); + } + if (!can_walk(player) || player.map == "jail") { + return fail_response("transport_failed"); + } + if (player.s.block || player.targets > 5) { + return fail_response("cant_escape"); + } + // if(player.role!="gm" && !(data.place==player.map && player.map=="cgallery")) + // { + // if(data.place!="resort" && !G.maps[player.map].ref.transporter || simple_distance(G.maps[player.map].ref.transporter,player)>80) return socket.emit("game_response","transport_cant_reach"); + // if(data.place=="resort" && player.map!="resort") return socket.emit("game_response","transport_cant_reach"); + // } + server_log(data); + var name = randomStr(24); + if (data.place == "resort" && 0) { + var name = "resort_" + data.name; + instance = instances[name] || create_instance(name, "resort_map"); + transport_player_to(player, name); + } else if (data.place == "duelland") { + if (instances[data.name] && instances[data.name].map == "duelland") { + transport_player_to(player, data.name); + instance_emit(data.name, "game_log", { message: player.name + " is spectating the duel", color: "gray" }); + } else { + return fail_response("cant_enter"); + } + } else if (data.place == "crypt" || data.place == "winter_instance") { + var f = "cave"; + var ref = G.maps.cave.spawns[2]; + var item = "cryptkey"; + if (data.place == "winter_instance") { + f = "winterland"; + ref = G.maps.winterland.spawns[5]; + item = "frozenkey"; + } + if (simple_distance(player, { in: f, map: f, x: ref[0], y: ref[1] }) > 120) { + return fail_response("transport_cant_reach"); + } + if (data.name && instances[data.name] && instances[data.name].map == data.place) { + transport_player_to(player, data.name); + } else { + if (!consume_one_by_id(player, item)) { + return fail_response("transport_cant_item"); + } + instance = create_instance(name, data.place); + transport_player_to(player, name); + } + resend(player, "u+cid+reopen"); + } else if (data.place == "dungeon0" && player.role == "gm") { + instance = create_instance(name, "dungeon0", { solo: player.id }); + transport_player_to(player, name); + } else if (data.place == "cgallery" && player.role == "gm") { + var point = null; + if (player.map == "cgallery") { + point = [player.x, player.y]; + } + instance = create_instance(name, "cgallery", { solo: player.id }); + transport_player_to(player, name, point); + var bodies = []; + var heads = []; + var hairs = []; + var wings = []; + var hats = []; + for (var s in G.sprites) { + var current = G.sprites[s]; + var matrix = current.matrix; + if (current.skip || current.rskip) { + continue; + } + if (!in_arr(current.type, ["body", "head", "hair", "s_wings", "hat"])) { + continue; + } + for (var i = 0; i < matrix.length; i++) { + for (var j = 0; j < matrix[i].length; j++) { + if (!matrix[i][j]) { + continue; + } + if (current.type == "body") { + bodies.push(matrix[i][j]); + } + if (current.type == "head") { + heads.push(matrix[i][j]); + } + if (current.type == "hair") { + hairs.push(matrix[i][j]); + } + if (current.type == "hat") { + hats.push(matrix[i][j]); + } + if (current.type == "s_wings") { + wings.push(matrix[i][j]); + } + T[matrix[i][j]] = current.type; + } + } + } + var xs = [-80, -40, 0, 40, 80]; + for (var n = 0; n < bodies.length; n++) { + var npc = create_npc( + { + name: bodies[n], + level: 1, + speed: 12, + hp: 1000, + skin: bodies[n], + cx: {}, + }, + { + position: [xs[n % xs.length], -parseInt(n / xs.length) * 40], + id: bodies[n], + }, + instance, + ); + npc.ctype = "body"; + npc.cx = { head: heads[n] }; + npc.id = "body: " + bodies[n]; + instance.players[NPC_prefix + npc.id] = npc; + } + for (var n = 0; n < heads.length; n++) { + var npc = create_npc( + { name: heads[n], level: 1, speed: 12, hp: 1000, skin: bodies[0], cx: {} }, + { position: [xs[n % xs.length] + 240, -parseInt(n / xs.length) * 40], id: heads[n] }, + instance, + ); + npc.ctype = "head"; + npc.cx = { head: heads[n] }; + npc.id = "head: " + heads[n]; + instance.players[NPC_prefix + npc.id] = npc; + } + for (var n = 0; n < hairs.length; n++) { + var npc = create_npc( + { name: hairs[n], level: 1, speed: 12, hp: 1000, skin: bodies[1], cx: {} }, + { position: [xs[n % xs.length] + 480, -parseInt(n / xs.length) * 40], id: hairs[n] }, + instance, + ); + npc.ctype = "hair"; + npc.cx = { head: heads[0], hair: hairs[n] }; + npc.id = "hair: " + hairs[n]; + instance.players[NPC_prefix + npc.id] = npc; + } + for (var n = 0; n < hats.length; n++) { + var npc = create_npc( + { name: hats[n], level: 1, speed: 12, hp: 1000, skin: bodies[2], cx: {} }, + { position: [xs[n % xs.length] + 720, -parseInt(n / xs.length) * 40], id: hats[n] }, + instance, + ); + npc.ctype = "hat"; + npc.cx = { head: heads[0], hat: hats[n] }; + npc.id = "hair: " + hats[n]; + instance.players[NPC_prefix + npc.id] = npc; + } + } else { + return fail_response("transport_cant_reach"); + } + success_response(); + }); + socket.on("town", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + // if(player.last.town && mssince(player.last.town)<1200) return; // bad ui experience [Unknown] - got reported and disabled [25/03/22] + if (!can_walk(player) || player.map == "jail") { + return fail_response("transport_failed"); + } + if ((0 && player.s.block) || player.targets > 5) { + return fail_response("cant_escape"); + } + player.c.town = { ms: min((player.c.town && player.c.town.ms) || 5000, 3000) }; + resend(player, "u+nc"); + success_response({ success: false, in_progress: true }); + }); + socket.on("respawn", function (data) { + var player = players[socket.id]; + if (!player || !player.rip) { + return fail_response("invalid"); + } + if (player.rip_time && ssince(player.rip_time) < B.rip_time) { + return fail_response("cant_respawn"); + } + + delete player.s.block; + player.hp = player.max_hp; + player.mp = round(player.max_mp / 2); + player.rip = false; + if (gameplay == "hardcore") { + reset_player(player, 1); + } + + if (data && data.safe) { + // gameplay=="hardcore" && + transport_player_to(player, "woffice", 0, 1); + } else { + var place = G.maps[player.map].on_death || G.maps[B.start_map].on_death || ["main", 0]; + transport_player_to(player, place[0], place[1], 1); + } + if (player.party) { + send_party_update(player.party); + } + invincible_logic(player); + resend(player, "u+cid"); + success_response("data", { place: "respawn", cevent: "respawn" }); + }); + socket.on("random_look", function (data) { + return socket.emit("game_log", "socket.emit('enter',{place:'cgallery'})"); + var player = players[socket.id]; + if (!player) { + return; + } + var bodies = []; + var heads = []; + var hairs = []; + var wings = []; + var hats = []; + if (player.rlooks == 25 && !is_sdk && player.role != "gm") { + return; + } + if (player.role == "gm") { + reduce_call_cost(40); + } + player.rlooks = (player.rlooks || 0) + 1; + for (var s in G.sprites) { + var current = G.sprites[s]; + var matrix = current.matrix; + if (current.skip || current.rskip) { + continue; + } + if (!in_arr(current.type, ["body", "head", "hair", "s_wings", "hat"])) { + continue; + } + for (var i = 0; i < matrix.length; i++) { + for (var j = 0; j < matrix[i].length; j++) { + if (!matrix[i][j]) { + continue; + } + if (current.type == "body") { + bodies.push(matrix[i][j]); + } + if (current.type == "head") { + heads.push(matrix[i][j]); + } + if (current.type == "hair") { + hairs.push(matrix[i][j]); + } + if (current.type == "hat") { + hats.push(matrix[i][j]); + } + if (current.type == "s_wings") { + wings.push(matrix[i][j]); + } + } + } + } + var head = "head"; + if (Math.random() < 0.1) { + head = random_one(heads); + } + player.tskin = random_one(bodies); + player.tcx = [head, random_one(hairs)]; + if (Math.random() < 0.08) { + player.tcx.push(random_one(wings)); + } + if (Math.random() < 0.4) { + player.tcx.push(random_one(hats)); + } + resend(player, "u+cid"); + }); + socket.on("unlock", function (data) { + return; // [27/06/18] + var player = players[socket.id]; + if (!player) { + return; + } + if (gameplay == "normal") { + return; + } // ? [27/06/18] + if (data.name == "code") { + var item = player.items[data.num || 0]; + if (!item || item.name != "computer" || item.charges === 0 || item.charges < 0 || player.unlocking_code) { + return; + } + if (!item.charges) { + item.charges = 2; + } + item.charges--; + player.citems[data.num || 0] = player.items[data.num || 0]; // explicitly linked to the actual item + player.unlocking_code = true; + appengine_call( + "user_operation", + { auth: player.auth, operation: "code_unlock", suffix: "/code_unlock/" + player.owner, retries: 4 }, + function (result) { + server_log("user_operation_code: " + JSON.stringify(result), 1); + if (result.failed) { + if (result.reason == "already") { + socket.emit( + "game_log", + "Your CODE slots are already unlocked. You can lend your Ancient Computer to a friend in need, that's why there are 2 charges :]", + ); + item.charges++; + } else { + socket.emit("game_log", "Unlock Failed. Email hello@adventure.land with a screenshot."); + } + return; + } + socket.emit("game_log", "CODE slots increased to 100!"); + resend(player, "reopen"); + }, + ); + } + }); + socket.on("dismantle", function (data) { + var player = players[socket.id]; + var check = true; + var items = []; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!player.computer && simple_distance(G.maps.main.ref.craftsman, player) > B.sell_dist) { + return fail_response("distance"); + } + // if(player.esize<=0) return socket.emit("game_response","inventory_full"); + var item = player.items[data.num]; + if (item && item.level && G.items[item.name].compound) { + var cost = min(50000000, calculate_item_value(item) * 10); + if (player.gold < cost) { + return fail_response("gold_not_enough"); + } + if (player.esize < 2) { + return fail_response("inv_size"); + } + if (G.items[item.name].type == "booster") { + return fail_response("dismantle_cant"); + } + player.gold -= cost; + consume_one(player, data.num); + add_item( + player, + { name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) }, + { announce: false }, + ); + add_item( + player, + { name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) }, + { announce: false }, + ); + add_item( + player, + { name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) }, + { announce: false }, + ); + resend(player, "reopen+nc+inv"); + return success_response("dismantle", { name: item.name, level: item.level, cost: cost, cevent: true }); + } + if (!item || !G.dismantle[item.name]) { + return fail_response("dismantle_cant"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + if (player.gold < G.dismantle[item.name].cost) { + return fail_response("gold_not_enough"); + } + if ( + !can_add_items(player, list_to_pseudo_items(G.dismantle[item.name].items), { + space: ((item.q || 1) == 1 && 1) || 0, + }) + ) { + return fail_response("inv_size"); + } + player.gold -= G.dismantle[item.name].cost; + consume_one(player, data.num); + G.dismantle[item.name].items.forEach(function (e) { + if (e[0] < 1 && Math.random() > e[0]) { + return; + } + add_item(player, e[1], { q: max(1, e[0]), p: item.p && !G.titles[item.p].misc && item.p }); + }); + resend(player, "reopen+nc+inv"); + success_response("dismantle", { name: item.name, cevent: true }); + }); + socket.on("craft", function (data) { + var player = players[socket.id]; + var check = true; + var items = []; + var quantity = {}; + var place = {}; + var space = false; + var p = {}; + var locked = false; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + data.items.forEach(function (x) { + if (!player.items[x[1]]) { + check = false; + } else if (player.items[x[1]].l || player.items[x[1]].b) { + check = false; + locked = true; + } else { + var name = player.items[x[1]].name; + if (player.items[x[1]].level) { + name += "+" + player.items[x[1]].level; + } + items.push(name); + quantity[player.items[x[1]].name] = player.items[x[1]].q || 1; // (quantity[player.items[x[1]].name]||0)+ removed this part, seems like a bad initial addition [17/06/18] + place[player.items[x[1]].name] = x[1]; + if (player.items[x[1]].p && !G.titles[player.items[x[1]].p].misc) { + p[player.items[x[1]].p] = player.items[x[1]].p; + } + } + }); + if (locked) { + return fail_response("item_locked"); + } + if (!check) { + return fail_response("no_item"); + } + //if(items.length<2) return socket.emit("game_response","craft_atleast2"); + items.sort(); + var key = items.join(","); + // console.log(key); + if (!D.craftmap[key]) { + return fail_response("craft_cant"); + } + var name = D.craftmap[key]; + var enough = true; + if ( + !player.computer && + !G.craft[name].quest && + simple_distance(get_npc_coords("craftsman"), player) > B.sell_dist + ) { + return fail_response("distance"); + } + if ( + !player.computer && + G.craft[name].quest && + simple_distance(get_npc_coords(G.craft[name].quest), player) > B.sell_dist + ) { + return fail_response("distance"); + } + if (player.gold < G.craft[name].cost) { + return fail_response("gold_not_enough"); + } + G.craft[name].items.forEach(function (i) { + if (quantity[i[1]] < i[0]) { + enough = false; + } + if (quantity[i[1]] == i[0]) { + space = true; + } + }); + if (!space && !can_add_item(player, create_new_item(name))) { + return fail_response("inventory_full"); + } + if (!enough) { + return fail_response("craft_cant_quantity"); + } + player.gold -= G.craft[name].cost; + G.craft[name].items.forEach(function (x) { + consume(player, place[x[1]], x[0]); + }); + var i = add_item(player, name, { r: 1, p: Object.keys(p).length && random_one(p) }); + resend(player, "reopen+nc+inv"); + success_response("craft", { num: i, name: name, cevent: true }); + }); + socket.on("exchange", function (data) { + var player = players[socket.id]; + var item = player.items[data.item_num]; + var def = G.items[item && item.name]; + var suffix = ""; + if (player.q.exchange) { + return fail_response("exchange_existing"); + } + if (def && (def.compound || def.upgrade)) { + suffix = item.level || 0; + } + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + G.maps.main.exchange.name = player.name; + if (!def || !def.e || item.q != data.q || !D.drops[item.name + suffix]) { + return fail_response("invalid"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (!player.computer && !def.quest && simple_distance(G.maps.main.exchange, player) > B.sell_dist) { + return fail_response("distance"); + } + if (!player.computer && def.quest && simple_distance(G.quests[def.quest], player) > B.sell_dist) { + return fail_response("distance"); + } + if (player.esize <= 0 && !((item.q || 1) == 1)) { + return fail_response("inventory_full"); + } + if (def.e > 1 && item.q < def.e) { + return fail_response("exchange_notenough"); + } + player.p.stats.exchanges[item.name + suffix] = (player.p.stats.exchanges[item.name + suffix] || 0) + 1; + consume(player, data.item_num, def.e); + var num = add_item(player, "placeholder"); + var ms = 3000 + parseInt(Math.random() * 3000); + if (gameplay == "hardcore") { + ms = 400; + } + player.q.exchange = { ms: ms, len: ms, name: item.name, id: item.name + suffix, q: def.e, num: num }; + if (suffix) { + player.q.exchange.s = suffix; + } + if (item.v) { + player.q.exchange.v = item.v; + } + if (def.quest) { + player.q.exchange.qs = def.quest; + } + resend(player, "reopen+nc+inv"); + success_response({ success: false, in_progress: true, num: num }); + }); + socket.on("exchange_buy", function (data) { + //console.log(JSON.stringify(data)); + var player = players[socket.id]; + var item = player.items[data.num]; + var def = G.items[item && item.name]; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!def || def.type != "token") { + return fail_response("invalid"); + } + var npc = G.maps.main.ref[G.items[item.name].npc || item.name + "s"]; + var num = -1; + if (!G.tokens[item.name][data.name]) { + return fail_response("invalid"); + } + if (item.l) { + return fail_response("item_locked"); + } + npc.name = player.name; + if (item.q != data.q) { + return fail_response("safety_check"); + } + if (!player.computer && simple_distance(npc, player) > B.sell_dist) { + return fail_response("distance"); + } + if (item.q < G.tokens[item.name][data.name]) { + return fail_response("exchange_notenough"); + } + + if (G.tokens[item.name][data.name] < 1) { + var q = parseInt(1 / G.tokens[item.name][data.name]); + if (item.q != 1 && !can_add_item(player, { name: data.name, q: q })) { + return fail_response("inventory_full"); + } + consume(player, data.num, 1); + num = add_item(player, data.name, { q: q, announce: false, r: true }); + } else { + var name = data.name; + var idata = undefined; + if (name.search("-") != -1) { + idata = name.split("-")[1]; + name = name.split("-")[0]; + } + var new_item = create_new_item(name); + if (idata) { + new_item.data = idata; + } + if (item.q != G.tokens[item.name][data.name] && !can_add_item(player, new_item)) { + return fail_response("inventory_full"); + } + consume(player, data.num, G.tokens[item.name][data.name]); + num = add_item(player, new_item, { announce: false, r: true }); + } + + xy_emit(npc, "upgrade", { type: item.name + "s", success: 1 }); + resend(player, "reopen+nc+inv"); + success_response({ num: num, cevent: true }); + }); + socket.on("locksmith", function (data) { + var player = players[socket.id]; + var item = player.items[data.item_num]; + var def = G.items[item && item.name]; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!player.computer && simple_distance(G.maps.desertland.ref.locksmith, player) > B.sell_dist) { + return fail_response("distance"); + } + var item = player.items[data.num]; + if (!item) { + return fail_response("no_item"); + } + var def = G.items[item.name]; + if (in_arr(def.type, ["uscroll", "cscroll", "pscroll", "offering", "tome"])) { + return fail_response("locksmith_cant"); + } + if (data.operation == "unlock") { + if (!item.l) { + resend(player, "reopen+nc"); + return fail_response("locksmith_aunlocked", "locksmith", "already_unlocked"); + } + if (item.l == "s") { + if (player.gold < 250000) { + return fail_response("gold_not_enough"); + } + player.gold -= 250000; + item.ld = JSON.stringify(future_s(2 * 24 * 60 * 60)); + item.l = "u"; + socket.emit("game_response", "locksmith_unsealed"); + } else if (item.l == "u") { + var date = new Date(JSON.parse(item.ld)); + if (date < new Date()) { + delete item.l; + socket.emit("game_response", "locksmith_unseal_complete"); + } else { + resend(player, "reopen+nc"); + return success_response("locksmith_unsealing", { hours: -hsince(date), success: false, in_progress: true }); + } + } else { + if (player.gold < 250000) { + return fail_response("gold_not_enough"); + } + player.gold -= 250000; + delete item.l; + socket.emit("game_response", "locksmith_unlocked"); + } + } else if (data.operation == "lock") { + if (item.l) { + resend(player, "reopen+nc"); + return fail_response("locksmith_alocked", "locksmith", "already_locked"); + } + if (player.gold < 250000) { + return fail_response("gold_not_enough"); + } + player.gold -= 250000; + item.l = "l"; + socket.emit("game_response", "locksmith_locked"); + } else if (data.operation == "seal") { + if (player.gold < 250000) { + return fail_response("gold_not_enough"); + } + player.gold -= 250000; + item.l = "s"; + socket.emit("game_response", "locksmith_sealed"); + } + player.citems[data.num] = cache_item(player.items[data.num]); + resend(player, "reopen+nc"); + success_response(); + }); + socket.on("compound", function (data) { + try { + var player = players[socket.id]; + var item0 = player.items[data.items[0]]; + var item1 = player.items[data.items[1]]; + var item2 = player.items[data.items[2]]; + var scroll = player.items[data.scroll_num]; + var result; + var ex = ""; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + G.maps.main.compound.name = player.name; + if (player.q.compound) { + return fail_response("compound_in_progress", "compound", "in_progress"); + } + var offering = player.items[data.offering_num]; + if (offering && G.items[offering.name].type != "offering") { + return socket.emit("game_response", "compound_invalid_offering"); + } + if (!item0 || (item0.level || 0) != data.clevel) { + return fail_response("no_item"); + } + if (!player.computer && simple_distance(G.maps.main.compound, player) > B.sell_dist) { + return socket.emit("game_response", { response: "distance", place: "compound", failed: true }); + } + var def = G.items[item0.name]; + var scroll_def = G.items[scroll.name]; + var offering_def = offering && G.items[offering.name]; + var grade = calculate_item_grade(def, item0); + if (grade == 4) { + return socket.emit("game_response", { + response: "max_level", + level: item.level, + place: "compound", + failed: true, + }); + } + if ( + !( + item0.name == item1.name && + item1.name == item2.name && + (item0.level || 0) == (item1.level || 0) && + (item1.level || 0) == (item2.level || 0) + ) + ) { + return socket.emit("game_response", "compound_mismatch"); + } + if (!def.compound) { + return socket.emit("game_response", "compound_cant"); + } + if (scroll_def.type != "cscroll" || grade > scroll_def.grade) { + return socket.emit("game_response", "compound_incompatible_scroll"); + } + if (data.items[0] == data.items[1] || data.items[1] == data.items[2] || data.items[0] == data.items[2]) { + return socket.emit("game_response", { response: "misc_fail", place: "compound", failed: true }); + } + if (item0.l || item1.l || item2.l) { + return socket.emit("game_response", { response: "item_locked", place: "compound", failed: true }); + } + + if (!data.calculate) { + consume_one(player, data.scroll_num); + } + + var new_level = (item0.level || 0) + 1; + var probability = 1; + var oprobability = 1; + var result = 0; + var proc = 0; + var grace = 0; + var igrade = def.igrade; + var high = false; + var grace_bonus = 0; + if (item0.level >= 3) { + igrade = calculate_item_grade(def, { name: item0.name, level: item0.level - 2 }); + } + + delete player.p.c_item; + delete player.p.c_itemx; + delete player.p.c_roll; + + oprobability = probability = D.compounds[igrade][new_level]; + result = Math.random(); + server_log(result + " < " + probability); + + if (scroll_def.grade > grade) { + probability = probability * 1.1 + 0.001; + server_log("higher grade scroll " + result + " < " + probability); + if (!data.calculate) { + grace_bonus += 0.4; + } + high = scroll_def.grade - grade; + } + + if (offering) { + var increase = 0.5; + if (!data.calculate) { + consume_one(player, data.offering_num); + } + grace = 0.027 * ((item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0) + 0.5 + player.p.ograce); + + if (offering_def.grade > grade + 1) { + probability = probability * 1.64 + grace * 2; + high = true; + increase = 3; + } else if (offering_def.grade > grade) { + probability = probability * 1.48 + grace; + high = true; + increase = 1; + } else if (offering_def.grade == grade) { + probability = probability * 1.36 + min(30 * 0.027, grace); + } else if (offering_def.grade == grade - 1) { + probability = probability * 1.15 + min(25 * 0.019, grace) / max(item0.level - 2, 1); + increase = 0.2; + } else { + probability = probability * 1.08 + min(15 * 0.015, grace) / max(item0.level - 1, 1); + increase = 0.1; + } + + if (!data.calculate) { + item0.grace = (item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0); + grace_bonus += increase; + } + server_log("offering " + result + " < " + probability); + } else { + grace = 0.007 * ((item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0) + player.p.ograce); + probability = probability + min(25 * 0.007, grace) / max(item0.level - 1, 1); + if (!data.calculate) { + item0.grace = max(max(item0.grace || 0, item1.grace || 0), item2.grace || 0); + } + } + + if (!data.calculate) { + item0.grace = item0.grace / 6.4 + grace_bonus; + } + + if (def.type == "booster") { + probability = 0.9999999999999; + proc = offering && 0.12; + } else { + probability = min( + probability, + min(oprobability * (3 + ((high && high * 0.6) || 0)), oprobability + 0.2 + ((high && high * 0.05) || 0)), + ); + } + + if (gameplay == "test") { + result = 0; + } + + server_log(result + " < " + probability + " grace: " + grace); + + if (data.calculate) { + return success_response("compound_chance", { + calculate: true, + chance: probability, + item: cache_item(item0), + scroll: scroll.name, + offering: (offering && offering.name) || undefined, + grace: (item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0), + }); + } + + player.p.c_roll = result; + var len = 10000; + if (gameplay == "hardcore") { + len = 1200; + } + if (player.s.massproduction) { + len /= 2; + delete player.s.massproduction; + ex = "+u+cid"; + } + if (player.s.massproductionpp) { + len /= 10; + delete player.s.massproductionpp; + ex = "+u+cid"; + } + player.q.compound = { ms: len, len: len, num: data.items[0], nums: [] }; + player.items[data.items[0]] = { + name: "placeholder", + p: { + chance: probability, + name: item0.name, + level: item0.level, + scroll: scroll.name, + offering: offering && offering.name, + nums: [], + }, + }; + player.items[data.items[1]] = null; + player.items[data.items[2]] = null; + + if (result <= probability) { + if (offering) { + player.p.ograce = 0; + } else { + player.p.ograce *= 1 - new_level * 0.02; + } + item0.level = new_level; + if ((item0.p || item1.p || item2.p) != "legacy") { + item0.p = item0.p || item1.p || item2.p; + } else { + delete item0.p; + } + player.p.c_item = item0; + if (item0.oo != player.name) { + item0.o = player.name; + } + player.esize += 2; + if (parseInt(result * 10000) == parseInt(probability * 10000)) { + // && grade>=1 + add_achievement(player, "lucky"); + add_item_property(item0, "lucky"); + } + if (item1.v || item2.v) { + item0.v = item1.v || item2.v; + } + if (item0.name == "ctristone" && item0.level == 1 && Math.random() < 0.012) { + item0.name = "cdarktristone"; + } + if (def.type == "booster") { + var activate = false; + item0.extra = 0; + while (offering && proc && Math.random() < proc) { + item0.extra++; + item0.level += 1; + proc /= 2.0; + } + var seconds = 6 * 24 * 60 * 60; + [item0, item1, item2].forEach(function (i) { + if (i.expires) { + seconds -= ssince(i.expires); + activate = true; + } else { + seconds += def.days * 24 * 60 * 60; + } + }); + if (activate) { + item0.expires = future_s(seconds / 3); + } + } + } else { + if (offering) { + player.p.ograce += 0.4; + } + player.esize += 2; + player.p.c_item = null; + player.p.c_itemx = item0; + } + + player.citems[data.items[0]] = cache_item(player.items[data.items[0]]); + player.citems[data.items[1]] = cache_item(player.items[data.items[1]]); + player.citems[data.items[2]] = cache_item(player.items[data.items[2]]); + + resend(player, "reopen+nc+inv" + ex); + } catch (e) { + server_log("compound_e " + e); + return socket.emit("game_response", { response: "exception", place: "compound", failed: true }); + } + }); + socket.on("upgrade", function (data) { + try { + var player = players[socket.id]; + var item = player.items[data.item_num]; + var scroll = player.items[data.scroll_num]; + var offering = player.items[data.offering_num]; + var result; + var ex = ""; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (player.q.upgrade) { + return socket.emit("game_response", "upgrade_in_progress"); + } + G.maps.main.upgrade.name = player.name; + if (!player.computer && simple_distance(G.maps.main.upgrade, player) > B.sell_dist) { + return socket.emit("game_response", { response: "distance", place: "upgrade", failed: true }); + } + if (!item) { + return socket.emit("game_response", "upgrade_no_item"); + } + if ( + offering && + !( + (G.items[offering.name] && G.items[offering.name].type == "offering") || + (G.items[offering.name] && G.items[offering.name].offering !== undefined && !(item.level > 0)) + ) + ) { + return socket.emit("game_response", "upgrade_invalid_offering"); + } + if (!scroll && !offering) { + return socket.emit("game_response", "upgrade_no_scroll"); + } + if ((item.level || 0) != data.clevel) { + return socket.emit("game_response", "upgrade_mismatch"); + } + var item_def = G.items[item.name]; + var scroll_def = scroll && G.items[scroll.name]; + var offering_def = offering && G.items[offering.name]; + var grade = calculate_item_grade(item_def, item); + if (!item_def.upgrade) { + return socket.emit("game_response", "upgrade_cant"); + } + if ( + scroll && + (!in_arr(scroll_def.type, ["uscroll", "pscroll"]) || + (scroll_def.type == "uscroll" && !item_def.upgrade) || + (scroll_def.type == "pscroll" && !item_def.stat) || + grade > scroll_def.grade) + ) { + if (grade == 4 && scroll_def.type == "uscroll") { + return socket.emit("game_response", { + response: "max_level", + level: item.level, + place: "upgrade", + failed: true, + }); + } + return socket.emit("game_response", "upgrade_incompatible_scroll"); + } + + var new_level = (item.level || 0) + 1; + var probability = 1; + var oprobability = 1; + var grace = 0; + var high = false; + var ograde = calculate_item_grade(item_def, { name: item.name, level: 0 }); + var tmult = 1; + if (ograde == 1) { + tmult = 1.5; + } else if (ograde == 2) { + tmult = 2; + } + + delete player.p.u_item; + delete player.p.u_type; + delete player.p.u_itemx; + delete player.p.u_roll; + delete player.p.u_fail; + delete player.p.u_level; + + player.p.u_level = item.level || 0; + + if (!scroll) { + if (G.items[offering.name] && G.items[offering.name].offering !== undefined) { + var chance = 0.16; + var ms = 2000; + if ( + G.items[offering.name].offering > ograde || + (G.items[offering.name].offering == 2 && calculate_item_value(item) <= 20000000) + ) { + chance = 0.32; + } + chance *= [2.8, 1.6, 1][ograde]; + if (G.items[offering.name].offering < ograde) { + return socket.emit("game_response", "upgrade_invalid_offering"); + } + if (data.calculate) { + return success_response("upgrade_chance", { + calculate: true, + chance: chance, + offering: offering.name, + item: cache_item(item), + grace: item.grace || 0, + }); + } + var result = Math.random(); + + consume_one(player, data.offering_num); + player.p.u_type = "normal"; + player.p.u_roll = result; + if (player.s.massproduction) { + ms /= 2; + delete player.s.massproduction; + ex = "+u+cid"; + } + if (player.s.massproductionpp) { + ms /= 10; + delete player.s.massproductionpp; + ex = "+u+cid"; + } + player.q.upgrade = { ms: ms, len: ms, num: data.item_num, silent: true }; + player.items[data.item_num] = { + name: "placeholder", + p: { + chance: chance, + name: item.name, + level: item.level, + scroll: null, + offering: offering.name, + nums: [], + }, + }; + + if (result <= chance) { + item.p = "shiny"; + player.p.u_item = item; + } else { + player.p.u_item = item; + player.p.u_fail = true; + } + } else { + var ms = 1000; + if (data.calculate) { + return success_response("upgrade_chance", { + calculate: true, + chance: 1, + offering: offering.name, + item: cache_item(item), + grace: item.grace || 0, + }); + } + consume_one(player, data.offering_num); + item.grace = (item.grace || 0) + 0.5; + server_log("item.grace: " + item.grace); + player.p.u_type = "offering"; + player.p.u_item = item; + player.p.u_roll = 0.999999999999999; + if (player.s.massproduction) { + ms /= 2; + delete player.s.massproduction; + ex = "+u+cid"; + } + if (player.s.massproductionpp) { + ms /= 10; + delete player.s.massproductionpp; + ex = "+u+cid"; + } + player.q.upgrade = { ms: ms, len: ms, num: data.item_num }; + player.items[data.item_num] = { + name: "placeholder", + p: { chance: 1, name: item.name, level: item.level, scroll: null, offering: offering.name, nums: [] }, + }; + } + } else if (scroll_def.type == "uscroll") { + if (item.l) { + return socket.emit("game_response", { response: "item_locked", place: "upgrade", failed: true }); + } + if (grade == 4) { + return socket.emit("game_response", { + response: "max_level", + level: item.level, + place: "upgrade", + failed: true, + }); + } + if (!data.calculate) { + consume_one(player, data.scroll_num); + } + oprobability = probability = D.upgrades[item_def.igrade][new_level]; + // grace=max(0,min(new_level+1, (item.grace||0) + min(3,player.p.ugrace[new_level]/3.0) +min(2,ugrace[new_level]/4.0) +item_def.igrace + player.p.ograce/2.0 )); - original [16/07/18] + grace = max( + 0, + min(new_level + 1, (item.grace || 0) + min(3, player.p.ugrace[new_level] / 4.5) + item_def.igrace) + + min(6, S.ugrace[new_level] / 3.0) + + player.p.ograce / 3.2, + ); + server_log( + "Grace num: " + + grace + + "\nItem: " + + (item.grace || 0) + + "\nPlayer: " + + min(3, player.p.ugrace[new_level] / 4.5) + + "\nDef: " + + item_def.igrace + + "\nOgrace:" + + player.p.ograce / 3.2 + + "\nS.ugrace: " + + min(6, S.ugrace[new_level] / 3.0), + ); + grace = (probability * grace) / new_level + grace / 1000.0; + server_log("Grace-prob: " + grace); + result = Math.random(); + server_log(result + " < " + probability); + + // if(!data.calculate && item.name=="throwingstars" && scroll_def.grade==2 && item.level==4) item.p="superfast"; + + if (scroll_def.grade > grade && new_level <= 10) { + probability = probability * 1.2 + 0.01; + high = true; + if (!data.calculate) { + item.grace = (item.grace || 0) + 0.4; + } + } + + if (offering) { + var increase = 0.4; + if (!data.calculate) { + consume_one(player, data.offering_num); + } + + if (offering_def.grade > grade + 1) { + probability = probability * 1.7 + grace * 4; + high = true; + increase = 3; + } else if (offering_def.grade > grade) { + probability = probability * 1.5 + grace * 1.2; + high = true; + increase = 1; + } else if (offering_def.grade == grade) { + probability = probability * 1.4 + grace; + } else if (offering_def.grade == grade - 1) { + probability = probability * 1.15 + grace / 3.2; + increase = 0.2; + } else { + probability = probability * 1.08 + grace / 4; + increase = 0.1; + } + + if (!data.calculate) { + item.grace = (item.grace || 0) + increase; + } // previously +1 [16/07/18] + } else { + grace = max(0, grace / 4.8 - 0.4 / ((new_level - 0.999) * (new_level - 0.999))); + probability += grace; // previously 12.0 // previously 9.0 [16/07/18] + } + + if (!data.calculate && Math.random() < 0.025) { + // Bonus grace + item.grace = (item.grace || 0) + 1; + } + + if (data.item_num == player.p.item_num && Math.random() < 0.6) { + // Added [29/10/17] + server_log("16 cheat"); + result = max(Math.random() / 10000.0, result * 0.975 - 0.012); + } + + if (high) { + probability = min(probability, min(oprobability + 0.36, oprobability * 3)); + } else { + probability = min(probability, min(oprobability + 0.24, oprobability * 2)); + } + server_log(result + " < " + probability + " grace: " + grace); + + if (data.calculate) { + return success_response("upgrade_chance", { + calculate: true, + chance: probability, + offering: (offering && offering.name) || undefined, + item: cache_item(item), + grace: item.grace || 0, + scroll: scroll.name, + }); + } + + if (gameplay == "test") { + result = 0; + } + // result=probability; + player.p.u_type = "normal"; + player.p.u_roll = result; + var ms = 500 * new_level * Math.sqrt(new_level) * tmult; + if (player.s.massproduction) { + ms /= 2; + delete player.s.massproduction; + ex = "+u+cid"; + } + if (player.s.massproductionpp) { + ms /= 10; + delete player.s.massproductionpp; + ex = "+u+cid"; + } + player.q.upgrade = { ms: ms, len: ms, num: data.item_num }; + if (gameplay == "hardcore") { + player.q.upgrade.ms = player.q.upgrade.len = 500; + } + player.items[data.item_num] = { + name: "placeholder", + p: { + chance: probability, + name: item.name, + level: item.level, + scroll: scroll.name, + offering: offering && offering.name, + nums: [], + }, + }; + + //result=probability+EPS; + + if (result <= probability) { + // console.log("here"); + player.p.ugrace[new_level] = S.ugrace[new_level] = 0; + if (offering) { + player.p.ograce *= 0.25; + } else { + player.p.ograce *= 1 - new_level * 0.005; + } + item.level = new_level; + if (item.oo != player.name) { + item.o = player.name; + } + player.p.u_item = item; + if (parseInt(result * 10000) == parseInt(probability * 10000) && grade >= 1) { + add_item_property(item, "lucky"); + add_achievement(player, "lucky"); + } + } else { + player.p.ugrace[new_level - 1] += 1; + S.ugrace[new_level - 1] += 1; + player.p.ugrace[new_level] += 1; + S.ugrace[new_level] += 1; + if (new_level >= 8 && new_level <= 15) { + player.p.ugrace[new_level - 1] += 1; + S.ugrace[new_level - 1] += 1; + player.p.ugrace[new_level - 2] += 2 + ((offering && 1) || 0); + S.ugrace[new_level - 2] += 2; + player.p.ugrace[new_level - 3] += 2 + ((offering && 2) || 0); + S.ugrace[new_level - 3] += 3 + ((offering && 1) || 0); + } + if (offering) { + player.p.ograce += 0.6; + } // previously 1 [16/07/18] + if (scroll_def.grade != 3.6) { + player.p.u_itemx = item; + } else { + player.p.u_item = item; + player.p.u_fail = true; + } + if (parseInt(result * 10000) == parseInt(probability * 10000)) { + if (player.esize && grade >= 1) { + add_item(player, "essenceofgreed"); + } + add_achievement(player, "unlucky"); + } + } + } else if (scroll_def.type == "pscroll") { + var needed = [1, 10, 100, 1000, 9999, 9999, 9999]; + if (scroll.q < needed[grade]) { + return socket.emit("game_response", { response: "upgrade_scroll_q", q: needed[grade], h: scroll.q }); + } + if (!data.calculate) { + consume(player, data.scroll_num, needed[grade]); + } + + if (data.calculate) { + return success_response("upgrade_chance", { + calculate: true, + chance: 0.99999, + scroll: scroll.name, + item: cache_item(item), + grace: item.grace || 0, + }); + } + + if (offering) { + consume_one(player, data.offering_num); + item.grace = (item.grace || 0) + 1; + server_log("Graced up to " + item.grace); + } + + item.stat_type = scroll_def.stat; + player.p.u_roll = Math.random(); + + player.p.u_type = "stat"; + if (player.p.u_roll <= 0.99999 || offering) { + player.p.u_item = item; + } else { + player.p.u_itemx = item; + } + var ms = 2000 * tmult * tmult; + if (player.s.massproduction) { + ms /= 2; + delete player.s.massproduction; + ex = "+u+cid"; + } + if (player.s.massproductionpp) { + ms /= 10; + delete player.s.massproductionpp; + ex = "+u+cid"; + } + player.q.upgrade = { ms: ms, len: ms, num: data.item_num }; + player.items[data.item_num] = { + name: "placeholder", + p: { + chance: 0.99999, + name: item.name, + level: item.level, + scroll: scroll.name, + offering: offering && offering.name, + nums: [], + }, + }; + } + + player.citems[data.item_num] = cache_item(player.items[data.item_num]); + + resend(player, "reopen+nc+inv" + ex); + } catch (e) { + server_log("upgrade_e " + e); + return socket.emit("game_response", { response: "exception", place: "upgrade", failed: true }); + } + }); + socket.on("equip", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + player.c = {}; + data.num = to_number(data.num); + if (data.num >= player.items.length) { + return fail_response("invalid"); + } + var item = player.items[data.num]; + if (!item) { + return fail_response("no_item"); + } + if (item.b) { + return fail_response("item_blocked"); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + var def = G.items[item.name]; + var to_update = "reopen+u+cid"; + var resolve = { num: data.num }; + var resolve_type = "data"; + def.iname = item.name; //just for orb name checks [08/10/16] + if (!def) { + return fail_response("no_item"); + } + // if(is_sdk) server_log("Trying to equip "+JSON.stringify(data)); + + if (data.slot && get_trade_slots(player).includes(data.slot) && !data.consume) { + if (item.acl || item.v) { + return fail_response("item_locked"); + } + var slot = data.slot; + var price = round(min(99999999999, max(parseInt(data.price) || 1, 1))); + var minutes = 1; + data.q = max(1, parseInt(data.q) || 1); + if ((item.q || 1) < data.q) { + return fail_response("not_enough"); + } + if (data.giveaway) { + minutes = max( + 5, + min(600, min(parseInt(data.minutes) || 5, max(60, round((calculate_item_value(item) * data.q) / 40000)))), + ); + } + if (!price || data.giveaway) { + price = 1; + } + if (player.slots[slot]) { + return fail_response("slot_occuppied"); + } + if (def.s) { + player.slots[slot] = create_new_item(player.items[data.num].name, 1); + player.slots[slot].price = price; + player.slots[slot].q = data.q; + player.slots[slot].rid = randomStr(4); + if (player.items[data.num].data) { + player.slots[slot].data = player.items[data.num].data; + } + if (data.giveaway) { + player.slots[slot].giveaway = minutes; + player.slots[slot].list = []; + player.giveaway = true; + } + player.cslots[slot] = cache_item(player.slots[slot], true); + consume(player, data.num, data.q); + if (data.giveaway) { + socket.emit("game_log", "Listed " + data.q + " " + item_name(player.slots[slot]) + " to giveaway!"); + } else { + socket.emit( + "game_log", + "Listed " + data.q + " " + item_name(player.slots[slot]) + " at " + to_pretty_num(price) + " gold each", + ); + } + } else { + player.items[data.num].price = price; + player.items[data.num].rid = randomStr(4); + player.slots[slot] = player.items[data.num]; + if (data.giveaway) { + player.slots[slot].giveaway = minutes; + player.slots[slot].list = []; + player.giveaway = true; + } + player.cslots[slot] = cache_item(player.slots[slot], true); + player.items[data.num] = null; + player.citems[data.num] = null; + if (data.giveaway) { + socket.emit("game_log", "Listed " + item_name(player.slots[slot]) + " to giveaway!"); + } else { + socket.emit( + "game_log", + "Listed " + item_name(player.slots[slot]) + " at " + to_pretty_num(price) + " gold", + ); + } + } + resolve.slot = slot; + } else if (def.type == "elixir") { + if (item.l) { + return fail_response("item_locked"); + } + if (player.slots.elixir && G.items[player.slots.elixir.name].withdrawal) { + add_condition(player, G.items[player.slots.elixir.name].withdrawal); + } + if (player.slots.elixir && player.slots.elixir.name == item.name) { + player.slots.elixir.expires = future_s(def.duration * 60 * 60 - ssince(player.slots.elixir.expires) * 0.975); + } else { + player.slots.elixir = { name: item.name, expires: future_s(def.duration * 60 * 60), ex: true }; + } + if (G.items[item.name] && G.items[item.name].withdrawal && player.s.withdrawal) { + delete player.s.withdrawal; + } + player.cslots.elixir = cache_item(player.slots.elixir); + consume_one(player, data.num); + resolve_type = "elixir"; + } else if (item.name == "cxjar") { + if (!item.data || item.l) { + return fail_response("item_locked"); + } + player.p.acx[item.data] = (player.p.acx[item.data] || 0) + 1; + socket.emit("game_response", { response: "cx_new", acx: player.p.acx, name: item.data, from: "cxjar" }); + consume_one(player, data.num); + } else if (item.name == "emotionjar") { + if (!item.data || item.l) { + return fail_response("item_locked"); + } + player.p.emx[item.data] = (player.p.emx[item.data] || 0) + 1; + socket.emit("game_response", { + response: "emotion_new", + emx: player.p.emx, + name: item.data, + from: "emotionjar", + }); + consume_one(player, data.num); + } else if (def.type == "licence") { + if (item.l) { + return fail_response("item_locked"); + } + player.s.licenced = { ms: ((player.s.licenced && player.s.licenced.ms) || 0) + 7 * 60 * 1000 }; + consume_one(player, data.num); + } else if (def.type == "spawner") { + new_monster(player.in, { type: def.spawn, stype: "trap", x: player.x, y: player.y, owner: player.name }); + consume_one(player, data.num); + } else if (def.gives) { + if (player.last.potion && mssince(player.last.potion) < 0) { + return fail_response("not_ready"); + } + if (item.l) { + return fail_response("item_locked"); + } + var timeout = 2000; + var timeout_ui = null; + var xp = false; + consume_one(player, data.num); + (def.gives || []).forEach(function (p) { + var amount = p[1]; + if (player.s.poisoned) { + amount = round(amount / 2); + } + player[p[0]] = (player[p[0]] || 0) + amount; + player[p[0]] = max(1, player[p[0]]); + if (p[0] == "hp") { + if (amount > 0) { + disappearing_text(socket, player, "+" + amount, { color: "hp", xy: 1, s: "hp" }); + } else { + disappearing_text(socket, player, amount, { color: "hp", xy: 1, s: "hp" }); + } + } + if (p[0] == "mp") { + disappearing_text(socket, player, "+" + amount, { color: "mp", xy: 1, s: "mp" }); + } + if (p[0] == "xp") { + disappearing_text(socket, player, "+1M", { color: "1mxp", xy: 1, s: "xp", size: "large" }); + xp = true; + timeout_ui = 120; + timeout = 0.1; + } + }); + if (def.debuff) { + for (var c in player.s) { + if (G.conditions[c] && G.conditions[c].debuff && (!G.conditions[c].persistent || c == "hopsickness")) { + delete player.s[c]; + } + } + } + player.hp = min(player.hp, player.max_hp); + player.mp = min(player.mp, player.max_mp); + if (def.cooldown) { + timeout = def.cooldown; + } + player.last.potion = future_ms(timeout); + if (!xp) { + to_update = "u+cid+reopen+nc"; + } + socket.emit("eval", { code: "pot_timeout(" + (timeout_ui || timeout) + ")" }); + } else if (!data.consume) { + // console.log(data); + var slot = data.slot || def.type; + var existing; + var comp = can_equip_item(player, def, slot); + if (comp == "no") { + resend(player, "u+cid+reopen"); // if you drop something on an invalid slot, it stays there for a bit otherwise [29/08/22] + return fail_response("cant_equip"); + } + slot = comp; + existing = player.slots[slot]; + player.slots[slot] = player.items[data.num]; + player.cslots[slot] = cache_item(player.slots[slot]); + if (existing && existing.b) { + existing = null; + } + player.items[data.num] = existing; + player.citems[data.num] = cache_item(existing); + player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 120, 120000) }; + resolve.slot = slot; + } else { + return fail_response("cant_consume"); + } + + if (to_update) { + resend(player, to_update); + } + success_response(resolve_type, resolve); + }); + socket.on("misc_npc", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var npc = players[data.npc]; + if (!npc || !npc.npc) { + return; + } + if (simple_distance(player, npc) > 1000) { + return socket.emit("game_response", "distance"); + } + if (data.npc == NPC_prefix + "Marven" && npc.misc == true) { + server_log("Here!"); + } + }); + socket.on("unequip", function (data) { + var player = players[socket.id]; + if (!player || !data.slot || !player.slots[data.slot]) { + return fail_response("invalid"); + } + if (data.slot == "elixir") { + return fail_response("cant"); + } + var item = player.slots[data.slot]; + var done = false; + if (in_arr(data.slot, trade_slots) && item.giveaway !== undefined) { + return fail_response("giveaway"); + } + // an oversight allowed .giveaway items to be equipped, so now they can be unequipped from regular slots [04/11/21] + if (player.esize <= 0 && !item.b) { + return fail_response("no_space"); + } + player.slots[data.slot] = null; + player.cslots[data.slot] = null; + player.c = {}; + if (!item.b) { + add_item(player, item, { announce: false }); + } + resend(player, "reopen+u+cid"); + success_response("data"); + }); + socket.on("secondhands", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (simple_distance(G.maps.main.ref.secondhands, player) > 500) { + return socket.emit("game_response", "distance"); + } + socket.emit("secondhands", csold); + }); + socket.on("lostandfound", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data == "info") { + return socket.emit("game_response", { response: "lostandfound_info", gold: S.gold }); + } + if (!player.donation) { + return socket.emit("game_response", "lostandfound_donate"); + } + if (simple_distance(G.maps.woffice.ref.lostandfound, player) > 500) { + return socket.emit("game_response", "distance"); + } + socket.emit("lostandfound", cfound); + }); + socket.on("split", function (data) { + var player = players[socket.id]; + var num = data.num; + var item = player.items[data.num]; + var quantity = min(max(parseInt(data.quantity) || 0, 1), (item && item.q) || 1); + if (!item) { + return fail_response("no_item"); + } + if (!G.items[item.name].s) { + return fail_response("invalid"); + } + quantity = min(quantity, G.items[item.name].s || 1); + if (!player.esize) { + return fail_response("cant_space"); + } + if (item.q == quantity) { + return success_response(); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + + consume(player, data.num, quantity); + + var new_item = cache_item(item); + if (item.q) { + new_item.q = quantity; + } + + for (var i = 0; i < player.isize; i++) { + if (!player.items[i]) { + player.items[i] = new_item; + player.citems[i] = cache_item(new_item); + break; + } + } + resend(player, "reopen"); + success_response(); + }); + socket.on("sell", function (data) { + var player = players[socket.id]; + var num = data.num; + var item = player.items[data.num]; + var quantity = min(max(parseInt(data.quantity) || 0, 1), (item && item.q) || 1); + var can_reach = false; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!item) { + return fail_response("no_item"); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + (G.maps[player.map].merchants || []).forEach(function (m) { + if (simple_distance(player, m) < B.sell_dist) { + can_reach = m; + } + }); + if (player.computer && !can_reach) { + can_reach = G.maps.main.merchants[0]; + } + if (!can_reach) { + return fail_response("distance"); + } + can_reach.name = player.name; + var value = calculate_item_value(item); + consume(player, data.num, quantity); + player.gold += value * quantity; + var new_item = cache_item(item); + if (item.q) { + new_item.q = quantity; + } + xy_emit(can_reach, "ui", { + type: "-$", + id: can_reach.id, + name: player.name, + item: new_item, + num: num, + event: "sell", + }); + resend(player, "reopen+nc+inv"); + success_response("gold_received", { gold: value * quantity, item: new_item, cevent: "sell" }); + secondhands_logic(item, quantity); // In the end, so if this fails, the "sell" still succeeds - otherwise it could cause a infinite gold loophole [10/07/18] + }); + socket.on("buy_shells", function (data) { + return socket.emit("game_log", "No longer possible"); + var player = players[socket.id]; + if (!player || player.user || gameplay == "hardcore" || gameplay == "test") { + return game_response("cant_in_bank"); + } + var gold = parseInt(data.gold) || 0; + if (gold < 1000000 || gold > player.gold) { + return socket.emit("game_response", "not_enough_gold"); + } + var shells = Math.floor(gold / G.multipliers.shells_to_gold); + player.gold -= gold; + socket.emit("game_log", "Gave " + to_pretty_num(gold) + " gold"); + appengine_call( + "bill_user", + { auth: player.auth, amount: -shells, reason: "buy_shells", name: player.name }, + function (result) { + server_log("buy_with_cash: " + JSON.stringify(result)); + if (result.failed || !result.done) { + socket.emit("game_log", "Purchase failed"); + return; + } + player.cash = result.cash; + socket.emit("game_log", "Received " + to_pretty_num(shells) + " shells"); + + resend(player, "reopen+nc"); + }, + ); + resend(player, "reopen+nc"); + }); + socket.on("buy_with_cash", function (data) { + var player = players[socket.id]; + var name = data.name; + var def = G.items[data.name]; + var quantity = min(max(parseInt(data.quantity) || 0, 1), (def && def.s) || 9999); + if (!player || player.user || gameplay == "hardcore" || gameplay == "test") { + return fail_response("cant_in_bank"); + } + var cost = def.cash * quantity; + if (!def.cash || def.ignore) { + return fail_response("invalid"); + } + if (def.p2w) { + return fail_response("invalid"); + } + if (!def.s) { + quantity = 1; + } + if (!can_add_item(player, create_new_item(name, quantity))) { + return fail_response("no_space"); + } + appengine_call( + "bill_user", + { auth: player.auth, amount: cost, reason: data.name, name: player.name }, + function (result) { + server_log("buy_with_cash: " + JSON.stringify(result)); + if (result.failed || !result.done) { + socket.emit("game_log", "Purchase failed"); + return; + } + player.cash = result.cash; + var new_item = create_new_item(name, quantity); + var done = false; + add_item(player, new_item, { announce: false }); + socket.emit("game_log", "Spent " + to_pretty_num(cost) + " shells"); + + resend(player, "reopen+nc+inv"); + }, + ); + success_response({ success: false, in_progress: true }); + }); + socket.on("sbuy", function (data) { + var player = players[socket.id]; + var done = false; + var npc = G.maps.main.ref.secondhands; + var c = S.sold; + var cc = csold; + var e = "+$p"; + var ev = "secondhands"; + var mult = 2; + var src = "scnd"; + if (data.f) { + npc = G.maps.woffice.ref.lostandfound; + c = S.found; + cc = cfound; + e = "+$f"; + ev = "lostandfound"; + mult = 4; + src = "lost"; + } + if (!player || player.user) { + return game_response("cant_in_bank"); + } + if (player.s.hopsickness && ev == "lostandfound") { + return fail_response("cant_when_sick", { goblin: true }); + } + if (simple_distance(npc, player) > 500) { + return socket.emit("game_response", "distance"); + } + for (var i = 0; i < c.length; i++) { + if (c[i].rid == data.rid) { + if (mult == 2 && G.items[c[i].name].cash) { + mult = 3; + } + var gold = calculate_item_value(c[i]) * mult * (c[i].q || 1); + var item = c[i]; + if (!can_add_item(player, c[i])) { + return disappearing_text(socket, player, "NO SPACE"); + } + if (gold > player.gold) { + return socket.emit("game_response", "buy_cost"); + } + player.gold -= gold; + item.src = src; + add_item(player, c[i], { announce: false }); + c.splice(i, 1); + cc.splice(i, 1); + socket.emit("game_log", "Spent " + to_pretty_num(gold) + " gold"); + resend(player, "reopen+nc+inv"); + socket.emit(ev, cc); + done = true; + xy_emit(npc, "ui", { type: e, id: npc.id, name: player.name, item: cache_item(item, 1) }); + break; + } + } + if (!done) { + return socket.emit("game_log", "Item gone"); + } + }); + socket.on("buy", function (data) { + var player = players[socket.id]; + var can_reach = false; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + var name = data.name; + var quantity = min(max(parseInt(data.quantity) || 0, 1), (G.items[name] && G.items[name].s) || 9999); + var cost = 0; + var added = false; + var done = false; + if (!can_buy[name] && gameplay != "test") { + return fail_response("buy_cant_npc"); + } + if (!can_add_item(player, create_new_item(name, quantity))) { + return fail_response("buy_cant_space"); + } + (G.maps[player.map].items[name] || []).forEach(function (l) { + if (simple_distance(player, l) < B.sell_dist) { + can_reach = l; + } + }); + if (player.computer && !can_reach) { + can_reach = G.maps.main.merchants[0]; + } + if (!can_reach) { + return fail_response("distance"); + } + can_reach.name = player.name; + var def = G.items[name]; + if (!def.s) { + quantity = 1; + } + cost = quantity * G.items[name].g; + if (G.items[name].p2w) { + cost *= G.inflation; + } + if (player.gold < cost) { + return fail_response("buy_cost"); + } + var new_item = create_new_item(name, quantity); + player.gold -= cost; + var num = add_item(player, new_item, { announce: false }); + xy_emit(can_reach, "ui", { + type: "+$", + id: can_reach.id, + name: player.name, + item: cache_item(new_item), + event: "buy", + }); + + resend(player, "reopen+nc+inv"); + success_response("buy_success", { cost: cost, num: num, name: name, q: quantity, cevent: "buy" }); + }); + socket.on("send", function (data) { + var player = players[socket.id]; + var receiver = players[name_to_id[data.name || ""]]; + var num; + var s_item; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!receiver || receiver.user) { + return fail_response("receiver_unavailable"); + } + if (distance(receiver, player, true) > B.dist || receiver.map != player.map) { + return fail_response("distance"); + } + if (data.num !== undefined) { + data.num = max(0, parseInt(data.num) || 0); + var item = player.items[data.num]; + if (!item) { + return fail_response("send_no_item"); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.l || (item.acl && receiver.owner != player.owner)) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + data.q = min(item.q || 1, max(1, parseInt(data.q || 1) || 1)); + if (!data.q) { + return fail_response("no_item"); + } + if (!can_add_item(receiver, create_new_item(item.name, data.q))) { + return fail_response("send_no_space"); + } + if ((item.q || 1) == data.q) { + player.items[data.num] = player.citems[data.num] = null; + player.esize++; + } else { + player.items[data.num].q -= data.q; + player.citems[data.num] = cache_item(player.items[data.num]); + } + + if (item.q) { + s_item = create_new_item(item.name, data.q); + if (item.v) { + s_item.v = item.v; + } + if (item.data) { + s_item.data = item.data; + } + num = add_item(receiver, s_item, { announce: false }); + } else { + s_item = item; + num = add_item(receiver, item, { announce: false }); + } + + if (receiver.owner != player.owner) { + item.src = "snd"; + } + + add_to_history(player, { name: "item", to: receiver.name, item: item.name, q: data.q, level: item.level }); + add_to_history(receiver, { name: "item", from: player.name, item: item.name, q: data.q, level: item.level }); + + xy_emit(player, "ui", { + type: "item_sent", + receiver: receiver.name, + sender: player.name, + item: cache_item(s_item, null, { q: data.q }), + num: num, + fnum: data.num, + event: true, + }); + + resend(player, "reopen+nc+inv"); + resend(receiver, "reopen+nc+inv"); + receiver.socket.emit("game_response", { + response: "item_received", + name: player.name, + item: item.name, + q: data.q, + num: num, + cevent: true, + }); + player.socket.emit("game_response", { + response: "item_sent", + name: receiver.name, + item: item.name, + q: data.q, + num: data.num, + cevent: true, + place: "send", + }); + } else if (data.gold !== undefined) { + data.gold = min(player.gold, max(1, parseInt(data.gold || 1) || 1)) || player.gold; + if (!data.gold) { + return fail_response("invalid"); + } + player.gold -= data.gold; + if (!is_same(player, receiver) && data.gold != 1) { + data.gold = parseInt(data.gold * 0.975); + } + receiver.gold += data.gold; + + add_to_history(player, { name: "gold", to: receiver.name, amount: data.gold }); + add_to_history(receiver, { name: "gold", from: player.name, amount: data.gold }); + + xy_emit(player, "ui", { + type: "gold_sent", + receiver: receiver.name, + sender: player.name, + gold: data.gold, + event: true, + }); + + resend(player, "reopen+nc+inv"); + resend(receiver, "reopen+nc+inv"); + receiver.socket.emit("game_response", { + response: "gold_received", + name: player.name, + gold: data.gold, + cevent: true, + }); + player.socket.emit("game_response", { + response: "gold_sent", + name: receiver.name, + gold: data.gold, + cevent: true, + place: "send", + }); + } else if (data.cx !== undefined) { + var cxl = map_cx(player); + var count = all_cx(player, 1); + if (!(cxl[data.cx] && player.p.acx[cxl[data.cx]] && count[data.cx] > 0)) { + return fail_response("send_no_cx"); + } + // count[cxl[data.cx]] before [28/01/22] + if (receiver.owner != player.owner) { + return fail_response("send_diff_owner"); + } + player.p.acx[cxl[data.cx]] -= 1; + if (!player.p.acx[cxl[data.cx]]) { + delete player.p.acx[cxl[data.cx]]; + } + receiver.p.acx[cxl[data.cx]] = (receiver.p.acx[cxl[data.cx]] || 0) + 1; + + xy_emit(player, "ui", { + type: "cx_sent", + receiver: receiver.name, + sender: player.name, + cx: data.cx, + event: true, + }); + + receiver.socket.emit("game_response", { + response: "cx_received", + name: player.name, + cx: data.cx, + acx: receiver.p.acx, + cevent: true, + }); + player.socket.emit("game_response", { + response: "cx_sent", + name: receiver.name, + cx: data.cx, + acx: player.p.acx, + cevent: true, + place: "send", + }); + } + }); + socket.on("donate", function (data) { + var player = players[socket.id]; + var XPX = 3.2; + if (!player || player.user) { + return game_response("cant_in_bank"); + } + var gold = max(1, min(parseInt(data.gold) || 0, 1000000000)); + if (gold > player.gold) { + return socket.emit("game_response", "gold_not_enough"); + } + if (gold >= 1000000) { + response = "thx"; + player.donation = true; + } else if (gold < 100000) { + response = "low"; + } else { + add_item(player, "gum"); + response = "gum"; + } + if (S.gold < 500000000) { + XPX = 4.8; + } else if (S.gold <= 1000000000) { + XPX = 4; + } + player.gold -= gold; + S.gold += gold; + if (gold >= 5000000) { + lstack(S.logs.donate, { name: player.name, gold: gold, xp: XPX }); + } + if (player.type == "merchant") { + player.xp += parseInt(gold * XPX); + } + resend(player, "reopen"); + socket.emit("game_response", { response: "donate_" + response, gold: gold, xprate: XPX }); + }); + socket.on("destroy", function (data) { + var player = players[socket.id]; + var add = "+nc+inv"; + data.num = max(0, parseInt(data.num) || 0); + if (!player.items[data.num]) { + return fail_response("no_item"); + } + var item = player.items[data.num]; + var name = player.items[data.num].name; + data.q = min(max(parseInt(data.q) || 0, 1), (item && item.q) || 1); + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + if (item.level != 13) { + consume(player, data.num, data.q); + //player.items[data.num]=player.citems[data.num]=null; + } + if (data.statue) { + if (item.name == "shadowstone") { + add = "+u+cid"; + player.s.invis = { ms: 99999 }; + } + if (G.items[item.name].upgrade && Math.random() < 1.0 / ((gameplay == "hardcore" && 10000) || 1000000)) { + add = "+u+cid"; + item.level = 13; + player.items[data.num] = item; + player.citems[data.num] = cache_item(player.items[data.num]); + } + xy_emit(G.maps.spookytown.ref.poof, "upgrade", { type: "poof", success: 1 }); + } + resend(player, "reopen" + add); + success_response("destroyed", { name: name, place: "destroy", cevent: "destroy" }); + }); + socket.on("join_giveaway", function (data) { + var player = players[socket.id]; + var seller = players[id_to_id[data.id]]; + var num; + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!in_arr(data.slot, trade_slots)) { + return fail_response("invalid"); + } + if (!seller || seller.npc || is_invis(seller)) { + return fail_response("seller_gone"); + } + if (seller.user) { + return fail_response("cant_in_bank"); + } + if (distance(seller, player, true) > B.dist || seller.map != player.map) { + return fail_response("distance"); + } + if (seller.id == player.id) { + return fail_response("hmm"); + } + var item = seller.slots[data.slot]; + if (!item || (data.rid && item.rid != data.rid)) { + return fail_response("item_gone"); + } + if (!item.giveaway) { + return fail_response("sneaky"); + } + if (!player.auth_id) { + return fail_response("need_auth"); + } + //if(item.list.includes(player.name)) return socket.emit("game_log","Already joined!"); + //if(player.type!="merchant") return socket.emit("game_log","Only merchants can join giveaways!"); + + //item.list.push(player.name); + + item.registry = item.registry || {}; + item.registry[player.auth_id] = player.name; + item.list = Object.values(item.registry); + + socket.emit("game_log", "Joined the giveaway!"); + seller.socket.emit("game_response", { + response: "giveaway_join", + name: player.name, + slot: data.slot, + cevent: true, + }); + + resend(player, "reopen"); + resend(seller, "reopen+u+cid"); + success_response({}); + }); + socket.on("trade_wishlist", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + data.q = min(9999, max(1, parseInt(data.q || 1) || 1)); + if (!in_arr(data.slot, trade_slots) || !G.items[data.name] || data.name == "placeholder") { + return fail_response("invalid"); + } + if (player.slots[data.slot] && !player.slots[data.slot].b) { + return fail_response("slot_occuppied"); + } + if (!get_trade_slots(player).includes(data.slot)) { + return fail_response("invalid"); + } + var item = { + name: data.name, + rid: randomStr(4), + price: round(min(99999999999, max(parseInt(data.price) || 1, 1))), + b: true, + }; + if (G.items[data.name].upgrade || G.items[data.name].compound) { + item.q = min(99, data.q); + item.level = round(min(12, max(parseInt(data.level) || 0, 0))); + } else { + item.q = data.q; + } + player.slots[data.slot] = item; + player.cslots[data.slot] = cache_item(player.slots[data.slot], true); + resend(player, "reopen+u+cid+nc"); + success_response({}); + }); + socket.on("trade_sell", function (data) { + // if(!is_sdk) return; + var player = players[socket.id]; + var buyer = players[id_to_id[data.id]]; + var num; + var actual = null; + var num = null; + data.q = max(1, parseInt(data.q || 1) || 1); + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!in_arr(data.slot, trade_slots)) { + return fail_response("invalid"); + } + if (!buyer || buyer.npc || is_invis(buyer)) { + return fail_response("buyer_gone"); + } + if (player.user) { + return fail_response("cant_in_bank"); + } + if (distance(buyer, player, true) > B.dist) { + return fail_response("distance"); + } + if (buyer.id == player.id) { + return fail_response("hmm"); + } + var item = buyer.slots[data.slot]; + if (!item || (data.rid && item.rid != data.rid)) { + return fail_response("item_gone"); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (!item.b && !B.rbugs) { + return fail_response("sneaky"); + } + if ((item.q || 1) < data.q) { + return fail_response("dont_have_enough"); + } + if (item.price * data.q > buyer.gold) { + return fail_response("buyer_gold"); + } + for (var i = 0; i < player.isize; i++) { + if ( + player.items[i] && + player.items[i].name == item.name && + (player.items[i].q || 1) >= data.q && + player.items[i].level == item.level && + !player.items[i].l + ) { + actual = player.items[i]; + num = i; + break; + } + } + if (!actual) { + return fail_response("no_item"); + } + if (actual.b) { + return fail_response("item_blocked"); + } + if (!can_add_item(buyer, create_new_item(item.name, data.q))) { + return fail_response("trade_bspace"); + } + var price = item.price * data.q; + player.gold += round(price * (1 - player.tax)); + add_to_trade_history(player, "sell", buyer.name, cache_item(actual, true, { q: data.q }), price); + buyer.gold -= price; + add_to_trade_history(buyer, "buy", player.name, cache_item(actual, true, { q: data.q }), price); + S.gold += price - round(price * (1 - player.tax)); + + if ((item.q || 1) == data.q) { + buyer.slots[data.slot] = buyer.cslots[data.slot] = null; + } else { + buyer.slots[data.slot].q -= data.q; + buyer.cslots[data.slot] = cache_item(buyer.slots[data.slot], true); + } + + // up to here + + if ((player.items[i].q || 1) == data.q) { + player.items[num] = player.citems[num] = null; + } else { + player.items[num].q -= data.q; + player.citems[num] = cache_item(player.items[num]); + } + + if (G.items[item.name].s) { + bnum = add_item(buyer, create_new_item(item.name, data.q), { announce: false }); + } else { + bnum = add_item(buyer, actual, { announce: false }); + } + + if (player.type == "merchant") { + merchant_xp_logic(player, buyer, price, price - round(price * (1 - player.tax))); + } + if (buyer.type == "merchant") { + merchant_xp_logic(buyer, player, price, price - round(price * (1 - buyer.tax))); + } + + socket.emit( + "game_log", + "Sales tax " + to_pretty_num(price - round(price * (1 - player.tax))) + " gold [" + player.tax * 100 + "%]", + ); + socket.emit("game_log", "Received " + to_pretty_num(round(price * (1 - player.tax))) + " gold"); + buyer.socket.emit("game_log", "Spent " + to_pretty_num(price) + " gold"); + + xy_emit(buyer, "ui", { + type: "+$$", + seller: player.name, + buyer: buyer.name, + item: cache_item(actual, true, { q: data.q, price: item.price }), + slot: data.slot, + num: bnum, + snum: num, + }); + + resend(player, "reopen"); + resend(buyer, "reopen+u+cid"); + success_response({}); + }); + socket.on("trade_buy", function (data) { + var player = players[socket.id]; + var seller = players[id_to_id[data.id]]; + var num; + data.q = max(1, parseInt(data.q || 1) || 1); + if (!player || player.user) { + return fail_response("cant_in_bank"); + } + if (!in_arr(data.slot, trade_slots)) { + return fail_response("invalid"); + } + if (!seller || seller.npc || is_invis(seller)) { + return fail_response("seller_gone"); + } + if (seller.user) { + return fail_response("cant_in_bank"); + } + if (distance(seller, player, true) > B.dist || seller.map != player.map) { + return fail_response("distance"); + } + if (seller.id == player.id) { + return fail_response("hmm"); + } + var item = seller.slots[data.slot]; + if (!item || (data.rid && item.rid != data.rid)) { + return fail_response("item_gone"); + } + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.b || item.giveaway) { + return fail_response("sneaky"); + } + if (item.price * data.q > player.gold) { + return fail_response("gold_not_enough"); + } + if ((item.q || 1) < data.q) { + return fail_response("insufficient_q"); + } + if (!can_add_item(player, create_new_item(item.name, data.q))) { + return fail_response("no_space"); + } + var price = item.price * data.q; + player.gold -= price; + add_to_trade_history(player, "buy", seller.name, cache_item(seller.slots[data.slot], true, { q: data.q }), price); + seller.gold += round(price * (1 - seller.tax)); + add_to_trade_history( + seller, + "sell", + player.name, + cache_item(seller.slots[data.slot], true, { q: data.q }), + price, + ); + S.gold += price - round(price * (1 - seller.tax)); + + if ((item.q || 1) == data.q) { + seller.slots[data.slot] = seller.cslots[data.slot] = null; + } else { + seller.slots[data.slot].q -= data.q; + seller.cslots[data.slot] = cache_item(seller.slots[data.slot], true); + } + + if (item.q) { + num = add_item(player, create_new_item(item.name, data.q), { announce: false }); + } else { + num = add_item(player, item, { announce: false }); + } + + if (seller.owner != player.owner) { + item.src = "tb"; + } + + if (player.type == "merchant") { + merchant_xp_logic(player, seller, price, price - round(price * (1 - player.tax))); + } + if (seller.type == "merchant") { + merchant_xp_logic(seller, player, price, price - round(price * (1 - seller.tax))); + } + + socket.emit("game_log", "Spent " + to_pretty_num(price) + " gold"); + seller.socket.emit( + "game_log", + "Sales tax " + to_pretty_num(price - round(price * (1 - seller.tax))) + " gold [" + seller.tax * 100 + "%]", + ); + seller.socket.emit("game_log", "Received " + to_pretty_num(round(price * (1 - seller.tax))) + " gold"); + + xy_emit(seller, "ui", { + type: "+$$", + seller: seller.name, + buyer: player.name, + item: cache_item(item, true, { q: data.q }), + slot: data.slot, + num: data.num, + }); + + resend(player, "reopen"); + resend(seller, "reopen+u+cid"); + success_response({}); + }); + socket.on("trade_history", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + server_log("trade_history: " + player.name); + socket.emit("trade_history", player.p.trade_history || []); + }); + socket.on("merchant", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var initial = player.p.stand; + server_log("merchant: " + player.name); + if (data.close || player.p.stand) { + player.p.stand = false; + for (var i = 0; i < player.items.length; i++) { + if (player.items[i] && player.items[i].b == "stand") { + delete player.items[i].b; + } + } + } + if (data.num !== undefined) { + var item = player.items[data.num]; + if (item && G.items[item.name].stand) { + player.p.stand = G.items[item.name].stand; + item.b = "stand"; + } else { + return fail_response("invalid"); + } + } + if (initial != player.p.stand) { + // All unneccessary causes of resend's should be patched [03/08/18] + reslot_player(player); + resend(player, "u+cid"); + } + success_response({}); + }); + socket.on("imove", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + data.a = max(0, parseInt(data.a) || 0); + data.b = max(0, parseInt(data.b) || 0); + if (data.a == data.b) { + return fail_response("invalid"); + } + var a = min(data.a, data.b); + var b = max(data.a, data.b); + var a_item; + if (!(b < player.isize || b < player.items.length)) { + return fail_response("invalid"); + } + // while(b>=player.items.length) player.items.push(null); [22/11/16] + if (player.items[a] && player.items[a].name == "placeholder") { + return fail_response("item_placeholder"); + } + if (player.items[b] && player.items[b].name == "placeholder") { + return fail_response("item_placeholder"); + } + if (can_stack(player.items[a], player.items[b])) { + player.items[data.a].q = (player.items[a].q || 1) + (player.items[b].q || 1); + player.items[data.b] = null; + player.esize++; + } else { + a_item = player.items[a]; + player.items[a] = player.items[b]; + player.items[b] = a_item; + } + player.citems[data.a] = cache_item(player.items[data.a]); + player.citems[data.b] = cache_item(player.items[data.b]); + resend(player, "reopen+nc+inv"); + return success_response("data"); + }); + socket.on("bank", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!player.user || player.mounting || player.unmounting) { + return fail_response("bank_unavailable"); + } + var success = {}; + if (data.operation == "withdraw") { + var amount = max(0, min(parseInt(data.amount) || 0, player.user.gold)); + player.user.gold -= amount; + player.gold += amount; + success = { response: "bank_withdraw", gold: amount, cevent: true }; + } + if (data.operation == "deposit") { + var amount = max(0, min(parseInt(data.amount) || 0, player.gold)); + player.user.gold += amount; + player.gold -= amount; + success = { response: "bank_store", gold: amount, cevent: true }; + } + if (data.operation == "unlock") { + if (!bank_packs[data.pack]) { + return fail_response("invalid"); + } + var gold = bank_packs[data.pack][1]; + var shells = bank_packs[data.pack][2]; + if (!gold) { + return fail_response("gold_not_enough"); + } + if (player.user[data.pack]) { + return fail_response("invalid"); + } + if (data.gold) { + if (player.gold < gold) { + return fail_response("gold_not_enough"); + } + player.gold -= gold; + player.user[data.pack] = []; + player.cuser[data.pack] = []; + success = { response: "bank_new_pack", pack: data.pack, gold: gold, cevent: true }; + } else if (data.shells) { + if (player["unlocking_" + data.pack]) { + return fail_response("bank_opi"); + } + player["unlocking_" + data.pack] = true; + appengine_call( + "bill_user", + { + auth: player.auth, + amount: shells, + reason: data.pack, + name: player.name, + suffix: "/" + player.name + "/" + data.pack, + override: true, + }, + function (result) { + server_log("buy_with_cash: " + JSON.stringify(result)); + player["unlocking_" + data.pack] = false; + if (result.failed || !result.done) { + return socket.emit("game_log", "Purchase failed"); + } + player.cash = result.cash; + + player.user[data.pack] = []; + player.cuser[data.pack] = []; + game_response("bank_new_pack", { cevent: true, pack: data.pack, shells: shells }); + + resend(player, "reopen"); + }, + ); + success = { success: false, in_progress: true }; + } + } + if (data.operation == "move") { + //within a .itemsN + if (!player.user[data.pack] || bank_packs[data.pack][0] != player.map) { + return fail_response("invalid"); + } + server_log("storage move " + JSON.stringify(data)); + data.a = max(0, min(41, parseInt(data.a) || 0)); + data.b = max(0, min(41, parseInt(data.b) || 0)); + if (data.a == data.b) { + return fail_response("invalid"); + } + if (player.user[data.pack][data.a] && player.user[data.pack][data.a].name == "placeholder") { + return fail_response("item_placeholder"); + } + if (player.user[data.pack][data.b] && player.user[data.pack][data.b].name == "placeholder") { + return fail_response("item_placeholder"); + } + if (can_stack(player.user[data.pack][data.a], player.user[data.pack][data.b])) { + player.user[data.pack][data.b].q = + (player.user[data.pack][data.a].q || 1) + (player.user[data.pack][data.b].q || 1); + player.user[data.pack][data.a] = null; + } else { + var temp = player.user[data.pack][data.a]; + player.user[data.pack][data.a] = player.user[data.pack][data.b]; + player.user[data.pack][data.b] = temp; + } + player.cuser[data.pack][data.a] = cache_item(player.user[data.pack][data.a]); + player.cuser[data.pack][data.b] = cache_item(player.user[data.pack][data.b]); + } + if (data.operation == "swap") { + //between .items and a .itemsN + var operation = "swap"; + if (!player.user[data.pack] || bank_packs[data.pack][0] != player.map) { + return fail_response("invalid"); + } + data.str = parseInt(data.str); + data.inv = parseInt(data.inv); + if (data.inv == -1 || (!data.inv && data.inv !== 0)) { + operation = "pull"; + for (var i = 0; i < player.isize; i++) { + if (!player.items[i]) { + data.inv = i; + break; + } + } + // if(data.inv==-1) { socket.emit("game_log","Inventory is full"); return; } + } + if (data.str == -1 || (!data.str && data.str !== 0)) { + if (operation == "pull") { + return fail_response("invalid"); + } + operation = "store"; + for (var i = 0; i < 42; i++) { + if (!player.user[data.pack][i]) { + data.str = i; + break; + } + } + // if(data.str==-1) { socket.emit("game_log","Storage is full"); return; } + } + server_log("storage swap " + JSON.stringify(data)); + data.str = max(0, min(41, parseInt(data.str) || 0)); + data.inv = max(0, min(player.isize - 1, parseInt(data.inv) || 0)); + var bank_item = player.user[data.pack][data.str]; + var inv_item = player.items[data.inv]; + if (inv_item && inv_item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (inv_item && inv_item.b) { + return fail_response("item_blocked"); + } + if (inv_item) { + delete inv_item.m; + delete inv_item.v; + } + if (operation == "swap") { + player.user[data.pack][data.str] = inv_item; + player.items[data.inv] = bank_item; + player.cuser[data.pack][data.str] = cache_item(player.user[data.pack][data.str]); + player.citems[data.inv] = cache_item(player.items[data.inv]); + } else if (operation == "store" && inv_item) { + if (!can_add_item(player.user[data.pack], inv_item)) { + return fail_response("storage_full"); + } + player.items[data.inv] = player.citems[data.inv] = null; + bank_add_item(player, data.pack, inv_item); + } else if (operation == "pull" && bank_item) { + if (!can_add_item(player, bank_item)) { + return fail_response("inventory_full"); + } + player.user[data.pack][data.str] = player.cuser[data.pack][data.str] = null; + add_item(player, bank_item, { announce: false }); + } + } + if (!player.user.gold && player.user.gold !== 0) { + player.user.gold = 0; + server_log("#X - GOLD BUG bank", 1); + } + resend(player, "reopen"); + success_response(success); + }); + socket.on("throw", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var item = player.items[data.num]; + if (item) { + if (item.name == "placeholder") { + return fail_response("item_placeholder"); + } + if (item.l) { + return fail_response("item_locked"); + } + if (item.b) { + return fail_response("item_blocked"); + } + var def = G.items[item.name]; + if (!def.throw) { + return fail_response("invalid"); + } + var x = parseFloat(data.x) || 0; + var y = parseFloat(data.y) || 0; + if (distance(player, { map: player.map, in: player.in, x: x, y: y }) > player.str * 3) { + fail_response("too_far"); + } + consume_one(player, data.num); + if (item.name == "confetti") { + player.thrilling = future_s(20); + xy_emit( + { map: player.map, in: player.in, x: x, y: y }, + "eval", + "confetti_shower({in:'" + player.in + "',map:'" + player.map + "',real_x:" + x + ",real_y:" + y + "})", + ); + } + if (item.name == "firecrackers") { + player.thrilling = future_s(200); + xy_emit( + { map: player.map, in: player.in, x: x, y: y }, + "eval", + "firecrackers({in:'" + player.in + "',map:'" + player.map + "',real_x:" + x + ",real_y:" + y + "})", + ); + for (var id in instances[player.in].monsters) { + var monster = instances[player.in].monsters[id]; + if (monster.target && distance({ map: player.map, in: player.in, x: x, y: y }, monster) < 64) { + stop_pursuit(monster, { force: true, cause: "firecrackers" }); + } + } + } + if (item.name == "whiteegg") { + xy_emit({ map: player.map, in: player.in, x: x, y: y }, "eval", "egg_splash(" + x + "," + y + ")"); + for (var id in instances[player.in].monsters) { + var monster = instances[player.in].monsters[id]; + if (!monster.target && distance({ map: player.map, in: player.in, x: x, y: y }, monster) < 32) { + target_player(monster, player); + } + } + } + if (item.name == "smoke") { + xy_emit({ map: player.map, in: player.in, x: x, y: y }, "eval", "assassin_smoke(" + x + "," + y + ");"); + } + resend(player, "reopen+nc+inv"); + success_response({}); + } else { + fail_response("no_item"); + } + }); + socket.on("poke", function (data) { + var player = players[socket.id]; + var level = 1; + if (!player || !player.slots.gloves || player.slots.gloves.name != "poker") { + return; + } + if (player.pokes >= 50) { + return socket.emit("game_log", "You are out of pokes!"); + } + player.pokes = (player.pokes || 0) + 1; + if (player.slots.gloves.level >= 10) { + level = 4; + } else if (player.slots.gloves.level == 9) { + level = 3; + } else if (player.slots.gloves.level == 8) { + level = 2; + } + xy_emit(player, "poke", { name: data.name, level: level, who: player.name }); + }); + socket.on("merge", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var container = player.slots[data.container]; + var pet = player.slots[data.pet]; + if (!container || container.type != "container" || !pet || pet.type != "container") { + return socket.emit("game_response", "merge_mismatch"); + } + if (G.items[container.name].grade < G.items[pet.name].grade || container.data) { + return socket.emit("game_response", "merge_mismatch"); + } + container.data = G.items[pet.name].pet; + player.slots[data.pet] = null; + socket.emit("game_response", "merge_complete"); + resend(player, "reopen"); + }); + socket.on("activate", function (data) { + var player = players[socket.id]; + // console.log(JSON.stringify(data)); + if (!player) { + return; + } + if (data.slot) { + var item = player.slots[data.slot]; + if (!item || in_arr(data.slot, trade_slots)) { + return; + } + if (item.name == "etherealamulet") { + if (player.last_ethereal && mssince(player.last_ethereal) < 120) { + return socket.emit("game_response", "not_ready"); + } + player.last_ethereal = new Date(); + player.s.ethereal = { ms: 5000 }; + socket.emit("eval", { code: "skill_timeout('ethereal',120)" }); + return resend(player, "u+cid"); + } else if (item.name == "angelwings") { + if (player.tskin == "snow_angel") { + player.tskin = ""; + } else if (item.level >= 8 && (player.type == "mage" || player.type == "priest")) { + player.tskin = "snow_angel"; + } else { + return socket.emit("game_response", "nothing"); + } + } else if (item.name == "tristone") { + var on = true; + if (player.tskin || player.tactivations == 100) { + player.tskin = ""; + on = false; + } else if (item.level <= 1 && deduct_gender(player) == "female") { + player.tskin = random_one(["tf_green", "tf_pink"]); + } else if (item.level == 2 && deduct_gender(player) == "female") { + player.tskin = random_one(["tf_blue", "tf_purple"]); + } else if (deduct_gender(player) == "female") { + player.tskin = "tf_orange"; + } else if (item.level <= 1) { + player.tskin = random_one(["tm_gray", "tm_brown", "tm_white"]); + } else if (item.level == 2) { + player.tskin = random_one(["tm_green", "tm_yellow", "tm_purple"]); + } else { + player.tskin = random_one(["tm_blue", "tm_red"]); + } + player.tactivations = (player.tactivations || 0) + 1; + } else if (item.name == "darktristone") { + var on = true; + if (player.tskin || player.tactivations == 100) { + player.tskin = ""; + on = false; + } else if (item.level == 4 && deduct_gender(player) == "female") { + player.tskin = "mf_blue"; + } else if (deduct_gender(player) == "female") { + player.tskin = "mf_yellow"; + } else if (item.level == 4) { + player.tskin = "mm_blue"; + } else { + player.tskin = "mm_yellow"; + } + player.tactivations = (player.tactivations || 0) + 1; + } else { + return; + } + resend(player, "reopen+nc+inv+u+cid"); + } else { + var item = player.items[data.num]; + if (item) { + if (item.name == "frozenstone") { + consume_one(player, data.num); + } + if (item.name == "bkey") { + if (!player.user) { + return socket.emit("game_response", "only_in_bank"); + } + if (player.user.unlocked && player.user.unlocked.bank_b) { + return socket.emit("game_response", "already_unlocked"); + } + consume_one(player, data.num); + player.user.unlocked = player.user.unlocked || {}; + player.user.unlocked.bank_b = new Date(); + player.user.items8 = player.user.items8 || []; + socket.emit("game_response", "door_unlocked"); + } + if (item.name == "ukey") { + if (!player.user) { + return socket.emit("game_response", "only_in_bank"); + } + if (player.user.unlocked && player.user.unlocked.bank_u) { + return socket.emit("game_response", "already_unlocked"); + } + consume_one(player, data.num); + player.user.unlocked = player.user.unlocked || {}; + player.user.unlocked.bank_u = new Date(); + player.user.items24 = player.user.items24 || []; + socket.emit("game_response", "door_unlocked"); + } + if (item.name == "dkey") { + if (!player.user) { + return socket.emit("game_response", "only_in_bank"); + } + var found = false; + for (var i = 0; i < 48; i++) { + var pack = "items" + i; + if (!bank_packs[pack] || player.user[pack]) { + continue; + } + found = true; + player.user[pack] = []; + player.cuser[pack] = []; + break; + } + if (!found) { + return; + } + consume_one(player, data.num); + socket.emit("game_response", "bank_pack_unlocked"); + } + } + resend(player, "reopen+nc+inv"); + } + }); + socket.on("booster", function (data) { + var player = players[socket.id]; + var item = player.items[data.num]; + if (!player) { + return; + } + server_log("booster " + data.num + " " + data.action); + if (!item || !in_arr(item.name, booster_items)) { + return fail_response("invalid"); + } + if (data.action == "activate" && !item.expires) { + item.expires = new Date(); + item.expires.setDate(item.expires.getDate() + 30 + (item.level || 0) * 2); + //item.expires.setMinutes(item.expires.getMinutes()+2); + } else if (data.action == "shift") { + if (!in_arr(data.to, booster_items)) { + return fail_response("invalid"); + } + player.xpm = player.goldm = player.luckm = 1; + item.name = data.to; + player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 240, 120000) }; + } + // if(item.expires) item.p="legacy"; + player.citems[data.num] = cache_item(player.items[data.num]); + resend(player, "reopen"); + success_response({ name: player.items[data.num].name }); + }); + socket.on("convert", function (data) { + // There are a lot of routines that could give birth to an endgame bug + // This routine ended up being the one + // "stone" boosters were originally 1200 shells + // when they were discontinued, I let players exchange them for 3600 shells instead + // turns out 'buy_with_cash' didn't check whether an item has "ignore" or not + // so one could buy the stones for 1200 shells and sell them for 3600 + // unlocking infinite gold and shells [17/01/18] + var player = players[socket.id]; + var item = player.items[data.num]; + var amount = 3600; + server_log("stone " + data.num + " " + data.action); + if (!item || !in_arr(item.name, ["stoneofxp", "stoneofgold", "stoneofluck"])) { + return; + } + if (item.expires) { + amount = round(600 - (hsince(item.expires) * 100) / 24.0); + } + add_shells(player, amount, "convert"); + player.items[data.num] = null; + player.citems[data.num] = cache_item(player.items[data.num]); + resend(player, "reopen+cid"); + }); + socket.on("emotion", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!data.name) { + data.name = random_one(Object.keys(player.p.emx)); + } + if (player.last.emotion && mssince(player.last.emotion) < 2000) { + return socket.emit("game_response", "emotion_cooldown"); + } + player.last.emotion = new Date(); + if (!G.emotions[data.name] || !player.p.emx[data.name]) { + return socket.emit("game_response", "emotion_cant"); + } + xy_emit(player, "emotion", { name: data.name, player: player.name }); + }); + socket.on("skill", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var target = null; + var cool = true; + var resolve = { response: "data", place: data.name, success: true }; + var reject = null; + player.first = true; + G.skills.attack.cooldown = player.attack_ms; + server_log("skill " + JSON.stringify(data)); + if (is_disabled(player)) { + return fail_response("disabled", data.name); + } + if ((player.tskin == "konami" || is_silenced(player)) && data.name != "attack") { + return socket.emit("game_response", { response: "skill_cant_incapacitated", place: data.name, failed: true }); + } + if (!G.skills[data.name]) { + return socket.emit("game_response", { response: "no_skill", place: data.name, failed: true }); + } + if ( + G.skills[data.name].mp && + player.mp < (G.skills[data.name].mp * (100 - (player.mp_reduction || 0))) / 100.0 && + !(player.role == "gm" && data.name == "blink") + ) { + return socket.emit("game_response", { response: "no_mp", place: data.name, failed: true }); + } + if (G.skills[data.name].level && player.level < G.skills[data.name].level) { + return socket.emit("game_response", { response: "no_level", place: data.name, failed: true }); + } + if ( + (G.skills[data.name].cooldown || G.skills[data.name].reuse_cooldown) && + player.last[data.name] && + mssince(player.last[data.name]) < (G.skills[data.name].cooldown || G.skills[data.name].reuse_cooldown) + ) { + return socket.emit("game_response", { + response: "cooldown", + skill: data.name, + place: data.name, + id: data.id, + ms: (G.skills[data.name].cooldown || G.skills[data.name].reuse_cooldown) - mssince(player.last[data.name]), + failed: true, + }); + } + if ( + G.skills[data.name].share && + player.last[G.skills[data.name].share] && + mssince(player.last[G.skills[data.name].share]) < + G.skills[G.skills[data.name].share].cooldown * (G.skills[data.name].cooldown_multiplier || 1) + ) { + return socket.emit("game_response", { + response: "cooldown", + skill: data.name, + place: data.name, + failed: true, + id: data.id, + ms: + G.skills[G.skills[data.name].share].cooldown * (G.skills[data.name].cooldown_multiplier || 1) - + mssince(player.last[G.skills[data.name].share]), + }); + } + if (G.skills[data.name].class && !in_arr(player.type, G.skills[data.name].class) && player.role != "gm") { + return socket.emit("game_response", { response: "skill_cant_use", place: data.name, failed: true }); + } + if ( + G.skills[data.name].wtype && + !is_array(G.skills[data.name].wtype) && + (!player.slots.mainhand || G.items[player.slots.mainhand.name].wtype != G.skills[data.name].wtype) && + player.role != "gm" + ) { + return socket.emit("game_response", { response: "skill_cant_wtype", place: data.name, failed: true }); + } + if ( + is_array(G.skills[data.name].wtype) && + (!player.slots.mainhand || !in_arr(G.items[player.slots.mainhand.name].wtype, G.skills[data.name].wtype)) && + player.role != "gm" + ) { + return socket.emit("game_response", { response: "skill_cant_wtype", place: data.name, failed: true }); + } + if (G.skills[data.name].hostile && G.maps[player.map].safe) { + return socket.emit("game_response", { response: "skill_cant_safe", place: data.name, failed: true }); + } + if (G.skills[data.name].target) { + if ( + ("" + parseInt(data.id) === "" + data.id && G.skills[data.name].target == "player") || + ("" + parseInt(data.id) !== "" + data.id && G.skills[data.name].target == "monster") + ) { + return fail_response("invalid_target", data.name, { id: data.id }); + } + if (G.skills[data.name].target != "monster" && players[id_to_id[data.id]]) { + target = players[id_to_id[data.id]]; + if (G.skills[data.name].hostile && target.name == player.name) { + return socket.emit("game_response", { response: "no_target", place: data.name, failed: true }); + } + } else if (G.skills[data.name].target != "player" && instances[player.in].monsters[data.id]) { + target = instances[player.in].monsters[data.id]; + } else { + return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" }); + } + + if ( + is_invis(target) || + (!G.skills[data.name].global && (target.map != player.map || distance(target, player, true) > B.max_vision)) + ) { + return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" }); + } + } + if (G.skills[data.name].requirements) { + for (var requirement in G.skills[data.name].requirements) { + if (!player[requirement] || player[requirement] < G.skills[data.name].requirements[requirement]) { + return socket.emit("game_response", { + response: "skill_cant_requirements", + place: data.name, + failed: true, + }); + } + } + } + if (G.skills[data.name].slot) { + var found = false; + var charges = false; + G.skills[data.name].slot.forEach(function (p) { + if (player.slots[p[0]] && player.slots[p[0]].name == p[1]) { + if (G.items[player.slots[p[0]].name].charge) { + if ((player.slots[p[0]].charges || 0) < G.items[player.slots[p[0]].name].charge) { + charges = true; + return; + } + player.slots[p[0]].charges -= G.items[player.slots[p[0]].name].charge; + player.cslots[p[0]] = cache_item(player.slots[p[0]]); + } + found = true; + } + }); + if (charges) { + return socket.emit("game_response", { response: "skill_cant_charges", place: data.name, failed: true }); + } + if (!found) { + return socket.emit("game_response", { response: "skill_cant_slot", place: data.name, failed: true }); + } + } + if (player.s.invincible) { + delete player.s.invincible; + player.to_resend = "u+cid"; + } + if (data.name == "attack" || data.name == "heal") { + var attack = commence_attack(player, target, data.name); + if (!attack.failed) { + resolve = attack; + } else { + reject = attack; + cool = false; + } + } + if (data.name == "invis" && !player.s.invis) { + if (player.s.marked) { + return socket.emit("game_response", { response: "skill_cant_use", place: data.name, failed: true }); + } + player.s.invis = { ms: 999999999999999 }; + xy_emit(player, "disappear", { id: player.id, invis: true, reason: "invis" }); + player.to_resend = " "; + } + if (data.name == "pickpocket") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + + consume_mp(player, G.skills[data.name].mp); + player.c[data.name] = { + ms: + G.skills[data.name].duration_min + + Math.random() * (G.skills[data.name].duration_max - G.skills[data.name].duration_min), + target: target.name, + }; + player.to_resend = "u+cid"; + resolve = { response: "data", place: data.name, success: false, in_progress: true }; + } + if (data.name == "fishing" || data.name == "mining") { + var direction = 0; + var the_zone = null; + consume_mp(player, G.skills[data.name].mp); + (G.maps[player.map].zones || []).forEach(function (zone) { + if (zone.type != data.name || the_zone) { + return; + } + [ + [0, -24, 3], + [-24, 0, 1], + [24, 0, 2], + [0, 24, 0], + ].forEach(function (m) { + if (is_point_inside([player.x + m[0], player.y + m[1]], zone.polygon)) { + the_zone = zone; + direction = m[2]; + } + }); + }); + if (!the_zone || player.moving) { + xy_emit(player, "ui", { type: data.name + "_fail", name: player.name }); + reject = { response: "data", place: data.name }; + } else { + xy_emit(player, "ui", { type: data.name + "_start", name: player.name, direction: direction }); + player.c[data.name] = { + ms: + G.skills[data.name].duration_min + + Math.random() * (G.skills[data.name].duration_max - G.skills[data.name].duration_min), + drop: the_zone.drop, + }; + } + resolve = { response: "data", place: data.name, success: false, in_progress: true }; + player.to_resend = "u+cid"; + } + if (data.name == "light") { + consume_mp(player, G.skills[data.name].mp); + xy_emit(player, "light", { name: player.name }); + player.to_resend = "u+cid"; + } + if (data.name == "charge") { + player.s.charging = { ms: G.skills.charge.duration }; + player.to_resend = "u+cid"; + } + if (data.name == "dash") { + //console.log(data); + consume_mp(player, G.skills[data.name].mp); + var x = parseFloat(data.x) || 0; + var y = parseFloat(data.y) || 0; + var point = true; + if (point_distance(player.x, player.y, x, y) > 50) { + point = false; + player.socket.emit("game_response", "dash_failed"); + reject = { response: "data", place: data.name }; + } + if (point) { + var spot = safe_xy_nearby(player.map, x, y); + if (!spot) { + spot = safe_xy_nearby(player.map, player.x + (x - player.x) * 0.8, player.y + (y - player.y) * 0.8); + } + if (!spot) { + spot = safe_xy_nearby(player.map, player.x + (x - player.x) * 0.6, player.y + (y - player.y) * 0.6); + } + if (!spot || point_distance(player.x, player.y, spot.x, spot.y) < 10) { + point = false; + player.socket.emit("game_response", "dash_failed"); + reject = { response: "data", place: data.name }; + } + if (point) { + //console.log([player.x,player.y,spot.x,spot.y]); + player.s.dash = { ms: 1000 }; + player.speed = 500; + player.going_x = spot.x; + player.going_y = spot.y; + start_moving_element(player); + player.to_resend = "u+cid"; + player.socket.emit("eval", { code: "ui_move(" + spot.x + "," + spot.y + ")" }); + } + } + } + if (data.name == "hardshell" || data.name == "power" || data.name == "xpower") { + consume_mp(player, G.skills[data.name].mp); + player.s[G.skills[data.name].condition] = { + ms: G.skills[data.name].duration || G.conditions[G.skills[data.name].condition].duration, + }; + player.to_resend = "u+cid"; + } + if (data.name == "mshield") { + consume_mp(player, G.skills[data.name].mp); + if (player.s[G.skills[data.name].condition]) { + delete player.s[G.skills[data.name].condition]; + } else { + player.s[G.skills[data.name].condition] = { ms: 999999999 }; + } + player.to_resend = "u+cid"; + } + if ( + data.name == "mcourage" || + data.name == "mfrenzy" || + data.name == "massproduction" || + data.name == "massproductionpp" + ) { + consume_mp(player, G.skills[data.name].mp); + player.s[G.skills[data.name].condition] = { ms: G.conditions[G.skills[data.name].condition].duration }; + xy_emit(player, "ui", { type: data.name, name: player.name }); + player.to_resend = "u+cid"; + } + if (data.name == "throw") { + if (distance(player, target, true) > G.skills[data.name].range + player.level) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + if (target.s.invincible || target.immune) { + return socket.emit("game_response", { response: "target_invincible", place: data.name, failed: true }); + } + if (!player.items[data.num]) { + return socket.emit("game_response", { response: "skill_no_item", place: data.name, failed: true }); + } + var item = player.items[data.num]; + var r = "u+cid"; + var damage = 0; + if (item.name == "placeholder") { + return socket.emit("game_response", { response: "item_placeholder", place: data.name, failed: true }); + } + if (item.l) { + return socket.emit("game_response", { response: "item_locked", place: data.name, failed: true }); + } + if (item.b) { + return socket.emit("game_response", { response: "item_blocked", place: data.name, failed: true }); + } + var prop = calculate_item_properties(item); + var negative = false; + if (in_arr(item.name, G.skills.throw.negative)) { + negative = true; + } + G.skills.throw.nprop.forEach(function (p) { + if (prop[p]) { + negative = true; + } + }); + if (negative && target.is_player && !is_in_pvp(player)) { + return socket.emit("game_response", { response: "not_in_pvp", place: data.name, failed: true }); + } + consume_one(player, data.num); + if (item.name == "essenceoffire") { + add_condition(target, "eburn", { from: player }); + } else if (item.name == "essenceoflife") { + add_condition(target, "eheal", { from: player }); + } else if (prop.attack) { + damage = round(Math.random() * prop.attack * 15); + } else if (prop.armor) { + damage = round(Math.random() * prop.armor * 24); + } + if (damage) { + if (target["1hp"]) { + damage = 1; + } + target.hp = max(1, target.hp - damage); + disappearing_text({}, target, "-" + damage, { color: "red", xy: 1 }); + } + consume_mp(player, G.skills[data.name].mp, target); + xy_emit(player, "ui", { type: "throw", from: player.name, to: target.id, item: item.name }); + player.to_resend = "u+cid+reopen"; + if (target.is_player) { + resend(target, r); + } else { + target.u = true; + target.cid++; + } + } + if (data.name == "phaseout") { + if (!player.items[data.num] || player.items[data.num].name != "shadowstone") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + consume_one(player, data.num); + consume_mp(player, G.skills[data.name].mp); + player.s.phasedout = { ms: G.conditions.phasedout.duration }; + player.to_resend = "u+cid+reopen"; + } + if (data.name == "pcoat") { + if (!player.items[data.num] || player.items[data.num].name != "poison") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + consume_one(player, data.num); + consume_mp(player, G.skills[data.name].mp); + player.s.poisonous = { ms: G.skills.pcoat.cooldown }; + player.to_resend = "u+cid+reopen"; + } + if (data.name == "curse") { + //#TODO: last_curse variable + check for multiple curses + var attack = commence_attack(player, target, "curse"); + if (!attack.failed) { + resolve = attack; + add_pdps(player, null, player.attack / 2); + } else { + reject = attack; + cool = false; + } + } + if (data.name == "snowball") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + var found = false; + for (var i = player.isize - 1; i >= 0; i--) { + if (player.items[i] && player.items[i].name == "snowball") { + consume_one(player, i); + found = true; + break; + } + } + if (!found) { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + var attack = commence_attack(player, target, "snowball"); + if (!attack.failed) { + resolve = attack; + } else { + reject = attack; + cool = false; + } + } + if (data.name == "entangle" || data.name == "tangle") { + if (data.name == "entangle") { + if (!player.items[data.num] || player.items[data.num].name != "essenceofnature") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + } + if (target.s.invincible) { + return socket.emit("game_response", { response: "target_invincible", place: data.name, failed: true }); + } + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + add_condition(target, "tangled", { ms: G.skills["entangle"].duration }); + target.abs = true; + target.moving = false; + xy_emit(player, "ui", { type: "entangle", from: player.name, to: target.id }); + consume_mp(player, G.skills[data.name].mp, target); + add_pdps(player, target, 4000); + if (data.name == "entangle") { + consume_one(player, data.num); + } + player.to_resend = "u+cid+reopen"; + if (target.is_monster) { + target.u = true; + target.cid++; + ccms(target); + } else { + resend(target, "u+cid"); + } + } + if (data.name == "4fingers") { + if (target.s.invincible) { + return socket.emit("game_response", { response: "target_invincible", place: data.name, failed: true }); + } + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + if (!is_in_pvp(player) && !is_same(player, target, 1)) { + return socket.emit("game_response", { response: "non_friendly_target", place: data.name, failed: true }); + } + add_condition(target, "fingered", { ms: G.skills["4fingers"].duration }); + add_condition(target, "stunned", { duration: G.skills["4fingers"].duration - 2000 }); + xy_emit(player, "ui", { type: "4fingers", from: player.name, to: target.name }); + consume_mp(player, G.skills[data.name].mp, target); + add_pdps(player, target, 1000); + resend(target, "u+cid"); + player.to_resend = "u+cid"; + } + if ( + [ + "quickpunch", + "quickstab", + "smash", + "mentalburst", + "purify", + "taunt", + "supershot", + "zapperzap", + "burst", + "piercingshot", + ].includes(data.name) + ) { + var attack = commence_attack(player, target, data.name); + if (!attack.failed) { + resolve = attack; + } else { + reject = attack; + cool = false; + } + } + if (data.name == "poisonarrow") { + if (!player.items[data.num] || player.items[data.num].name != "poison") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + + var attack = commence_attack(player, target, data.name); + if (!attack.failed) { + resolve = attack; + } else { + reject = attack; + cool = false; + } + + consume_one(player, data.num); + player.to_resend = "reopen"; + } + if (data.name == "revive") { + if (!player.items[data.num] || player.items[data.num].name != "essenceoflife") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + if (!target.rip) { + return socket.emit("game_response", { response: "target_alive", place: data.name, failed: true }); + } + + consume_one(player, data.num); + player.to_resend = "reopen"; + if (target.party == player.party) { + add_pdps(player, target, target.pdps / 5); + } + + if (target.hp != target.max_hp) { + reject = { response: "data", place: data.name, reason: "hp" }; + player.socket.emit("game_response", { response: "revive_failed", id: data.id }); + } else { + target.c.revival = { ms: 8000, f: player.name }; + } + resend(target, "u+cid"); + } + if (data.name == "cburst") { + var hit = {}; + var times = 0; + var attack = null; + var c_resolve = null; + consume_mp(player, G.skills[data.name].mp); + player.first_burst = true; + player.halt = true; + if (is_array(data.targets)) { + data.targets.forEach(function (t) { + // console.log(id); + var id = t[0]; + var mp = max(0, parseInt(t[1]) || 0); + if (player.mp < 20 || times > 16 || !mp) { + return; + } + times += 1; + var target = instances[player.in].monsters[id]; + if (!target) { + target = instances[player.in].players[id]; + } + if (!target || is_invinc(target) || target.name == player.name) { + return; + } + if (hit[id]) { + return; + } + hit[id] = true; + player.next_mp = mp; + attack = commence_attack(player, target, "cburst"); + if (!attack || !attack.projectile) { + return; + } + if (!c_resolve) { + c_resolve = attack; + attack.pids = [attack.pid]; + attack.targets = [attack.target]; + } else { + c_resolve.pids.push(attack.pid); + c_resolve.targets.push(attack.target); + } + }); + } + player.halt = false; + player.to_resend = "u+cid"; + if (!c_resolve) { + if (attack) { + reject = attack; + } + disappearing_text(player.socket, player, "NO HITS"); + } else { + resolve = c_resolve; + } + } + if (data.name == "partyheal") { + var targets = [player]; + var attack = null; + var hits = 0; + if (parties[player.party]) { + targets = []; + parties[player.party].forEach(function (name) { + var current = players[name_to_id[name]]; + targets.push(current); + }); + } + targets.forEach(function (target) { + attack = commence_attack(player, target, "partyheal"); + if (!attack.failed) { + hits = true; + } + }); + if (!hits) { + reject = attack; + } + } + if (data.name == "selfheal") { + var attack = commence_attack(player, player, data.name); + if (!attack.failed) { + resolve = attack; + } else { + reject = attack; + cool = false; + } + } + if (data.name == "darkblessing" || data.name == "warcry") { + consume_mp(player, G.skills[data.name].mp); + for (var id in instances[player.in].players) { + var target = instances[player.in].players[id]; + if ( + !target.npc && + distance(player, target, true) < G.skills[data.name].range && + (!(G.maps[player.map].pvp || is_pvp) || is_same(player, target, 1)) + ) { + target.s[G.skills[data.name].condition] = { ms: G.skills[data.name].duration, f: player.name }; + resend(target, "u+cid"); + add_pdps(player, target, 500); + } + } + xy_emit(player, "ui", { type: data.name }); + } + if (data.name == "3shot" || data.name == "5shot") { + player.halt = true; + var times = 0; + var hit = {}; + var reftarget = null; + var targets = 3; + var attack = null; + var c_resolve = null; + if (data.name == "5shot") { + targets = 5; + } + //console.log(data.ids); + if (is_array(data.ids)) { + data.ids.forEach(function (id) { + if (times >= targets) { + return; + } + times += 1; + var target = instances[player.in].monsters[id]; + if (!target) { + target = instances[player.in].players[id]; + } + if (!target || is_invinc(target) || target.name == player.name) { + attack = { failed: true, place: data.name, reason: "no_target" }; + return; + } + if (hit[id]) { + return; + } + hit[id] = true; + attack = commence_attack(player, target, data.name); + if (!attack || !attack.projectile) { + return; + } + if (!c_resolve) { + reftarget = target; + c_resolve = attack; + attack.pids = [attack.pid]; + attack.targets = [attack.target]; + } else { + c_resolve.pids.push(attack.pid); + c_resolve.targets.push(attack.target); + } + // if(times==1 && attack==null) times=40; + }); + } + consume_mp(player, G.skills[data.name].mp, reftarget); + player.halt = false; + player.to_resend = "u+cid"; + if (!c_resolve) { + if (attack) { + reject = attack; + } + disappearing_text(player.socket, player, "NO HITS"); + } else { + resolve = c_resolve; + } + } + if (data.name == "track") { + var list = []; + for (var id in instances[player.in].players) { + if (id == player.name) { + continue; + } + var target = instances[player.in].players[id]; + var current = { sound: "wmp" }; + if (target.npc) { + continue; + } + if (is_invis(target)) { + current.invis = true; + } + if (target.type == "rogue" || target.type == "ranger") { + current.sound = "rr"; + } else if (target.type == "priest" || target.type == "mage") { + current.sound = "pm"; + } + current.dist = distance(player, target); + if (current.dist < G.skills.track.range) { + list.push(current); + } + } + list.sort(function (a, b) { + return a.dist - b.dist; + }); + consume_mp(player, G.skills[data.name].mp); + xy_emit(player, "ui", { type: "track", name: player.name }); + socket.emit("track", list); + player.to_resend = "u+cid"; + } + if (data.name == "agitate") { + var ids = []; + for (var id in instances[player.in].monsters) { + var target = instances[player.in].monsters[id]; + if (target.target == player.name) { + ids.push(id); + continue; + } // why did the missing continue cause a disengage I have no idea [25/03/23] + if (target.target && !(get_player(target.target) && is_same(player, get_player(target.target), 1))) { + continue; + } + var dist = distance(player, target); + if (dist < G.skills.agitate.range) { + if (target.target) { + stop_pursuit(target, { redirect: true, cause: "agitate redirect" }); + } + target.cid += 1; + target.u = true; + target.last.attacked = new Date(); + target_player(target, player); + ids.push(id); + } + } + consume_mp(player, G.skills[data.name].mp, target); + xy_emit(player, "ui", { type: "agitate", name: player.name, ids: ids }); + player.to_resend = "u+cid"; + add_pdps(player, null, 1000); + } + if (data.name == "absorb") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + var ids = []; + if (is_same(player, target, 1)) { + consume_mp(player, G.skills[data.name].mp); + } else { + if (player.mp < G.skills[data.name].mp * 6 || player.level < 75) { + return socket.emit("game_response", { response: "non_friendly_target", place: data.name, failed: true }); + } + consume_mp(player, G.skills[data.name].mp * 6); + if (Math.random() < 0.95) { + consume_skill(player, data.name); + resend(player, "u+cid"); + return socket.emit("game_response", { response: "non_friendly_target", place: data.name, failed: true }); + } + } + for (var id in instances[player.in].monsters) { + var monster = instances[player.in].monsters[id]; + if (monster.target == target.name) { + stop_pursuit(monster, { redirect: true, cause: "absorb redirect" }); + monster.cid += 1; + monster.u = true; + target_player(monster, player); + ids.push(id); + } + } + xy_emit(player, "ui", { type: "absorb", name: player.name, from: data.id, ids: ids }); + player.to_resend = "u+cid"; + add_pdps(player, target, 1000); + } + if (data.name == "stomp") { + var ids = []; + var reftarget = null; + for (var id in instances[player.in].monsters) { + var target = instances[player.in].monsters[id]; + var dist = distance(player, target); + if (dist < G.skills.stomp.range) { + if (target.immune) { + player.hitchhikers.push(["game_response", { response: "skill_immune", skill: data.name }]); + player.to_resend = "nc"; + disappearing_text(target.socket, target, "IMMUNE!", { xy: 1, color: "evade", nv: 1, from: player.id }); + } else if (add_condition(target, "stunned", { duration: G.skills.stomp.duration })) { + ids.push(id); + add_pdps(player, target, 500); + } + } + } + if (is_in_pvp(player, 1)) { + for (var id in instances[player.in].players) { + var target = instances[player.in].players[id]; + var dist = distance(player, target); + if (dist < G.skills.stomp.range && !target.npc && !is_same(player, target, 1) && !target.s.invincible) { + if (add_condition(target, "stunned", { duration: G.skills.stomp.duration })) { + reftarget = target; + resend(target, "u+cid"); + ids.push(id); + add_pdps(player, target, 5000); + } + } + } + } + consume_mp(player, G.skills[data.name].mp, reftarget); + xy_emit(player, "ui", { type: "stomp", name: player.name, ids: ids }); + player.to_resend = "u+cid"; + resolve = { response: "data", place: data.name, success: true, ids: ids }; + } + if (data.name == "scare") { + var ids = []; + consume_mp(player, G.skills[data.name].mp); + for (var id in instances[player.in].monsters) { + var target = instances[player.in].monsters[id]; + if (target.target == player.name) { + if (target.immune) { + player.hitchhikers.push(["game_response", { response: "skill_immune", skill: "scare" }]); + player.to_resend = "nc"; + disappearing_text(target.socket, target, "IMMUNE!", { xy: 1, color: "evade", nv: 1, from: player.id }); + } else { + stop_pursuit(target, { force: true, cause: "scare" }); + target.abs = true; + target.moving = false; + ids.push(id); + } + } + } + xy_emit(player, "ui", { type: "scare", name: player.name, ids: ids }); + player.to_resend = "u+cid"; + } + if (data.name == "huntersmark") { + if (target.is_player && !is_in_pvp(player) && !is_same(player, target, 1)) { + return socket.emit("game_response", { response: "skill_cant_pve", place: data.name, failed: true }); + } + consume_mp(player, G.skills[data.name].mp, target); + add_condition(target, "marked"); + if (target.is_player) { + resend(target, "u+cid"); + } else { + target.cid++; + target.u = true; + } + xy_emit(player, "ui", { type: "huntersmark", name: player.name, id: target.id }); + player.to_resend = "u+cid"; + } + if (data.name == "charm") { + consume_mp(player, G.skills[data.name].mp); + if (Math.random() > 0.01) { + socket.emit("game_response", "charm_failed"); + xy_emit(player, "ui", { type: "charm", name: player.name, id: target.id, fail: true }); + } else { + target.cid++; + target.u = true; + target.s.charmed = { ms: G.conditions.charmed.duration }; + xy_emit(player, "ui", { type: "charm", name: player.name, id: target.id }); + } + player.to_resend = "u+cid"; + } + if (data.name == "cleave" || data.name == "shadowstrike") { + player.to_resend = "u+cid"; + if (data.name == "shadowstrike") { + if (!player.items[data.num] || player.items[data.num].name != "shadowstone") { + return socket.emit("game_response", { response: "skill_cant_item", place: data.name, failed: true }); + } + consume_one(player, data.num); + player.to_resend = "u+cid+reopen"; + } + player.halt = true; + var ids = []; + var reftarget = null; + var c_resolve = null; + for (var id in instances[player.in].monsters) { + var target = instances[player.in].monsters[id]; + var dist = distance(player, target); + if (dist < G.skills[data.name].range) { + attack = commence_attack(player, target, data.name); + if (!attack || !attack.projectile) { + continue; + } + ids.push(id); + if (!c_resolve) { + c_resolve = attack; + attack.pids = [attack.pid]; + attack.targets = [attack.target]; + } else { + c_resolve.pids.push(attack.pid); + c_resolve.targets.push(attack.target); + } + } + } + if (is_in_pvp(player, 1)) { + for (var id in instances[player.in].players) { + var target = instances[player.in].players[id]; + var dist = distance(player, target); + if (dist < G.skills[data.name].range && !target.npc && !is_same(player, target, 1)) { + attack = commence_attack(player, target, data.name); + if (!attack || !attack.projectile) { + continue; + } + ids.push(id); + if (!c_resolve) { + reftarget = target; + c_resolve = attack; + attack.pids = [attack.pid]; + attack.targets = [attack.target]; + } else { + c_resolve.pids.push(attack.pid); + c_resolve.targets.push(attack.target); + } + } + } + } + consume_mp(player, G.skills[data.name].mp, reftarget); + xy_emit(player, "ui", { type: data.name, name: player.name, ids: ids }); + player.halt = false; + if (c_resolve) { + resolve = c_resolve; + } + } + if (data.name == "magiport") { + var pported = false; + consume_mp(player, G.skills[data.name].mp); + if (!is_pvp && mode.pve_safe_magiports) { + if (!magiportations[player.name]) { + magiportations[player.name] = {}; + } + magiportations[player.name][target.name] = true; + target.socket.emit("magiport", { name: player.name }); + player.socket.emit("game_response", { response: "magiport_sent", id: data.id }); + xy_emit(player, "ui", { type: "magiport", name: player.name }); + } else { + if (is_same(player, target, 1) || !target.slots.helmet) { + ported = magiport_someone(target, player); + } + if (!ported) { + player.socket.emit("game_response", { response: "magiport_failed", id: data.id }); + } + } + player.to_resend = "u+cid"; + } + if (data.name == "blink") { + var x = parseFloat(data.x) || 0; + var y = parseFloat(data.y) || 0; + var spot = safe_xy_nearby(player.map, x, y); + if (!spot) { + return player.socket.emit("game_response", { response: "blink_failed", place: data.name, failed: true }); + } + player.s.blink = { ms: 200 }; + player.s.blink.in = player.in; + player.s.blink.map = player.map; + player.s.blink.x = spot.x; + player.s.blink.y = spot.y; + player.s.blink.d = 0; + if (in_arr(data.direction, [1, 2, 3])) { + player.s.blink.d = data.direction; + } + if (player.role != "gm") { + consume_mp(player, G.skills[data.name].mp); + } + // xy_emit(player,"ui",{type:"blinking",name:player.name}); + resend(player, "u+cid"); + // #TODO: Appear animation for non-self's [21/05/18] + } + if (data.name == "warp") { + var x = parseFloat(data.x) || 0; + var y = parseFloat(data.y) || 0; + var ins = data.in || "main"; + var instance = instances[ins]; + if (!instance) { + return player.socket.emit("game_response", { response: "blink_failed", place: data.name, failed: true }); + } + if (instance.mount != instances[player.in].mount) { + return player.socket.emit("game_response", { response: "cant_in_bank", place: data.name, failed: true }); + } + var spot = safe_xy_nearby(instance.name, x, y); + if (!spot) { + return player.socket.emit("game_response", { response: "blink_failed", place: data.name, failed: true }); + } + player.s.blink = { ms: 200 }; + player.s.blink.in = ins; + player.s.blink.map = instance.name; + player.s.blink.x = spot.x; + player.s.blink.y = spot.y; + player.s.blink.d = 0; + if (in_arr(data.direction, [1, 2, 3])) { + player.s.blink.d = data.direction; + } + if (player.role != "gm") { + consume_mp(player, G.skills[data.name].mp); + } + // xy_emit(player,"ui",{type:"blinking",name:player.name}); + player.to_resend = "u+cid"; + // #TODO: Appear animation for non-self's [21/05/18] + } + if (data.name == "mluck") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + consume_mp(player, G.skills[data.name].mp); + if ( + !target.s[G.skills[data.name].condition] || + !target.s[G.skills[data.name].condition].strong || + target.s[G.skills[data.name].condition].f == player.name + ) { + target.s[G.skills[data.name].condition] = { ms: G.conditions.mluck.duration, f: player.name }; + } + if (target.owner == player.owner) { + target.s[G.skills[data.name].condition].strong = true; + } + xy_emit(player, "ui", { type: "mluck", from: player.name, to: target.name }); + resend(target, "u+cid"); + resend(player, "u+cid"); + } + if (data.name == "rspeed") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + consume_mp(player, G.skills[data.name].mp); + target.s[G.skills[data.name].condition] = { ms: G.conditions.rspeed.duration, f: player.name }; + xy_emit(player, "ui", { type: "rspeed", from: player.name, to: target.name }); + resend(target, "u+cid"); + resend(player, "u+cid"); + if (player.party == target.party && player != target) { + add_pdps(player, target, 2000); + } + } + if (data.name == "reflection") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + consume_mp(player, G.skills[data.name].mp); + target.s[G.skills[data.name].condition] = { ms: G.conditions.reflection.duration, f: player.name }; + xy_emit(player, "ui", { type: "reflection", from: player.name, to: target.name }); + resend(target, "u+cid"); + resend(player, "u+cid"); + if (player.party == target.party && player != target) { + add_pdps(player, target, 4000); + } + } + if (data.name == "energize") { + if (distance(player, target, true) > G.skills[data.name].range) { + return socket.emit("game_response", { response: "too_far", place: data.name, failed: true }); + } + if (!player.mp) { + return socket.emit("game_response", { response: "no_mp", place: data.name, failed: true }); + } + var mp = parseInt(data.mp) || 10000; + mp = max(1, min(player.mp, mp)); + if (mp > target.max_mp - target.mp) { + mp = target.max_mp - target.mp; + } + target.mp += mp; + player.mp -= mp; + if (player.party == target.party && player != target) { + add_pdps(player, target, mp * 2); + } + disappearing_text(player.socket, player, "-" + mp, { color: "mana", xy: 1 }); + disappearing_text(target.socket, target, "+" + mp, { color: "mana", xy: 1 }); + target.s[G.skills[data.name].condition] = { ms: G.conditions[G.skills[data.name].condition].duration }; + xy_emit(player, "ui", { type: "energize", from: player.name, to: target.name }); + resend(target, "u+cid"); + resend(player, "u+cid"); + } + if (data.name == "alchemy") { + var gold = 0; + var rate = 0.8; + if (player.level >= 100) { + rate = 1.12; + } else if (player.level >= 90) { + rate = 1.1; + } else if (player.level >= 80) { + rate = 1.06; + } else if (player.level >= 70) { + rate = 1; + } else if (player.level >= 60) { + rate = 0.92; + } else if (player.level >= 50) { + rate = 0.86; + } + consume_mp(player, G.skills[data.name].mp); + xy_emit(player, "ui", { type: "alchemy", name: player.name }); + for (var i = 0; i < player.isize; i++) { + if (!player.items[i] || player.items[i].l) { + continue; + } + gold = calculate_item_value(player.items[i]); + consume_one(player, i); + break; + } + player.gold += gold * rate; + resend(player, "reopen"); + socket.emit("game_response", { response: "gold_received", gold: gold * rate }); + } + if (cool) { + consume_skill(player, data.name); + } + if (player.to_resend) { + resend(player, player.to_resend); + } + if (reject) { + if (!reject.response) { + reject.response = "data"; + } + socket.emit("game_response", reject); + } else if (resolve) { + socket.emit("game_response", resolve); + } + }); + socket.on("click", function (data) { + // You'll be missed 'click' method, the 'click' method was the first method on this server, it was used as an attack method up until [17/06/18] - at this date, there were 3 simple conditions left which checked for data.button=="right" - the game matured so that all interactions were handled client-side rather than processed server-side + socket.emit("game_log", "'click' method is deprecated."); + }); + socket.on("attack", function (data) { + return socket.fs.skill({ name: "attack", id: data.id }); + }); + socket.on("heal", function (data) { + return socket.fs.skill({ name: "heal", id: data.id }); + }); + socket.on("interaction", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.type == "newyear_tree") { + var x = ""; + if ( + !G.maps[player.map].ref || + !G.maps[player.map].ref[data.type] || + distance(G.maps[player.map].ref[data.type], player, true) > 300 + ) { + return socket.emit("game_response", "distance"); + } + if (!player.s.holidayspirit && player.esize) { + add_item(player, "funtoken"); + x = "+reopen"; + } + add_condition(player, "holidayspirit"); + resend(player, "u+cid" + x); + } else if (["redorb", "blueorb", "greenorb", "yelloworb"].includes(data.type)) { + if ( + !G.maps[player.map].ref || + !G.maps[player.map].ref[data.type] || + distance(G.maps[player.map].ref[data.type], player, true) > 40 + ) { + return socket.emit("game_response", "distance"); + } + ["redorb", "blueorb", "greenorb", "yelloworb"].forEach(function (s) { + delete player.s[s]; + }); + add_condition(player, data.type); + resend(player, "u+cid"); + } else if (data == "the_lever") { + if (player.map != "resort_e") { + return socket.emit("game_response", "distance"); + } + player.s.magiport = { ms: 300 }; + player.s.magiport.x = player.x; + player.s.magiport.y = player.y; + player.s.magiport.f = player.name; + player.s.magiport.in = "resort"; + player.s.magiport.map = "resort"; + resend(player, "u+cid"); + } else if (data.type == "dailytask") { + if (!G.maps.main.ref.dailytask || distance(G.maps.main.ref.dailytask, player, true) > 150) { + return socket.emit("game_response", "distance"); + } + player.p.monsterhunt = { ms: 60 * 60 * 1000, m: "goo" }; + socket.emit("game_response", { response: "monsterhunt", monster: "goo" }); + } else if (data.key && player.konami) { + if (data.key == "B" && player.konami.length < 20) { + player.konami.push(data.key[0]); + } else if (data.key == "A") { + if (player.konami.join("") == "uuddlrlrB") { + player.tskin = "konami"; + resend(player, "u+cid"); + if (!player.p.target_lock || !G.monsters[player.p.target_lock] || hsince(player.p.dt.last_tl) > 15 * 24) { + var monsters = []; + for (var name in G.monsters) { + if (!G.monsters[name].special && !G.monsters[name].stationary && G.monsters[name].c) { + monsters.push(name); + } + } + player.p.target_lock = random_one(monsters); + player.p.dt.last_tl = new Date(); + } + socket.emit("game_response", { response: "target_lock", monster: player.p.target_lock }); + } else { + player.konami = []; + } + } + } + }); + socket.on("mreport", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var a = 0; + var b = 0; + a = parseFloat(data.x) || 0; + b = parseFloat(data.y) || 0; + // console.log(JSON.stringify(data)); + socket.emit("game_log", "" + simple_distance({ x: player.x, y: player.y }, { x: a, y: b })); + }); + socket.on("move", function (data) { + // if(observers[socket.id]) observers[socket.id].x=data.x,observers[socket.id].y=data.y; Old observer code [17/02/17] + var player = players[socket.id]; + var current = -1; + var going = -1; + var actual = true; + var x = parseFloat(data.going_x) || 0; + var y = parseFloat(data.going_y) || 0; + if (data.pet) { + player = player.monster; + actual = false; + } + if (data.key && (!player.konami || player.konami.length < 20)) { + // console.log(data.key); + if (!player.konami) { + player.konami = []; + } + player.konami.push(data.key[0]); + } + if (player && player.m == data.m && can_walk(player) && (x != player.x || y != player.y)) { + // if(is_sdk) server_log("Player moving to: "+data.going_x+","+data.going_y); + + // player.x=data.x; player.y=data.y; [03/08/16] Seems like a really bad idea to update x/y based on what players provide + data.x = parseFloat(data.x) || 0; + data.y = parseFloat(data.y) || 0; + player.going_x = x; + player.going_y = y; + if (smap_data[player.map] != -1 && mode.enforce_smap) { + current = smap_data[player.map][rphash(data.x, data.y)]; + going = smap_data[player.map][rphash(player.going_x, player.going_y)]; + // server_log("current:"+current+" going:"+going+" real:"+smap_data[player.map][phash(player.x,player.y)]); + if (current === undefined || current >= 2 || going === undefined || going >= 2) { + server_log( + "#C cheater: " + + player.name + + " current: " + + current + + "[" + + data.x + + "," + + data.x + + "] going: " + + going + + "[" + + player.going_x + + "," + + player.going_y + + "] at " + + player.map, + 1, + ); + appengine_log("violation", "move_line: " + player.name + " afk: " + player.afk + " code: " + player.code); + player.socket.emit("game_log", "Line violation detected"); + player.socket.emit("game_log", "Make sure you only move with the built-in move function"); + defeat_player(player); + transport_player_to(player, "jail"); + return; + } + } + // player.check_x=player.going_x; player.check_y=player.going_y; player.checked_xy=false; + start_moving_element(player); + if ( + !player.pet && + simple_distance(player, { x: data.x, y: data.y }) > 132 && + current == 0 && + mode.lcorrection + ) { + // server_log("Correction sent"); + socket.emit("correction", { x: player.x, y: player.y }); + } + if (actual) { + var change = false; + for (var id in player.c) { + if (G.conditions[id] && !G.conditions[id].can_move) { + change = true; + delete player.c[id]; + } + } + if (change) { + resend(player, "u+cid"); + } + } + //if(smap_data[player.map][rphash(player.x,player.y)]===0) push_xyh(player,player.x,player.y); + //else push_xyh(player,data.x,data.y); + } + }); + socket.on("open_chest", function (data) { + var chest = chests[data.id]; + var player = players[socket.id]; + var reopen = false; + if (!player) { + return; + } + var r = { id: data.id, goldm: player.goldm, opener: player.name, items: [] }; + if (chest && simple_distance(chest, player) > 400) { + r.goldm = 1; + r.dry = true; + } + if (chest && msince(chest.date) > 8) { + r.goldm = 1; + r.stale = true; + } + if (player.map == "woffice" || G.maps[player.map].mount || is_invis(player)) { + return fail_response("loot_failed"); + } + try { + if (chest && !chest.pvp && chest.gold > 100000000 && !player.stealth) { + broadcast("server_message", { + message: player.name + " looted " + to_pretty_num(server_tax(round(chest.gold * r.goldm), true)) + " gold", + color: "gold", + type: "server_gold", + name: player.name, + }); + } + if (chest && player.owner && chest.owners && !in_arr(player.owner, chest.owners) && !W.chest[player.owner]) { + W.chest[player.owner] = new Date(); + server_log("SEVERE - Cross Loot from " + player.name + " not from " + chest.owners.toString()); + } + if (chest && !player.party) { + var all_items = chest.items.slice(0); + all_items.concat(chest.pvp_items); + if (!can_add_items(player, all_items)) { + return fail_response("loot_no_space"); + } + delete chests[data.id]; // The add_shells routine was at the top, so when inventory was full, attempting to open the chest gave shells infinitely, repeated lesson, always remove before adding, or exceptions [11/07/18] + if (chest && chest.cash) { + add_shells(player, chest.cash, "chest", true, "override"); + } + chest.items.forEach(function (item) { + item.src = "pvp"; + add_item(player, item, { found: 1, m: 1, v: B.v }); + reopen = true; + var ritem = cache_item(item); + ritem.looter = player.name; + r.items.push(ritem); + socket.emit("game_log", { message: "Found " + item_to_phrase(item), color: "#4BAEAA" }); + if (player.t) { + player.t.dgold += round(G.items[item.name].g * 0.6); + } + }); + (chest.pvp_items || []).forEach(function (item) { + item.v = new Date(); + if (can_add_item(player, item)) { + add_item(player, item, { found: 1, v: B.v }); + reopen = true; + var ritem = cache_item(item); + ritem.looter = player.name; + ritem.pvp_loot = true; + r.items.push(ritem); + socket.emit("game_log", { message: "Looted " + item_to_phrase(item), color: "#4BAEAA" }); + } else { + lostandfound_logic(item); + var ritem = cache_item(item); + ritem.looter = null; + ritem.lostandfound = true; + r.items.push(ritem); + socket.emit("game_log", { message: "Lost " + item_to_phrase(item), color: "#AB4E4F" }); + } + }); + r.gold = round(chest.gold * r.goldm) + round(chest.egold || 0); + r.gold = server_tax(r.gold); + player.gold += r.gold; + if (player.t) { + player.t.cgold += r.gold; + } + if (r.gold) { + socket.emit("game_log", { message: to_pretty_num(r.gold) + " gold", color: "gold" }); + } + socket.emit("disappearing_text", { + message: "+" + r.gold, + x: chest.x, + y: chest.y - 10, + args: { color: "+gold", size: "large" }, + }); + if (!r.items.length) { + delete r.items; + } + resend(player, (reopen && "reopen+nc+inv") || ""); + socket.emit("chest_opened", r); + } else if (chest) { + // var gold=round(chest.gold/parties[player.party].length); + r.party = true; + var chest = chests[data.id]; + var reopen = {}; + delete chests[data.id]; + if (chest && chest.cash) { + add_shells(player, chest.cash, "chest", true, "override"); + } + chest.items.forEach(function (item) { + // console.log(item); + var pool = 0; + var can = {}; + parties[player.party].forEach(function (name) { + var current = players[name_to_id[name]]; + if (current && can_add_item(current, item)) { + pool += current.share; + can[name] = true; + } + }); + var pool_winner = Math.random() * pool; + var pool_current = 0; + var awarded = false; + parties[player.party].forEach(function (name) { + if (awarded) { + return; + } + var current = players[name_to_id[name]]; + if (current && can[name]) { + if (pool_winner <= pool_current + current.share) { + awarded = true; + add_item(current, item, { found: 1, m: 1, v: B.v }); + reopen[current.id] = true; + var ritem = cache_item(item); + ritem.looter = current.name; + r.items.push(ritem); + party_emit(player.party, "game_log", { + message: current.name + " found " + item_to_phrase(item), + color: "#4BAEAA", + }); + if (current.t) { + current.t.dgold += round(G.items[item.name].g * 0.6); + } + } else { + pool_current += current.share; + } + } + }); + if (!awarded) { + lostandfound_logic(item); + var ritem = cache_item(item); + ritem.looter = null; + ritem.lostandfound = true; + r.items.push(ritem); + party_emit(player.party, "game_log", { message: "Lost " + item_to_phrase(item), color: "#AB4E4F" }); + } + }); + (chest.pvp_items || []).forEach(function (item) { + item.v = new Date(); + var pool = 0; + var can = {}; + parties[player.party].forEach(function (name) { + var current = players[name_to_id[name]]; + if (current && current.share && can_add_item(current, item)) { + pool += current.share; + can[name] = true; + } + }); + var pool_winner = Math.random() * pool; + var pool_current = 0; + var awarded = false; + parties[player.party].forEach(function (name) { + if (awarded) { + return; + } + var current = players[name_to_id[name]]; + if (current && can[name]) { + if (pool_winner <= pool_current + current.share) { + awarded = true; + add_item(current, item, { found: 1, v: B.v }); + reopen[current.id] = true; + var ritem = cache_item(item); + ritem.looter = current.name; + ritem.pvp_loot = true; + r.items.push(ritem); + party_emit(player.party, "game_log", { + message: current.name + " looted " + item_to_phrase(item), + color: "#4BAEAA", + }); + } else { + pool_current += current.share; + } + } + }); + if (!awarded) { + lostandfound_logic(item); + var ritem = cache_item(item); + ritem.looter = null; + ritem.pvp_loot = true; + ritem.lostandfound = true; + r.items.push(ritem); + party_emit(player.party, "game_log", { message: "Lost " + item_to_phrase(item), color: "#AB4E4F" }); + } + }); + parties[player.party].forEach(function (name) { + var current = players[name_to_id[name]]; + var cgold = + round(chest.gold * (current.share || 0) * r.goldm) + round((chest.egold || 0) * (current.share || 0)); + r.gold = cgold = server_tax(cgold); + current.gold += cgold; + if (current.t) { + current.t.cgold += cgold; + } + if (cgold) { + current.socket.emit("game_log", { message: to_pretty_num(cgold) + " gold", color: "gold" }); + } + if (current.in == player.in) { + current.socket.emit("disappearing_text", { + message: "+" + cgold, + x: chest.x, + y: chest.y - 10, + args: { color: "gold", size: "large" }, + }); + } + resend(current, (reopen[current.id] && "reopen+nc+inv") || ""); + current.socket.emit("chest_opened", r); + }); + } else { + socket.emit("chest_opened", { id: data.id, gone: true }); + } + } catch (e) { + delete chests[data.id]; // If this didn't exist, any exception would end up being a source for infinite gold and items + log_trace("#X chest_error", e); + return fail_response("error"); + } + }); + socket.on("auth", function (data) { + if (gameplay == "test" && data.passphrase != "potato salad") { + return socket.emit("game_log", "Wrong passphrase!"); + } + if (observers[socket.id] && observers[socket.id].auth_engaged) { + return socket.emit("game_log", "Authorization in progress."); + } + if (dc_players[data.character]) { + return socket.emit("game_log", "Authorization in progress."); + } + if (!server.live || !observers[socket.id] || players[socket.id]) { + return; + } + if (Object.keys(players).length >= max_players) { + socket.emit("game_error", "Can't accept more than " + max_players + " players at this time"); + return; + } + socket.observer_secret = randomStr(24); + observers[socket.id].auth_engaged = true; + appengine_call( + "start_character", + { + auth: data.user + "-" + data.auth, + secret: socket.observer_secret, + code_slot: data.code_slot, + character: data.character, + mode: gameplay, + ip: get_ip(socket), + suffix: "/" + data.character, + }, + function (result) { + if (observers[socket.id]) { + observers[socket.id].auth_engaged = false; + } + if (result.failed) { + socket.emit("game_error", "Failed: " + result.reason); + return; + } + // console.log(JSON.stringify(result)); + server_log("start_character: " + JSON.stringify(result.character.name), 1); + var player = { u: true, is_player: true, humanoid: true, secret: socket.observer_secret }; + for (prop in result.character) { + player[prop] = result.character[prop]; + } + if (!instances[player.map] || !instances[player.map].allow || instances[player.map].mount) { + var place = G.maps[player.map].on_exit || G.maps[B.start_map].on_exit || ["main", 0]; + player.map = player.in = place[0]; + player.x = G.maps[player.map].spawns[place[1]][0]; + player.y = G.maps[player.map].spawns[place[1]][1]; + } else { + player.in = player.map; + } + player.owner = data.user; + player.auth = data.user + "-" + data.auth; + player.last_sync = new Date(); + player.socket = socket; + player.max_stats = result.stats; + + if (data.bot == variables.bot_key) { + player.bot = true; + player.afk = "bot"; + } + if (data.no_html) { + player.afk = "code"; + try { + player.controller = (name_to_id[data.no_html] && data.no_html) || ""; + } catch (e) { + player.controller = ""; + } + } + if (!player.afk) { + player.afk = true; + } + if (gameplay == "test") { + player.name += parseInt(Math.random() * 10000); + } + player.real_id = player.id; + player.id = player.name; + + player.total_ips = 1; + player.width = 26; + player.height = 36; + player.damage_type = G.classes[player.type].damage_type; + player.xrange = 25; + player.red_zone = 0; + player.targets = player.targets_p = player.targets_m = player.targets_u = 0; + player.cid = 1; + player.hits = 0; + player.kills = 0; + player.m = 0; // map number + /* party variables*/ + player.pdps = 0; + player.party_length = 1; + player.party_luck = 0; + player.party_xp = 0; + player.party_gold = 0; + player.share = 0.1; + player.cx = player.cx || {}; + if (!player.s) { + player.s = {}; + } + player.t = { mdamage: 0, cgold: 0, dgold: 0, xp: 0, start: new Date() }; + player.hitchhikers = []; // socket events to be registered after a resend + player.last = { attack: future_ms(-1200), attacked: really_old }; + player.bets = {}; + player.base = dbase; + player.age = parseInt(ceil(hsince(new Date(player.created)) / 24.0)); + // player.vision=[round((data.width/2)/data.scale)+B.ext_vision,round((data.height/2)/data.scale)+B.ext_vision]; + // player.vision[0]=min(1000,player.vision[0]); + // player.vision[1]=min(700,player.vision[1]); + player.vision = B.vision; + + if (!player.verified) { + player.s.notverified = { ms: 30 * 60 * 1000 }; + } else if (player.s.notverified) { + player.s.notverified = { ms: 100 }; + } + + if (player.guild) { + console.log(player.guild); + player.guild = player.guild.short; + } + + if (gameplay == "hardcore") { + reset_player(player); + } // || gameplay=="test" + + init_player(player); + if (!observers[socket.id]) { + // observer hang up before "auth" + server_log("Abrupt stop for " + result.character.name, 1); + if (gameplay != "hardcore" && gameplay != "test") { + dc_players[player.real_id] = player; + } + sync_loop(); + return; + } + try { + delete_observer(socket); + } catch (e) {} + + players[socket.id] = player; + resume_instance(instances[player.in]); + instances[player.in].players[player.id] = player; + pmap_add(player); + + name_to_id[player.name] = socket.id; + id_to_id[player.id] = socket.id; + + cache_player_items(player); + invincible_logic(player); + serverhop_logic(player); + calculate_player_stats(player); + + if (data.epl == "mas" && data.receipt) { + player.platform = "mas"; + verify_mas_receipt(player, data.receipt); + } else if (data.epl == "steam" && data.ticket) { + player.platform = "steam"; + verify_steam_ticket(player, data.ticket); + } else { + player.platform = "web"; + if (result.character.pid) { + player.auth_id = result.character.pid; + } // part of the new restriction system [02/05/19] + } + + if (mode.drm_check) { + if (result.character.drm && !player.auth_id) { + player.s.authfail = { ms: 900000 * 1000 }; + } else if (player.s.authfail) { + player.s.authfail = { ms: 100 }; + } + } + + if (!is_player_allowed(player)) { + socket.emit("disconnect_reason", "limits"); + socket.disconnect(); + } else { + var cdata = player_to_client(player); + player.ipass = cdata.ipass = randomStr(12); + player.last_ipass = new Date(); + player.last.attack = future_ms(-10000); + player.last.transport = future_ms(-10000); + cdata.home = player.p.home; + cdata.friends = player.friends; + cdata.acx = player.p.acx; + cdata.xcx = player.p.xcx; + cdata.emx = player.p.emx; + cdata.info = instances[player.in].info; + cdata.base_gold = D.base_gold; + broadcast_e(true); + cdata.s_info = E; + if (result.code) { + cdata.code = result.code; + cdata.code_slot = result.code_slot; + cdata.code_version = result.code_version; + } + cdata.entities = send_all_xy(player, { raw: true }); + socket.emit("start", cdata); + total_players++; + } + }, + function (err) { + server_log("start_character_failed: " + data.character + " reason: " + err, 1); + if (observers[socket.id]) { + observers[socket.id].auth_engaged = false; + } + }, + ); + }); + socket.on("use", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.item == "hp" || data.item == "mp") { + if (player.last.potion && mssince(player.last.potion) < 0) { + return fail_response("not_ready"); + } + player.last.potion = future_ms(4000); + if (data.item == "hp") { + player.hp += 50; + disappearing_text(socket, player, "+50", { color: "green", xy: 1, s: "hp", nohp: 1 }); + } + if (data.item == "mp") { + player.mp += 100; + disappearing_text(socket, player, "+100", { color: "#006AA9", xy: 1, s: "mp", nomp: 1 }); + } + player.hp = min(player.hp, player.max_hp); + player.mp = min(player.mp, player.max_mp); + // calculate_player_stats(player); [22/11/16] + player.cid++; + player.u = true; + socket.emit("player", player_to_client(player)); + socket.emit("eval", { code: "pot_timeout(4000)" }); + } + success_response({}); + }); + socket.on("friend", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + server_log("friend: " + JSON.stringify(data)); + if (data.event == "request") { + var friend = players[name_to_id[data.name || ""]]; + if (!friend) { + return fail_response("friend_rleft"); + } + if (in_arr(friend.owner, player.friends) || friend.owner == player.owner) { + return success_response("friend_already"); + } + requests[player.name + "-" + friend.name] = { a: player.owner, b: friend.owner }; + friend.socket.emit("friend", { event: "request", name: player.name }); + return success_response("friend_rsent"); + } + if (data.event == "accept") { + if (!requests[data.name + "-" + player.name]) { + return fail_response("friend_expired"); + } + appengine_call( + "set_friends", + { user1: requests[data.name + "-" + player.name].a, user2: requests[data.name + "-" + player.name].b }, + function (result) { + var player = players[socket.id]; + if (result.failed) { + if (player) { + socket.emit("game_response", { response: "friend_failed", reason: result.reason }); + } + return; + } + //var friend=players[name_to_id[data.name||""]]; + //if(player) + //if(friend) friend.emit("friend",{event:"accepted",name:player.name}); + }, + function () { + var player = players[socket.id]; + if (player) { + socket.emit("game_response", { response: "friend_failed", reason: "coms failure" }); + } + }, + ); + requests[data.name + "-" + player.name] = false; + } + if (data.event == "unfriend") { + appengine_call( + "not_friends", + { user1: player.owner, user2: data.name }, + function (result) { + var player = players[socket.id]; + if (result.failed) { + if (player) { + socket.emit("game_response", { response: "unfriend_failed", reason: result.reason }); + } + return; + } + }, + function () { + var player = players[socket.id]; + if (player) { + socket.emit("game_response", { response: "unfriend_failed", reason: "coms failure" }); + } + }, + ); + } + success_response({ success: false, in_progress: true }); + }); + socket.on("duel", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.event == "challenge") { + var invited = players[name_to_id[data.id || data.name]]; + if (!invited || invited.id == player.id) { + return socket.emit("game_log", "Invalid"); + } + if (invited.duel || player.duel) { + return socket.emit("game_log", "Already dueling"); + } + invited.socket.emit("duel", { event: "chellenge", name: player.name }); + socket.emit("game_response", { response: "challenge_sent", name: invited.name }); + invited.socket.emit("game_response", { response: "challenge_received", name: player.name }); + challenges[player.name] = invited.name; + } else if (data.event == "accept") { + var challenger = players[name_to_id[data.id || data.name]]; + if (!challenger || challenges[challenger.name] != player.name) { + return socket.emit("game_log", "Challenge expired"); + } + if (challenger.duel || player.duel) { + return socket.emit("game_log", "Already dueling"); + } + if (is_in_pvp(challenger) || is_in_pvp(player)) { + return socket.emit("game_log", "Can't start a duel if any of the parties are already in a pvp zone"); + } + delete challenges[challenger.name]; + challenger.socket.emit("game_response", { response: "challenge_accepted", name: player.name }); + var name = randomStr(20); + var a = []; + var b = []; + instance = create_instance(name, "duelland"); + instance.info = { + seconds: (is_sdk && 20) || 60, + active: false, + A: [player_to_summary(challenger)], + B: [player_to_summary(player)], + id: name, + }; + instance.info.A[0].active = true; + instance.info.B[0].active = true; + clean_slate(challenger); + transport_player_to(challenger, name, 1); + if (challenger.party) { + parties[challenger.party].forEach(function (p) { + a.push(p); + }); + } else { + a = [challenger.name]; + } + clean_slate(player); + transport_player_to(player, name, 2); + if (player.party) { + parties[player.party].forEach(function (p) { + b.push(p); + }); + } else { + b = [player.name]; + } + if (!E.duels) { + E.duels = {}; + } + var duel = { + challenger: challenger.name, + a: a, + vs: player.name, + b: b, + instance: name, + seconds: (is_sdk && 20) || 60, + active: false, + id: name, + }; + challenger.team = "A"; + challenger.duel = duel; + challenger.s.stunned = { ms: 120000 }; + resend(challenger, "u+cid"); + player.team = "B"; + player.duel = duel; + player.s.stunned = { ms: 120000 }; + resend(player, "u+cid"); + E.duels[name] = duel; + broadcast_e(); + a.forEach(function (p) { + if (p != challenger.name && get_player(p)) { + get_player(p).socket.emit("game_response", { + response: "duel_started", + challenger: challenger.name, + vs: player.name, + id: name, + }); + } + }); + b.forEach(function (p) { + if (p != player.name && get_player(p)) { + get_player(p).socket.emit("game_response", { + response: "duel_started", + challenger: challenger.name, + vs: player.name, + id: name, + }); + } + }); + } else if (data.event == "enter") { + if (is_in_pvp(player)) { + return socket.emit("game_log", "Can't join the duel from a pvp zone!"); + } + if (player.duel) { + return socket.emit("game_log", "Already in a duel!"); + } + if (!E.duels[data.id]) { + return socket.emit("game_log", "Duel expired"); + } + if (E.duels[data.id].active) { + return socket.emit("game_log", "Duel already started"); + } + if (!(E.duels[data.id].a.includes(player.name) || E.duels[data.id].b.includes(player.name))) { + return socket.emit("game_log", "Not your duel"); + } + clean_slate(player); + + if (E.duels[data.id].a.includes(player.name)) { + transport_player_to(player, data.id, 1); + player.team = "A"; + } else { + transport_player_to(player, data.id, 2); + player.team = "B"; + } + + player.duel = E.duels[data.id]; + if (player.duel.a.includes(player.name)) { + instances[data.id].info.A.push(player_to_summary(player)); + instances[data.id].info.A[instances[data.id].info.A.length - 1].active = true; + } else { + instances[data.id].info.B.push(player_to_summary(player)); + instances[data.id].info.B[instances[data.id].info.B.length - 1].active = true; + } + player.s.stunned = { ms: 120000 }; + + resend(player, "u+cid"); + instance_emit(data.id, "game_chat", { message: player.name + " joined the duel!" }); + } + }); + socket.on("party", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.event == "invite") { + // if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can send invites"); return; } + if (player.party && (parties[player.party].length >= limits.party_max || player.party_length >= limits.party)) { + return fail_response("party_full"); + } + var invited = players[name_to_id[data.id || data.name]]; + if (!invited || invited.id == player.id) { + return fail_response("invalid"); + } + if (player.party && player.party == invited.party) { + return success_response("already_in_party"); + } + // if(invited.party) { socket.emit("game_log",invited.name+" is already partying"); return; } + invited.socket.emit("invite", { name: player.name }); + socket.emit("game_log", "Invited " + invited.name + " to party"); + if (!invitations[player.name]) { + invitations[player.name] = {}; + } + invitations[player.name][invited.id] = 1; + } + if (data.event == "request") { + // if(player.party) { return; } + var invited = players[name_to_id[data.id || data.name]]; + if (!invited || invited.id == player.id) { + return fail_response("invalid"); + } + if (player.party && player.party == invited.party) { + return success_response("already_in_party"); + } + // if(!invited.party) { socket.emit("game_log",invited.name+" isn't partying"); return; } + invited.socket.emit("request", { name: player.name }); + socket.emit("game_log", "Requested to join " + invited.name + "'s party"); + if (!requests[player.name]) { + requests[player.name] = {}; + } + requests[player.name][invited.id] = 1; + } + if (data.event == "accept") { + var inviter = players[name_to_id[data.name]]; + // if(player.party) { socket.emit("party_update",{list:parties[player.party],party:party_to_client(player.party)}); socket.emit("game_log","Already partying"); return; } + // if(!inviter || (inviter.party && inviter.party!=inviter.name)) { socket.emit("game_log","Party was disbanded"); return; } + if (!inviter) { + return fail_response("player_gone", { name: data.name }); + } + if ( + inviter.party && + (parties[inviter.party].length >= limits.party_max || inviter.party_length >= limits.party) + ) { + return fail_response("party_full"); + } + if (!invitations[inviter.name] || !invitations[inviter.name][player.id]) { + return fail_response("invitation_expired"); + } + if (player.party && player.party == inviter.party) { + return success_response("already_in_party"); + } + if (player.party) { + leave_party(player.party, player); + socket.emit("party_update", {}); + socket.emit("game_log", "Left your current party"); + } + invitations[inviter.name][player.id] = 0; + if (!inviter.party) { + inviter.party = inviter.name; + parties[inviter.name] = [inviter.name]; + resend(inviter, "nc+u+cid"); + delete requests[inviter.name]; + if (!players[name_to_id[data.name]]) { + return fail_response("player_gone", { name: data.name }); + } // these repetitions are all because socket.emit's can cause in-line disconnects and disband parties right after creation [07/08/20] + } + player.party = inviter.party; + parties[inviter.party].push(player.name); + party_emit(player.party, "party_update", { + list: parties[inviter.party], + party: party_to_client(inviter.party), + message: + player.name + + " joined the party" + + ((inviter.party != inviter.name && " with " + inviter.name + "'s invite") || ""), + }); + resend(player, "nc+u+cid"); + delete requests[player.name]; + delete requests[inviter.name]; // optional + } + if (data.event == "raccept") { + var requester = players[name_to_id[data.name]]; + if (!requester) { + return fail_response("player_gone", { name: data.name }); + } + // if(requester.party) { socket.emit("game_log","Already partying"); return; } + if (player.party && (parties[player.party].length >= limits.party_max || player.party_length >= limits.party)) { + return fail_response("party_full"); + } + if (!requests[requester.name] || !requests[requester.name][player.id]) { + return fail_response("request_expired"); + } + if (player.party && player.party == requester.party) { + return success_response("already_in_party"); + } + if (requester.party) { + leave_party(requester.party, requester); + requester.socket.emit("party_update", {}); + requester.socket.emit("game_log", "Left the party"); + if (!players[name_to_id[data.name]]) { + return fail_response("player_gone", { name: data.name }); + } + } + requests[requester.name][player.id] = 0; + if (!player.party) { + player.party = player.name; + parties[player.name] = [player.name]; + resend(player, "nc+u+cid"); + delete requests[player.name]; + } + requester.party = player.party; + parties[player.party].push(requester.name); + party_emit(requester.party, "party_update", { + list: parties[player.party], + party: party_to_client(player.party), + message: requester.name + " joined the party", + }); + resend(requester, "nc+u+cid"); + delete requests[requester.name]; + delete requests[player.name]; // optional + } + if (data.event == "leave") { + if (!player.party) { + socket.emit("party_update", {}); + return success_response({}); + } + leave_party(player.party, player); + socket.emit("party_update", {}); + socket.emit("game_log", "Left the party"); + resend(player, "nc+u+cid"); + } + if (data.event == "kick") { + if (!player.party) { + return success_response({}); + } + // if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can kick someone"); return; } + if (!in_arr(data.name, parties[player.party])) { + socket.emit("party_update", { list: parties[player.party], party: party_to_client(player.party) }); + return success_response({}); + } + if (parties[player.party].indexOf(player.name) > parties[player.party].indexOf(data.name)) { + return fail_response("cant_kick"); + } + var kicked = players[name_to_id[data.name]]; + if (!kicked) { + return fail_response("player_gone"); + } + leave_party(player.party, kicked); + if (player.party) { + party_emit(player.party, "game_log", player.name + " kicked " + kicked.name); + } + kicked.socket.emit("party_update", {}); + kicked.socket.emit("game_log", "You've been removed from the party"); + resend(kicked, "nc+u+cid"); + } + success_response({}); + }); + socket.on("magiport", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (magiportations[data.name] && magiportations[data.name][player.name] && get_player(data.name)) { + delete magiportations[data.name][player.name]; + if (instances[get_player(data.name).in].mount != instances[player.in].mount) { + return fail_response("cant_in_bank"); + } + if (!magiport_someone(player, get_player(data.name))) { + return fail_response("invalid"); + } + return success_response({}); + } else { + player.socket.emit("game_response", { response: "magiport_gone", name: data.name }); + return fail_response("inviter_gone"); + } + }); + socket.on("trade", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (data.event == "show" && player.p.trades != 1) { + player.p.trades = 1; + reslot_player(player); + resend(player, "nc+u+cid"); + } else if (data.event == "hide" && player.p.trades != null) { + player.p.trades = null; + reslot_player(player); + resend(player, "nc+u+cid"); + } + }); + socket.on("signup", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!signups[player.name]) { + disappearing_text(false_socket, npcs.bean, "+1", { color: "#4D9A59", xy: 1 }); + } + signups[player.name] = true; + socket.emit("game_response", { response: "signed_up" }); + }); + socket.on("join", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (player.type == "merchant") { + return fail_response("no_merchants"); + } + if (player.s.hopsickness) { + return fail_response("cant_when_sick"); + } + if (player.user) { + return fail_response("cant_in_bank"); + } + if (data.name == "goobrawl" && events.goobrawl) { + if (player.map != "goobrawl") { + transport_player_to(player, "goobrawl"); + } + } else if (data.name == "crabxx" && events.crabxx) { + if (simple_distance(player, { map: "main", x: -1000, y: 1700 }) > 200) { + transport_player_to(player, "main", [-1000, 1700, 0, 40]); + } + } else if (data.name == "franky" && events.franky) { + if (simple_distance(player, { map: "level2w", x: -300, y: 150 }) > 200) { + transport_player_to(player, "level2w", [-300, 150, 0, 40]); + } + } else if (data.name == "icegolem" && events.icegolem) { + if (simple_distance(player, { map: "winterland", x: 820, y: 425 }) > 100) { + transport_player_to(player, "winterland", [820, 425, 0, 10]); + } + } else if (data.name == "abtesting" && E.abtesting) { + if (player.map != "abtesting" || !player.team) { + if ( + ssince(timers.abtesting_start) > 120 && + (!player.p.abtesting || player.p.abtesting[0] != E.abtesting.id) + ) { + return fail_response("join_too_late"); + } + + if (player.p.abtesting && player.p.abtesting[0] == E.abtesting.id) { + player.team = player.p.abtesting[1]; + } else { + player.team = random_one(["A", "B"]); + } + + player.p.abtesting = [E.abtesting.id, player.team]; + + player.team = player.p.abtesting[1]; // transport does a restore ... + save_state(player); + + if (player.team == "A") { + transport_player_to(player, "abtesting", 2); + } else if (player.team == "B") { + transport_player_to(player, "abtesting", 3); + } + + resend(player, "cid"); + } + } else { + return fail_response("cant_join"); + } + success_response(); + }); + socket.on("stop", function (data) { + var player = players[socket.id]; + var change = false; + if (!player) { + return; + } + if (!data || !data.action) { + if (player.s.invis) { + step_out_of_invis(player); + change = true; + } + if (Object.keys(player.c).length) { + player.c = {}; + change = true; + } + } else if (data.action == "invis") { + if (player.s.invis) { + step_out_of_invis(player); + change = true; + } + } else if (data.action == "channeling") { + if (Object.keys(player.c).length) { + player.c = {}; + change = true; + } + } else if (data.action == "teleport" || data.action == "town") { + if (player.c.town) { + delete player.c.town; + change = true; + } + } else if (data.action == "revival") { + if (player.c.revival) { + delete player.c.revival; + change = true; + } + } + if (change) { + resend(player, "u+cid"); + } + success_response(); + }); + socket.on("tarot", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + var npc = G.maps[player.map].ref.twitch; + if (!npc || simple_distance(npc, player) > 500) { + return socket.emit("game_response", "distance"); + } + for (var name in player.s) { + if (name.startsWith("tarot")) { + return socket.emit("game_response", "tarot_exists"); + } + } + }); + socket.on("bet", function (data) { + if (!instances.tavern) { + return; + } + var player = players[socket.id]; + if (!player || player.user || (player.map != "tavern" && !is_sdk)) { + return; + } + if (player.s.xshotted) { + return socket.emit("game_response", "bet_xshot"); + } + if (data.type == "roulette") { + if (!is_sdk) { + return; + } // Old routine [23/08/18] + if (!data.odds) { + data.odds = "black"; + } + data.odds = "" + data.odds; + data.gold = max(1, parseInt(data.gold) || 1); + if (!tavern.roulette.odds[data.odds]) { + return; + } + if (tavern.roulette.state != "bets") { + socket.emit("game_log", "Not taking bets yet!"); + return; + } + if (Object.keys(player.bets).length >= 5) { + socket.emit("game_log", "You already have 5 active bets"); + return; + } + if (player.gold < data.gold) { + socket.emit("game_log", "Not enough gold"); + return; + } + + socket.emit("game_log", { message: "Bet accepted on " + data.odds, color: "white" }); + socket.emit("game_log", { message: to_pretty_num(data.gold) + " gold", color: "gray" }); + + var rid = randomStr(10); + player.gold -= data.gold; + player.bets[rid] = { + id: rid, + type: "roulette", + odds: data.odds, + gold: data.gold, + pid: socket.id, + state: "bet", + }; + tavern.roulette.players[player.socket.id] = true; + io.to("roulette").emit("bet", { name: player.name, type: "roulette", odds: data.odds, gold: data.gold }); + socket.emit("tavern", player.bets[rid]); + resend(player, "reopen+nc"); + } + if (data.type == "dice") { + // server_log(JSON.stringify(data)); + var num = min(99.99, max(parseFloat(data.num) || 0, 0.01)); + var gold = max(10000, min(parseInt(data.gold) || 0, 100000000000)); + var odds = 100.0 / num; + var dir = "down"; + if (data.dir == "up") { + odds = 100.0 / (100 - num); + dir = "up"; + } + odds = min(odds, 10000); + odds = parseFloat(floor_f2(odds)); + var win = gold * odds; + var raw = win; + var edge = ceil(((gold * odds - gold) * house_edge()) / 100.0); + win = parseInt(win); + edge = parseInt(edge); + if (tavern.dice.state == "roll") { + return socket.emit("game_response", "tavern_too_late"); + } + if (tavern.dice.state != "bets") { + return socket.emit("game_response", "tavern_not_yet"); + } + if (gold > player.gold) { + return socket.emit("game_response", "gold_not_enough"); + } + if (win - edge - gold > (S.gold - house_debt()) * 0.4) { + return socket.emit("game_response", "tavern_gold_not_enough"); + } + if (Object.keys(player.bets).length) { + return socket.emit("game_response", "tavern_dice_exist"); + } + // if(Object.keys(player.bets).length>=5) return socket.emit("game_response","tavern_too_many_bets"); + // socket.emit("game_log",{"message":"Bet accepted on "+num.toFixed(2)+" "+dir.toUpperCase(),"color":"white"}); + socket.emit("game_log", { message: "Num: " + num.toFixed(2) + " " + dir.toUpperCase(), color: "white" }); + socket.emit("game_log", { message: "Bet: " + to_pretty_num(gold) + " gold", color: "gray" }); + + var rid = randomStr(10); + player.gold -= gold; + player.bets[rid] = { + id: rid, + type: "dice", + num: num, + gold: gold, + dir: dir, + pid: socket.id, + state: "bet", + win: win, + edge: edge, + odds: odds, + }; + tavern.dice.players[player.socket.id] = true; + instance_emit(tavern, "tavern", { + event: "bet", + name: player.name, + type: "dice", + num: num, + gold: gold, + dir: dir, + }); + resend(player, "reopen+nc"); + } + if (data.type == "slots") { + var gold = 1000000; + if (gold > player.gold) { + return socket.emit("game_response", "gold_not_enough"); + } + player.gold -= gold; + S.gold += gold; + player.q.slots = { ms: 3000 }; + xy_emit(player, "ui", { type: "slots", player: player.name }); + socket.emit("game_response", { response: "gold_use", gold: gold, game: data.type }); + resend(player, "u+cid+reopen+nc"); + } + }); + socket.on("tavern", function (data) { + if (data.event == "info") { + socket.emit("tavern", { event: "info", edge: house_edge(), max: parseInt((S.gold - house_debt()) * 0.4) }); + } + }); + socket.on("play", function (data) {}); + socket.on("pet", function (data) { + var player = players[socket.id]; + if (!player) { + return; + } + if (!player.monster) { + player.monster = new_monster(player.in, { + type: player.pet, + stype: "pet", + x: player.x, + y: player.y, + owner: player.name, + name: "Skimpy", + }); + } + }); + socket.on("whistle", function (data) { + if (!player.monster) { + return; + } + xy_emit(player.monster, "disappear", { id: player.monster.id }); + player.monster.x = player.x; + player.monster.y = player.y; + player.monster.map = player.map; + player.monster.in = player.in; + player.monster.u = true; + player.monster.cid++; + }); + socket.on("list_pvp", function (data) { + var plist = []; + for (var j = 1; j < 201; j++) { + if (pend - j < 0) { + break; + } + plist.push(pwns[(pend - j) % 200]); + } + socket.emit("pvp_list", { code: data && data.code, list: plist }); + }); + socket.on("players", function (data) { + var player = players[socket.id]; + var sdata = []; + if (!player) { + return; + } // || is_pvp + for (var id in players) { + var current = players[id]; + var mapn = current.map; + current.age = parseInt(ceil(hsince(new Date(current.created)) / 24.0)); + if (is_pvp) { + mapn = "Unknown"; + sdata.push({ + name: "Hidden", + map: mapn, + age: current.age, + level: current.level, + type: current.type, + afk: (current.afk && 1) || 0, + party: (current.party && "Hidden") || "", + kills: current.kills, + }); + } else if (!is_in_pvp(current)) { + sdata.push({ + name: current.name, + map: mapn, + age: current.age, + level: current.level, + type: current.type, + afk: (current.afk && 1) || 0, + party: current.party || "", + }); + } + } + socket.emit("players", sdata); + }); + socket.on("pets", function (data) { + var player = players[socket.id]; + var sdata = []; + if (!player) { + return; + } // || is_pvp + for (var id in player.p.pets || {}) { + sdata.push(player.p.pets[id]); + } + socket.emit("players", sdata); + }); + socket.on("harakiri", function (data) { + var player = players[socket.id]; + if (!player || player.rip) { + return; + } + defeat_player(player); + if (!player.rip) { + rip(player); + } + disappearing_text(player.socket, player, "SEPPUKU", { xy: 1, size: "huge", color: "#6F76A6" }); + resend(player, "u+cid"); + }); + socket.on("deepsea", function (data) { + return; + var player = players[socket.id]; + if (!player || player.rip || player.tskin == "deepsea") { + return; + } + player.tskin = "deepsea"; + disappearing_text(player.socket, player, "ROARRRRRRR", { xy: 1, size: "huge", color: "#60A975" }); + resend(player, "u+cid"); + }); + socket.on("blend", function (data) { + var player = players[socket.id]; + var min = 99999; + var x = null; + if (!player || player.rip) { + return; + } + for (var id in instances[player.in].monsters || {}) { + var m = instances[player.in].monsters[id]; + var c = distance(m, player, true); + if (c < min) { + min = c; + x = m; + } + } + if (x) { + var skin = G.monsters[x.type].skin || x.type; + if (player.tskin != skin) { + player.tskin = skin; + resend(player, "u+cid"); + } + } + }); + socket.on("legacify", function (data) { + var player = players[socket.id]; + if (!player || player.rip) { + return; + } + for (var i = 0; i < 42; i++) { + if (0 && player.items[i] && !player.items[i].p && ["fury", "starkillers"].includes(player.items[i].name)) { + player.items[i].p = "legacy"; + } + } + resend(player, "reopen"); + }); + socket.on("requested_ack", function (data) { + // send "requesting_ack", to verify someone is actually connected [03/08/16] + server_log("requested_ack" + JSON.stringify(data), 1); + }); + socket.on("disconnect", function () { + //#IMPORTANT: disconnect exceptions are fatal [07/08/16] + // console.log("disconnect!"); + var player = players[socket.id]; + var observer = observers[socket.id]; + try { + delete sockets[socket.id]; + } catch (e) {} + if (player) { + player.dc = true; + try { + defeat_player(player); + } catch (e) { + log_trace("#X DCERRORPVP", e); + } + // if(!server.live) { server_log('Ignoring the "disconnect" of '+player.name,1); return; } + + try { + if (player.moving && distance(player, { x: player.going_x, y: player.going_y }) < 800) { + player.x = player.going_x; + player.y = player.going_y; + } + } catch (e) { + log_trace("#X DCERRORM", e); + } + // to not get stuck on out-of-bounds corners + + try { + if (player.party) { + leave_party(player.party, player); + } + } catch (e) { + log_trace("#X DCERROR1", e); + } + + try { + xy_emit(player, "disappear", { id: player.id, reason: "disconnect" }); + } catch (e) { + log_trace("#X DCERROR2", e); + } + + server_log("Disconnected Player: " + socket.id + " " + player.name + " Calls: " + socket.total_calls, 1); + + try { + for (var bid in player.bets) { + player.gold += player.bets[bid].gold; + } + player.bets = {}; + } catch (e) { + log_trace("#X DCERRORBETS", e); + } + + try { + if (player.monster) { + remove_monster(player.monster); + } + } catch (e) { + log_trace("#X DCERRORPETS", e); + } + + try { + delete players[socket.id]; + delete instances[player.in].players[player.id]; + if (instances[player.in].solo == player.id) { + destroy_instance(player.in); + } + pmap_remove(player); + } catch (e) { + log_trace("#X DCERROR3 ", e); + } + + try { + restore_state(player, true); + } catch (e) { + log_trace("#X DCERRORrestore", e); + } + + try { + if (gameplay != "hardcore" && gameplay != "test") { + dc_players[player.real_id] = player; + sync_loop(); + } else { + save_player(player); + } + } catch (e) { + log_trace("#X DCERROR", e); + } + } + if (observer) { + server_log("Disconnected Observer: " + socket.id + " Calls: " + socket.total_calls); + try { + delete_observer(socket); + } catch (e) { + log_trace("#X DCERRORO ", e); + } + } + }); + socket.on("shutdown", function (data) { + if (data.pass != variables.access_master) { + return; + } + if (data.reason) { + broadcast("disconnect_reason", data.reason); + } + setTimeout(shutdown_routine, 10000); + }); + socket.on("notice", function (data) { + if (data.pass != variables.access_master) { + return; + } + broadcast("notice", { message: data.message }); + }); + socket.on("render", function (data) { + if (data.pass != variables.access_master) { + return; + } + var player = players[socket.id]; + var output = ""; + var json_output = undefined; + var window = null; + var after = ""; + try { + eval(data.code); + } catch (e) { + output = "Exception: " + e; + } + if (window) { + socket.emit("simple_eval", { + window: window, + after: after, + code: "for(var id in data.window) window[id]=data.window[id]; eval(data.after);", + }); + } else if (json_output !== undefined) { + socket.emit("simple_eval", { output: output, json_output: json_output, code: "show_json(data.json_output)" }); + } else { + socket.emit("simple_eval", { + output: output, + json_output: json_output, + code: 'show_modal("
"+data.output+"
")', + }); + } + resend(player, "reopen+u+cid"); + }); + socket.on("error", function (data) { + // socket.emit("error") brings server down [16/02/22] + }); + socket.on("eval", function (data) { + if (data.command) { + var player = players[socket.id]; + if (!player) { + return; + } + if (player.map != "cyberland" || player.rip) { + return player.socket.emit("game_log", "Not connected to the mainframe"); + } + if (data.command == "hello") { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "hi", + id: "mainframe", + }); + } else if (data.command == "give") { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "what?", + id: "mainframe", + }); + } else if (data.command.startsWith("swap")) { + var numbers = data.command.split(" "); + if (numbers.length != 3) { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "...", + id: "mainframe", + }); + } else { + var a = parseInt(numbers[1]); + var b = parseInt(numbers[2]); + if (0 <= a && a < 42 && 0 <= b && b < 42) { + if (a == player.p.item_num) { + player.p.item_num = b; + } else if (b == player.p.item_num) { + player.p.item_num = a; + } + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "done", + id: "mainframe", + }); + } else { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "ugh, ok", + id: "mainframe", + }); + } + } + } else if (data.command == "stop") { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "mechagnomes assemble", + id: "mainframe", + }); + get_monsters("mechagnome").forEach(function (m) { + if (m.target) { + stop_pursuit(m, { stop: 1, cause: "stop()" }); + } + //else m.irregular=3; + }); + } else if (data.command.startsWith("give") && data.command != "give spares") { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "no", + id: "mainframe", + }); + } else if (data.command == "secret web mode" && (player.p.steam_id || player.p.mas_auth_id)) { + player.p.secret_web_mode = true; + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "secret web mode unlocked", + id: "mainframe", + }); + } else if (data.command == "give spares") { + if (S.misc && S.misc.spares && S.misc.spares.length) { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "here you go", + id: "mainframe", + }); + drop_one_thing(player, S.misc.spares, { x: 1, y: -88 }); + S.misc.spares = []; + } else { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "come later", + id: "mainframe", + }); + } + } else { + if (!player.supercomputer) { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "UNAUTHORIZED COMMAND", + id: "mainframe", + }); + for (var id in instances.cyberland.monsters) { + var monster = instances.cyberland.monsters[id]; + if (!monster.target) { + target_player(monster, player); + } + } + } else { + xy_emit({ in: "cyberland", map: "cyberland", x: 0, y: -100 }, "chat_log", { + owner: "mainframe", + message: "UNAUTHORIZED, COMRADE", + id: "mainframe", + }); + } + } + } + if (data.pass == variables.access_master) { + eval(data.code); + } + }); + }); } -function init_io(){ -io.on('connection', function (socket) { - if(socket.handshake.query.server_method) - { - if(0 && socket.handshake.query.server_master==variables.server_master) // this was to make servers communicate with each other and disconnect overflows immediately [28/10/23] - { - if(socket.handshake.query.server_method=="players") - { - socket.emit("players"); // decided to make the existing cron more aggressive [26/09/21] - } - } - socket.disconnect(); - return; - } - sockets[socket.id]=socket; - socket.total_calls=0; - socket.calls=[]; - socket.fs={}; // function list - - if(!is_socket_allowed(socket)) disconnect_old_sockets(socket); - - var original_on=socket.on; - socket.on=function(method,f){ - // takes the "f" function, the function thats sent to socket.on, wraps it into a "g" function - var g=function(data){ - ls_method=method; - if(mode.log_all) console.log("'"+method+"': "+JSON.stringify(data)); - try{ - var climit=limits.calls,name="_observer"; - if(data===undefined) data={}; // data normalisation [28/08/18] - socket.total_calls++; add_call_cost(-1); - current_socket=socket; - call_modifier=({"open_chest":0.1,"skill":0.05,"target":0.5}[method])||1; - if(players[socket.id]) - { - name=players[socket.id].name; - // if(players[socket.id].type=="merchant") climit=round(climit/3); - // Merchants are first class citizens now! [14/01/18] - } - else - { - climit=round(climit/4); - } - if(method=="cm") - { - var add=1,len=data.message.length,mult=1; - if(len>100) add=2; - else if(len>1000) add=3; - else if(len>10000) add=10; - else if(len>50000) add=20; - // console.log("add: "+add+"len: "+len); - if(data.to.length>1) mult=0.8; - add_call_cost(add*data.to.length*mult,undefined,"cm_data"); - } - else - { - if(CC[method]) add_call_cost(CC[method]||0); - } - if(get_call_cost()>climit && method!="disconnect") - { - server_log(">>> LIMITDC "+name,1); - socket.emit("limitdcreport",{calls:socket.calls,climit:climit,total:socket.total_calls}); - socket.emit("disconnect_reason","limitdc"); - socket.disconnect(); - } - else f(data); - } - catch(e){ - try{ - var climit=limits.calls; - add_call_cost(16); - log_trace("socket.on: "+method.substr(0,200),e); - if(get_call_cost()>climit) - { - server_log(">>> LIMITDC2 "+name,1); - socket.emit("limitdcreport",{calls:socket.calls,climit:climit,total:socket.total_calls,method:method.substr(0,200)}); - socket.emit("disconnect_reason","limitdc"); - socket.disconnect(); - } - else - { - try{ - socket.emit("game_error","ERROR!"); - }catch(e){} - } - }catch(e){ - log_trace("limit_calls",e); - } - } - current_socket=false_socket; - } - socket.fs[method]=f; - original_on.apply(socket,[method,g]); - }; - - var data={region:region,name:server_name,pvp:is_pvp,gameplay:gameplay,info:instances[socket.first_map]&&instances[socket.first_map].info||{}}; - socket.first_map=socket.first_in=observer_map; - socket.first_x=observer_x; - socket.first_y=observer_y+120; - socket.desktop=true; - if(socket.request && socket.request._query && socket.request._query.secret) - { - for(var id in players) - { - var player=players[id]; - if(player.secret==socket.request._query.secret) - { - socket.player=player; - data.character=player_to_client(player); data.character.id=data.character.name=player.name; - socket.first_map=player.map; - socket.first_in=player.in; - socket.first_x=player.x; - socket.first_y=player.y; - if(socket.request._query.desktop) socket.desktop=true,socket.first_y+=120; - else socket.desktop=false; - } - } - } - data.x=socket.first_x; data.y=socket.first_y; data.map=socket.first_map; data.in=socket.first_in; - broadcast_e(true); - data.S=E; - socket.emit('welcome',data); - socket.on('send_updates',function(){ - if(observers[socket.id]) send_all_xy(observers[socket.id]); - if(players[socket.id]) send_all_xy(players[socket.id]); - }) - socket.on('loaded',function(data){ - var observer=observers[socket.id]={ - socket:socket, - x:socket.first_x,y:socket.first_y, - // vision:[round((data.width/2)/data.scale)+B.ext_vision,round((data.height/2)/data.scale)+B.ext_vision], - map:socket.first_map, - in:socket.first_in, - observer:1, - id:"o"+socket.id, - s:{}, - }; - if(socket.player) observer.player=socket.player; - // observer.vision[0]=min(1000,observer.vision[0]); observer.vision[1]=min(700,observer.vision[1]); - observer.vision=B.vision; - // socket.emit("observing",{map:observer.map,x:observer.x,y:observer.y}); - resume_instance(instances[observer.in]); - instances[observer.in].observers[observer.id]=observer; - send_all_xy(observer); - }); - socket.on('o:home',function(data){ - var observer=observers[socket.id]; - if(!observer) return; - var player=observer.player; - if(!player || player.dc) return; - transport_observer_to(observer,player.in,player.map,player.x,player.y+(socket.desktop&&120||0)); - }); - socket.on('o:command',function(data){ - var observer=observers[socket.id]; - if(!observer) return; - var player=observer.player; - if(!player || player.dc) return; - player.socket.emit('code_eval',data); - }); - socket.on('cm',function(data){ - var player=players[socket.id],receivers=[]; - if(!player || player.s.mute) return fail_response("muted"); - data.to.forEach(function(name){ - var p=players[name_to_id[name]]; - if(p) p.socket.emit("cm",{name:player.name,message:data.message||""}),receivers.push(name); - }); - success_response("data",{locals:[],receivers:receivers}); - }); - socket.on('say',function(data){ - var player=players[socket.id],message=strip_string(data.message).substr(0,1200); if(!player || player.s.mute) return fail_response("muted"); - if(data.code && player.last_say && ssince(player.last_say)<15) return fail_response("chat_slowdown"); - if(player.last_say && mssince(player.last_say)<400) return fail_response("chat_slowdown"); - if(!message || !message.length) return fail_response("invalid"); - player.last_say=new Date(); - if(data.party) - { - if(!player.party) return fail_response("not_in_a_party"); - party_emit(player.party,"partym",{owner:player.name,message:message,id:player.id,p:true}); - } - else if(data.name) - { - var target=get_player(data.name); - if(!target) - { - player.socket.emit("pm",{owner:player.name,to:data.name,message:message,id:player.id,xserver:true}); - appengine_call("log_chat",{to:["",data.name],type:"xprivate",message:message,fro:player.name,author:player.owner},function(result){ - if(result.failed && players[socket.id]) - player.socket.emit("pm",{owner:player.name,to:data.name,message:"(FAILED)",id:player.id,xserver:true}); - }); - } - else - { - if(target.name==player.name) return fail_response("invalid"); - player.socket.emit("pm",{owner:player.name,to:data.name,message:message,id:player.id}); - target.socket.emit("pm",{owner:player.name,message:message,id:player.id}); - appengine_call("log_chat",{to:[target.owner,target.name],type:"private",message:message,fro:player.name,author:player.owner}); - } - } - else - { - if(1) - { - broadcast("chat_log",{owner:player.name,message:message,id:player.id,p:true}); - var owners={}; - for(var id in players) - { - var p=players[id]; - owners[p.owner]=owners[p.owner]||[]; - owners[p.owner].push(p.name); - } - appengine_call("log_chat",{to:Object.entries(owners),type:"ambient",message:message,fro:player.name,author:player.owner}); - } - else - xy_emit(player,"chat_log",{owner:player.name,message:message,id:player.id,p:true}); - appengine_call("log_chat",{type:"server",message:message,fro:player.name,author:player.owner}); - } - if(player.s.typing) - { - delete player.s.typing; - resend(player,"u+cid+nc"); - } - success_response(); - }); - socket.on('ping_trig',function(data){ socket.emit("ping_ack",data); }); - socket.on('target',function(data){ - var player=players[socket.id]; if(!player) return; - reduce_call_cost(); - player.target=data.id; - player.focus=data.xid; - var target=instances[player.in].monsters[data.id]; - if(!target) target=instances[player.in].players[data.id]; - if(!target) target=instances[player.in].players[NPC_prefix+data.id]; - if(!target) player.target=null; - var focus=instances[player.in].monsters[data.xid]; - if(!focus) focus=instances[player.in].players[data.xid]; - if(!focus) focus=instances[player.in].players[NPC_prefix+data.xid]; - if(!focus) player.focus=null; - if(focus && focus.screenshot) - { - target.going_x=player.x; - target.going_y=player.y; - target.u=true; - start_moving_element(focus); - } - if(focus && player.map=="cgallery" && focus.npc) - { - // target.going_x=player.x; - // target.going_y=player.y; - // target.u=true; - // start_moving_element(target); - if(!player.tcx) player.tcx={}; - if(focus.ctype=="body") - player.tskin=focus.skin; - else if(player.cx.length>5) return fail_response("invalid"); - player.tcx[focus.ctype]=focus.cx[focus.ctype]; - // player.tcx=target.cx; - resend(player,"u+cid"); - } - // server_log(player.name+" target: "+player.target+" focus: "+player.focus); - resend(player,"u+nc"); - success_response({}); - }); - socket.on('ureward',function(data){ - if(!player || player.user || !data.name || !G.docs.rewards[data.name]) return; - if(!player.verified || !player.auth_id) return socket.emit("game_response","reward_notverified"); - if(!player.user.rewards) player.user.rewards=[]; - if(player.user.rewards.includes(data.name)) return socket.emit("game_response","reward_already"); - player.user.rewards.push(data.name); - exchange(player,G.docs.rewards[data.name],{name:"reward_"+data.name}); - socket.emit("game_response",{response:"reward_received",rewards:player.user.rewards}); - - }); - socket.on('creward',function(data){ - if(!player || !data.name || !G.classes[player.type].rewards[data.name]) return; - if(!player.verified || !player.auth_id) return socket.emit("game_response","reward_notverified"); - if(!player.p.rewards) player.p.rewards=[]; - if(player.p.rewards.includes(data.name)) return socket.emit("game_response","reward_already"); - player.p.rewards.push(data.name); - exchange(player,G.classes[player.type].rewards[data.name].reward,{name:"reward_"+data.name}); - socket.emit("game_response",{response:"reward_received",rewards:player.p.rewards}); - - }); - socket.on('cx',function(data){ - var player=players[socket.id]; if(!player) return; - // if(player.role!="gm") return socket.emit('game_log',"Cosmetics system is out of the test phase for now"); - // console.log(data); - var cx=player.cx,cxl=all_cx(player); - // if(!player.tcx) player.tcx=clone(player.cx); - if(data.slot && !data.name) - { - if(data.slot=="back") delete cx.tail; // synced with render_cgallery - if(data.slot=="face") delete cx.makeup; - delete cx[data.slot]; - } - else - { - if(!T[data.name] || !cxl[data.name] && player.role!="cx") return fail_response("cx_not_found"); - if((T[data.name]=="body" || T[data.name]=="armor" || T[data.name]=="character") && data.slot=="skin") player.skin=data.name; - else if((T[data.name]=="body" || T[data.name]=="armor") && data.slot=="upper") cx.upper=data.name; - else if(cxtype_to_slot[T[data.name]] && cxtype_to_slot[T[data.name]]!="skin") - cx[cxtype_to_slot[T[data.name]]]=data.name; - } - prune_cx(player.cx,player.skin); - resend(player,"u+cid"); - success_response(); - }); - socket.on('gm',function(data){ - var player=players[socket.id]; if(!player || player.role!="gm") return; - var target=players[id_to_id[data.id]],action=data.action; - if(action=="mute") - { - if(!target) return socket.emit("game_log","Player not found: "+data.id); - if(!target.s.mute) - { - target.s.mute={ms:48*60*60*1000}; - return socket.emit("game_chat","Muted "+target.name); - } - else - { - target.s.mute={ms:0}; - return socket.emit("game_chat","Unmuted "+target.name); - } - } - else if(action=="jail") - { - if(!target) return socket.emit("game_log","Player not found: "+data.id); - transport_player_to(target,"jail"); - } - else if(action=="ban") - { - appengine_call("ban_user",{name:data.id},function(result){ - socket.emit("game_log","Ban: "+(result&&result.result||"No result")); - }); - } - else if(action=="invincible") - { - player.s.invincible={ms:24*60*60*1000}; - resend(player,"u+cid"); - } - else if(action=="jump") - { - if(!target) return socket.emit("game_log","Player not found: "+data.id); - var spot=safe_xy_nearby(target.map,target.x-8,target.y-6); - if(spot) - { - pulled=player; - pulled.s.magiport={ms:400}; - pulled.s.magiport.x=spot.x; - pulled.s.magiport.y=spot.y; - pulled.s.magiport.f=target.name; - pulled.s.magiport.in=target.in; - pulled.s.magiport.map=target.map; - resend(pulled,"u+cid") - } - else return socket.emit("game_log","No safe spot near: "+data.id); - } - else if(action=="mjump") - { - target=get_monsters(data.monster)[0]; - if(!target) return socket.emit("game_log","Monster not found: "+data.monster); - var spot=safe_xy_nearby(target.map,target.x-8,target.y-6); - if(spot) - { - pulled=player; - pulled.s.magiport={ms:400}; - pulled.s.magiport.x=spot.x; - pulled.s.magiport.y=spot.y; - pulled.s.magiport.f=target.name; - pulled.s.magiport.in=target.in; - pulled.s.magiport.map=target.map; - resend(pulled,"u+cid") - } - else return socket.emit("game_log","No safe spot near: "+data.monster); - } - else if(action=="jump_list") - { - var ids=[]; - for(var id in players) - ids.push(players[id].name); - socket.emit("gm",{action:"jump_list",ids:ids}); - } - else if(action=="server_info") - { - var info=[]; - for(var id in players) - { - info.push({name:players[id].name,owner:players[id].owner,ip:get_ip(players[id])}); - } - //socket.emit("gm",{action:"server_info",info:info}); - } - }); - socket.on('monsterhunt',function(data){ - var player=players[socket.id]; if(!player) return; - if(simple_distance(G.maps.main.ref.monsterhunter,player,true)>B.sell_dist) return fail_response("distance"); - var hunted=[]; - for(var id in server.s) - { - if(server.s[id].type=="monsterhunt") - hunted.push(server.s[id].id); - } - if(player.s.monsterhunt && player.s.monsterhunt.c) return fail_response("monsterhunt_already"); - else if(player.s.monsterhunt) - { - delete server.s["monsterhunt_"+player.s.monsterhunt.id] - delete player.s.monsterhunt; - add_item(player,"monstertoken",{log:true,q:(gameplay=="hardcore"&&100||1)}); - resend(player,"u+cid+reopen"); - return success_response({completed:true}); - } - if(player.type=="merchant") return socket.emit("game_response","monsterhunt_merchant"); - var mmax=-1,name="goo",count=100,times=0,the_hp=0; - for(var id in instances) - { - if(instances[id].name!=id || !G.maps[id] || G.maps[id].irregular) continue; - for(var mid in instances[id].monsters) - { - var monster=instances[id].monsters[mid]; - if(monster.level>mmax && !in_arr(monster.type,hunted) && !monster.target) // added the target condition [21/07/23] - { - name=monster.type; - mmax=monster.level; - the_hp=monster.max_hp/1000.0; - } - } - } - for(var id in G.maps) - { - if(G.maps[id].irregular || !G.maps[id].monsters) continue; - G.maps[id].monsters.forEach(function(p){ - if(p.type==name) times+=p.count; - }); - } - // console.log(times); - count=max(1,min(500,parseInt(20*60*max(1,times)/the_hp/(G.monsters[name].respawn+0.25)))); - if(gameplay=="hardcore") count=max(1,parseInt(count/10)) - player.s.monsterhunt={sn:region+" "+server_name,id:name,c:count,ms:30*60*1000,dl:true}; - server.s["monsterhunt_"+name]={name:player.name,id:name,ms:20*60*1000,type:"monsterhunt"}; - player.hitchhikers.push(["game_response","monsterhunt_started"]); - resend(player,"u+cid"); - success_response({started:true}); - }); - socket.on('ccreport',function(){ - socket.emit('ccreport',{calls:socket.calls,climit:limits.calls,total:socket.total_calls}); - }); - socket.on('tracker',function(data){ - var player=players[socket.id]; if(!player) return; - if(!player.tracker) return; - var data={monsters:player.p.stats.monsters,monsters_diff:player.p.stats.monsters_diff,exchanges:player.p.stats.exchanges,maps:D.drops.maps,tables:{},max:player.max_stats}; // ,computer:false - function register_table(table) - { - if(table) - table.forEach(function(drop){ - if(drop[1]=="open" && !data.tables[drop[2]]) - { - data.tables[drop[2]]=D.drops[drop[2]]; - register_table(D.drops[drop[2]]); - } - }); - } - if(player.computer || 1) - { - // data.computer=true; - data.drops=D.drops.monsters; - } - else - { - data.drops={}; - for(var name in data.monsters) - if(data.monsters[name]>=100 && D.drops.monsters[name]) - data.drops[name]=D.drops.monsters[name]; - } - if(D.drops.maps.global) data.global=D.drops.maps.global,register_table(data.global); - if(D.drops.maps.global_static) data.global_static=D.drops.maps.global_static,register_table(data.global_static); - for(var name in data.drops) - register_table(data.drops[name]); - for(var name in data.maps) - register_table(data.maps[name]); - for(var name in G.items) - if(G.items[name].e && (player.computer || 1 || player.p.stats.exchanges && player.p.stats.exchanges[name] && player.p.stats.exchanges[name]>=100)) - { - if(G.items[name].upgrade || G.items[name].compound) - { - for(var i=0;i<13;i++) - if(D.drops[name+i]) - { - data.tables[name+i]=D.drops[name+i]; - register_table(data.tables[name+i]); - } - } - else if(D.drops[name]) - { - data.tables[name]=D.drops[name]; - register_table(data.tables[name]); - } - } - socket.emit('tracker',data) - }); - socket.on('set_home',function(data){ - var player=players[socket.id]; if(!player) return; - if(player.p.dt.last_homeset && hsince(player.p.dt.last_homeset)<36) return fail_response("sh_time",{hours:36-hsince(player.p.dt.last_homeset)}); - player.p.dt.last_homeset=new Date(); - player.p.home=region+server_name; - delete player.s.hopsickness; - success_response("home_set",{home:player.p.home}); - }); - socket.on('code',function(data){ - var player=players[socket.id]; if(!player) return; - if(data.run) players[socket.id].code=true; - else players[socket.id].code=false; - resend(players[socket.id],"u+cid"); - }); - socket.on('property',function(data){ - var player=players[socket.id]; if(!player) return; - if(data.typing) - { - if(!player.s.typing || player.s.typing.ms<3000) reduce_call_cost(); - player.s.typing={ms:4000}; - resend(player,"u+cid+nc"); - } - else - { - var original=player.afk; - if(player.afk=="bot" || player.afk=="code"); - else if(data.afk===true) - { - player.afk=true; - } - else if(data.afk===false) - { - if(player.afk!==undefined) player.afk=false; - } - if(original!==player.afk) - { - reduce_call_cost(); - resend(player,"u+cid+nc"); - } - } - }); - socket.on('cruise',function(speed){ - var player=players[socket.id]; if(!player) return; - player.cruise=parseInt(speed); - resend(player,"u+cid"); - success_response("cruise",{speed:player.cruise}); - }); - socket.on('test',function(data){ - if(is_sdk) console.log(data.test); - socket.emit('test',{date:new Date()}); - }); - socket.on('blocker',function(data){ - if(data.type=="pvp") - { - if(instances["arena"].allow) - { - socket.emit("blocker",{type:"pvp",allow:1}); - } - else - { - socket.emit("blocker",{type:"pvp"}); - } - } - }); - socket.on('mail_take_item',function(data){ - var player=players[socket.id]; if(!player) return; - if(mode.prevent_external) return socket.emit("game_response",{response:"not_in_this_server"}); - if(!player.esize) return socket.emit("game_response","inv_size"); - appengine_call("take_item_from_mail",{owner:player.owner,mid:data.id},function(result){ - var player=players[socket.id]; - if(result.failed) return socket.emit("game_response",{response:"mail_item_already_taken"}); - var item=JSON.parse(result.item); - add_item(player,item,{announce:false}); - resend(player,"reopen"); - socket.emit("game_response",{response:"mail_item_taken"}); - },function(){ - socket.emit("game_response",{response:"mail_take_item_failed"}); - }); - }); - socket.on('mail',function(data){ - var player=players[socket.id]; if(!player) return; - if(mode.prevent_external) return fail_response("not_in_this_server"); - var item=null,retries=1; - if(player.gold<48000) return fail_response("gold_not_enough"); - player.gold-=48000; - if(data.item && player.items[0] && player.items[0].name!="placeholder") - { - if(player.items[0].l) return fail_response("item_locked"); - if(player.items[0].b || player.items[0].v) return fail_response("item_blocked"); - if(player.gold<312000) return fail_response("gold_not_enough"); - player.gold-=312000; - item=JSON.stringify(player.items[0]); - player.items[0]=player.citems[0]=null; - retries=3; - } - appengine_call("send_mail",{fro:player.name,to:data.to,subject:data.subject||"",message:data.message||"",rid:randomStr(50),retries:retries,item:item},function(result){ - var player=players[socket.id]; - if(result.failed) - { - if(player) socket.emit("game_response",{response:"mail_failed",to:data.to,reason:result.reason,cevent:"mail_failed"}); - if(player && item && result.return && player.esize) - { - var r=JSON.parse(item); - add_item(player,r); - resend(player,"reopen"); - } - else - console.log("#M unsent mail, lost item: "+item); - return; - } - if(player) socket.emit("game_response",{response:"mail_sent",to:data.to,cevent:"mail_sent"}); - },function(){ - var player=players[socket.id]; - if(player) socket.emit("game_response",{response:"mail_failed",reason:"coms_failure"}); - if(item) console.log("#M unsent mail, lost item: "+item); - }); - resend(player,"reopen"); - success_response("mail_sending",{sucess:false,in_progress:true,received:"unknown"}); - }); - socket.on('leave',function(data){ - var player=players[socket.id]; if(!player) return; - if(!can_walk(player)) return fail_response("transport_failed"); - if(0 && player.s.block || player.targets>5 || !(player.map=="jail" || player.map=="cyberland" || instances[player.in].solo)) - return fail_response("cant_escape"); - transport_player_to(player,B.start_map); - success_response(); - }); - socket.on('transport',function(data){ - var player=players[socket.id],can_reach=false; if(!player) return; // this was missing, probably caused a production exception - should be added everywhere [04/09/16] - if(!can_walk(player) || player.map=="jail") return fail_response("transport_failed"); - var new_map=G.maps[data.to],s=data.s||0,the_door=null; - if(!new_map || !instances[data.to] || !instances[data.to].allow) return fail_response("cant_enter"); - if(0 && player.s.block || player.targets>5) return fail_response("cant_escape"); - - (G.maps[player.map].doors||[]).forEach(function(door){ - if(!can_reach && door[4]==data.to && s==(door[5]||0) && simple_distance({map:player.map,x:G.maps[player.map].spawns[door[6]][0],y:G.maps[player.map].spawns[door[6]][1]},player)5) return fail_response("cant_escape"); - // if(player.role!="gm" && !(data.place==player.map && player.map=="cgallery")) - // { - // if(data.place!="resort" && !G.maps[player.map].ref.transporter || simple_distance(G.maps[player.map].ref.transporter,player)>80) return socket.emit("game_response","transport_cant_reach"); - // if(data.place=="resort" && player.map!="resort") return socket.emit("game_response","transport_cant_reach"); - // } - server_log(data); - var name=randomStr(24); - if(data.place=="resort" && 0) - { - var name="resort_"+data.name; - instance=instances[name]||create_instance(name,"resort_map"); - transport_player_to(player,name); - } - else if(data.place=="duelland") - { - if(instances[data.name] && instances[data.name].map=="duelland") - { - transport_player_to(player,data.name); - instance_emit(data.name,"game_log",{message:player.name+" is spectating the duel",color:"gray"}); - } - else return fail_response("cant_enter"); - } - else if(data.place=="crypt" || data.place=="winter_instance") - { - var f="cave",ref=G.maps.cave.spawns[2],item="cryptkey"; - if(data.place=="winter_instance") f="winterland",ref=G.maps.winterland.spawns[5],item="frozenkey"; - if(simple_distance(player,{in:f,map:f,x:ref[0],y:ref[1]})>120) - return fail_response("transport_cant_reach"); - if(data.name && instances[data.name] && instances[data.name].map==data.place) - { - transport_player_to(player,data.name); - } - else - { - if(!consume_one_by_id(player,item)) - return fail_response("transport_cant_item"); - instance=create_instance(name,data.place); - transport_player_to(player,name); - } - resend(player,"u+cid+reopen"); - } - else if(data.place=="dungeon0" && player.role=="gm") - { - instance=create_instance(name,"dungeon0",{solo:player.id}); - transport_player_to(player,name); - } - else if(data.place=="cgallery" && player.role=="gm") - { - var point=null; - if(player.map=="cgallery") point=[player.x,player.y]; - instance=create_instance(name,"cgallery",{solo:player.id}); - transport_player_to(player,name,point); - var bodies=[],heads=[],hairs=[],wings=[],hats=[]; - for(var s in G.sprites) - { - var current=G.sprites[s],matrix=current.matrix; - if(current.skip || current.rskip) continue; - if(!in_arr(current.type,["body","head","hair","s_wings","hat"])) continue; - for(var i=0;i5) return fail_response("cant_escape"); - player.c.town={ms:min(player.c.town&&player.c.town.ms||5000,3000)}; - resend(player,"u+nc"); - success_response({success:false,in_progress:true}) - }); - socket.on('respawn',function(data){ - var player=players[socket.id]; if(!player || !player.rip) return fail_response("invalid"); - if(player.rip_time && ssince(player.rip_time)B.sell_dist) return fail_response("distance"); - // if(player.esize<=0) return socket.emit("game_response","inventory_full"); - var item=player.items[data.num]; - if(item && item.level && G.items[item.name].compound) - { - var cost=min(50000000,calculate_item_value(item)*10); - if(player.golde[0]) return; - add_item(player,e[1],{q:max(1,e[0]),p:item.p&&!G.titles[item.p].misc&&item.p}); - }); - resend(player,"reopen+nc+inv"); - success_response("dismantle",{name:item.name,cevent:true}); - }); - socket.on('craft',function(data){ - var player=players[socket.id],check=true,items=[],quantity={},place={},space=false,p={},locked=false; - if(!player || player.user) return fail_response("cant_in_bank"); - data.items.forEach(function(x){ - if(!player.items[x[1]]) check=false; - else if(player.items[x[1]].l || player.items[x[1]].b) check=false,locked=true; - else - { - var name=player.items[x[1]].name; - if(player.items[x[1]].level) name+="+"+player.items[x[1]].level; - items.push(name); - quantity[player.items[x[1]].name]=(player.items[x[1]].q||1); // (quantity[player.items[x[1]].name]||0)+ removed this part, seems like a bad initial addition [17/06/18] - place[player.items[x[1]].name]=x[1]; - if(player.items[x[1]].p && !G.titles[player.items[x[1]].p].misc) p[player.items[x[1]].p]=player.items[x[1]].p; - } - }); - if(locked) return fail_response("item_locked") - if(!check) return fail_response("no_item"); - //if(items.length<2) return socket.emit("game_response","craft_atleast2"); - items.sort(); - var key=items.join(","); - // console.log(key); - if(!D.craftmap[key]) return fail_response("craft_cant"); - var name=D.craftmap[key],enough=true; - if(!player.computer && !G.craft[name].quest && simple_distance(get_npc_coords('craftsman'),player)>B.sell_dist) return fail_response("distance"); - if(!player.computer && G.craft[name].quest && simple_distance(get_npc_coords(G.craft[name].quest),player)>B.sell_dist) return fail_response("distance"); - if(player.goldB.sell_dist) return fail_response("distance"); - if(!player.computer && def.quest && simple_distance(G.quests[def.quest],player)>B.sell_dist) return fail_response("distance"); - if(player.esize<=0 && !((item.q||1)==1)) return fail_response("inventory_full"); - if(def.e>1 && item.qB.sell_dist) return fail_response("distance"); - if(item.qB.sell_dist) return fail_response("distance"); - var item=player.items[data.num]; - if(!item) return fail_response("no_item"); - var def=G.items[item.name]; - if(in_arr(def.type,["uscroll","cscroll","pscroll","offering","tome"])) return fail_response("locksmith_cant"); - if(data.operation=="unlock") - { - if(!item.l) { resend(player,"reopen+nc"); return fail_response("locksmith_aunlocked","locksmith","already_unlocked"); } - if(item.l=="s") - { - if(player.gold<250000) return fail_response("gold_not_enough"); - player.gold-=250000; - item.ld=JSON.stringify(future_s(2*24*60*60)); - item.l="u"; - socket.emit("game_response","locksmith_unsealed"); - } - else if(item.l=="u") - { - var date=new Date(JSON.parse(item.ld)); - if(dateB.sell_dist) return socket.emit("game_response",{response:"distance",place:"compound",failed:true}); - var def=G.items[item0.name],scroll_def=G.items[scroll.name],offering_def=offering&&G.items[offering.name],grade=calculate_item_grade(def,item0); - if(grade==4) return socket.emit("game_response",{response:"max_level",level:item.level,place:"compound",failed:true}); - if(!(item0.name == item1.name && item1.name==item2.name && (item0.level||0)==(item1.level||0) && (item1.level||0)==(item2.level||0) )) return socket.emit("game_response","compound_mismatch"); - if(!def.compound) return socket.emit("game_response","compound_cant"); - if(scroll_def.type!="cscroll" || grade>scroll_def.grade) return socket.emit("game_response","compound_incompatible_scroll"); - if(data.items[0]==data.items[1] || data.items[1]==data.items[2] || data.items[0]==data.items[2]) return socket.emit("game_response",{response:"misc_fail",place:"compound",failed:true}); - if(item0.l || item1.l || item2.l) return socket.emit("game_response",{response:"item_locked",place:"compound",failed:true}); - - if(!data.calculate) consume_one(player,data.scroll_num); - - var new_level=(item0.level||0)+1,probability=1,oprobability=1,result=0,proc=0,grace=0,igrade=def.igrade,high=false,grace_bonus=0; - if(item0.level>=3) igrade=calculate_item_grade(def,{name:item0.name,level:item0.level-2}); - - delete player.p.c_item; delete player.p.c_itemx; delete player.p.c_roll; - - oprobability=probability=D.compounds[igrade][new_level]; - result=Math.random(); - server_log(result+" < "+probability); - - if(scroll_def.grade > grade) - { - probability=probability*1.1+0.001; - server_log("higher grade scroll "+result+" < "+probability); - if(!data.calculate) grace_bonus+=0.4; - high=scroll_def.grade-grade; - } - - if(offering) - { - var increase=0.5; - if(!data.calculate) consume_one(player,data.offering_num); - grace=0.027*((item0.grace||0)+(item1.grace||0)+(item2.grace||0)+0.5+player.p.ograce); - - if(offering_def.grade>grade+1) - probability=probability*1.64+grace*2,high=true,increase=3; - else if(offering_def.grade>grade) - probability=probability*1.48+grace,high=true,increase=1; - else if(offering_def.grade==grade) - probability=probability*1.36+min(30*0.027,grace); - else if(offering_def.grade==grade-1) - probability=probability*1.15+min(25*0.019,grace)/max(item0.level-2,1),increase=0.2; - else - probability=probability*1.08+min(15*0.015,grace)/max(item0.level-1,1),increase=0.1; - - if(!data.calculate) item0.grace=(item0.grace||0)+(item1.grace||0)+(item2.grace||0),grace_bonus+=increase; - server_log("offering "+result+" < "+probability); - } - else - { - grace=0.007*((item0.grace||0)+(item1.grace||0)+(item2.grace||0)+player.p.ograce); - probability=probability+min(25*0.007,grace)/max(item0.level-1,1); - if(!data.calculate) item0.grace=max(max((item0.grace||0),(item1.grace||0)),(item2.grace||0)); - } - - if(!data.calculate) item0.grace=item0.grace/6.4+grace_bonus; - - if(def.type=="booster") probability=0.9999999999999,proc=offering&&0.12; - else probability=min(probability,min(oprobability*(3+(high&&high*0.6||0)),oprobability+0.2+(high&&high*0.05||0))); - - if(gameplay=="test") result=0; - - server_log(result+" < "+probability+" grace: "+grace); - - if(data.calculate) return success_response("compound_chance",{calculate:true,chance:probability,item:cache_item(item0),scroll:scroll.name,offering:offering&&offering.name||undefined,grace:(item0.grace||0)+(item1.grace||0)+(item2.grace||0)}); - - player.p.c_roll=result; - var len=10000; - if(gameplay=="hardcore") len=1200; - if(player.s.massproduction) { len/=2; delete player.s.massproduction; ex="+u+cid"; } - if(player.s.massproductionpp) { len/=10; delete player.s.massproductionpp; ex="+u+cid"; } - player.q.compound={ms:len,len:len,num:data.items[0],nums:[]}; - player.items[data.items[0]]={"name":"placeholder",p:{chance:probability,name:item0.name,level:item0.level,scroll:scroll.name,offering:offering&&offering.name,nums:[]}}; - player.items[data.items[1]]=null; - player.items[data.items[2]]=null; - - if(result<=probability) - { - if(offering) player.p.ograce=0; - else player.p.ograce*=1-new_level*0.02; - item0.level=new_level; - if((item0.p||item1.p||item2.p)!="legacy") item0.p=(item0.p||item1.p||item2.p); - else { delete item0.p; } - player.p.c_item=item0; - if(item0.oo!=player.name) item0.o=player.name; - player.esize+=2; - if(parseInt(result*10000)==parseInt(probability*10000)) // && grade>=1 - add_achievement(player,"lucky"),add_item_property(item0,"lucky"); - if(item1.v||item2.v) item0.v=item1.v||item2.v; - if(item0.name=="ctristone" && item0.level==1 && Math.random()<0.012) item0.name="cdarktristone"; - if(def.type=="booster") - { - var activate=false; item0.extra=0; - while(offering && proc && Math.random()B.sell_dist) return socket.emit("game_response",{response:"distance",place:"upgrade",failed:true}); - if(!item) return socket.emit("game_response","upgrade_no_item"); - if(offering && !(G.items[offering.name] && G.items[offering.name].type=="offering" || G.items[offering.name] && G.items[offering.name].offering!==undefined && !(item.level>0))) return socket.emit("game_response","upgrade_invalid_offering"); - if(!scroll && !offering) return socket.emit("game_response","upgrade_no_scroll"); - if((item.level||0)!=data.clevel) return socket.emit("game_response","upgrade_mismatch"); - var item_def=G.items[item.name],scroll_def=scroll&&G.items[scroll.name],offering_def=offering&&G.items[offering.name],grade=calculate_item_grade(item_def,item); - if(!item_def.upgrade) return socket.emit("game_response","upgrade_cant"); - if(scroll && (!in_arr(scroll_def.type,["uscroll","pscroll"]) || (scroll_def.type=="uscroll" && !item_def.upgrade) || (scroll_def.type=="pscroll" && !item_def.stat) || grade>scroll_def.grade)) - { - if(grade==4 && scroll_def.type=="uscroll") return socket.emit("game_response",{response:"max_level",level:item.level,place:"upgrade",failed:true}); - return socket.emit("game_response","upgrade_incompatible_scroll"); - } - - var new_level=(item.level||0)+1,probability=1,oprobability=1,grace=0,high=false; - var ograde=calculate_item_grade(item_def,{name:item.name,level:0}),tmult=1; - if(ograde==1) tmult=1.5; else if(ograde==2) tmult=2; - - delete player.p.u_item; delete player.p.u_type; delete player.p.u_itemx; delete player.p.u_roll; delete player.p.u_fail; delete player.p.u_level; - - player.p.u_level=(item.level||0); - - if(!scroll) - { - if(G.items[offering.name] && G.items[offering.name].offering!==undefined) - { - var chance=0.16,ms=2000; - if(G.items[offering.name].offering>ograde || G.items[offering.name].offering==2 && calculate_item_value(item)<=20000000) chance=0.32; - chance*=[2.8,1.6,1][ograde]; - if(G.items[offering.name].offeringgrade && new_level<=10) - { - probability=probability*1.2+0.01; - high=true; - if(!data.calculate) item.grace=(item.grace||0)+0.4; - } - - if(offering) - { - var increase=0.4; - if(!data.calculate) consume_one(player,data.offering_num); - - if(offering_def.grade>grade+1) - probability=probability*1.7+grace*4,high=true,increase=3; - else if(offering_def.grade>grade) - probability=probability*1.5+grace*1.2,high=true,increase=1; - else if(offering_def.grade==grade) - probability=probability*1.4+grace; - else if(offering_def.grade==grade-1) - probability=probability*1.15+grace/3.2,increase=0.2; - else - probability=probability*1.08+grace/4,increase=0.1; - - if(!data.calculate) item.grace=(item.grace||0)+increase; // previously +1 [16/07/18] - } - else - { - grace=max(0,grace/4.8-0.4/((new_level-0.999)*(new_level-0.999))); - probability+=grace; // previously 12.0 // previously 9.0 [16/07/18] - } - - if(!data.calculate && Math.random()<0.025) // Bonus grace - item.grace=(item.grace||0)+1; - - if(data.item_num==player.p.item_num && Math.random()<0.6) // Added [29/10/17] - { - server_log("16 cheat"); - result=max(Math.random()/10000.0,result*0.975-0.012); - } - - if(high) probability=min(probability,min(oprobability+0.36,oprobability*3)); - else probability=min(probability,min(oprobability+0.24,oprobability*2)); - server_log(result+" < "+probability+" grace: "+grace); - - if(data.calculate) return success_response("upgrade_chance",{calculate:true,chance:probability,offering:offering&&offering.name||undefined,item:cache_item(item),grace:item.grace||0,scroll:scroll.name}); - - if(gameplay=="test") result=0; - // result=probability; - player.p.u_type="normal"; - player.p.u_roll=result; - var ms=500*new_level*Math.sqrt(new_level)*tmult; - if(player.s.massproduction) { ms/=2; delete player.s.massproduction; ex="+u+cid"; } - if(player.s.massproductionpp) { ms/=10; delete player.s.massproductionpp; ex="+u+cid"; } - player.q.upgrade={ms:ms,len:ms,num:data.item_num}; - if(gameplay=="hardcore") player.q.upgrade.ms=player.q.upgrade.len=500; - player.items[data.item_num]={"name":"placeholder",p:{chance:probability,name:item.name,level:item.level,scroll:scroll.name,offering:offering&&offering.name,nums:[]}}; - - //result=probability+EPS; - - if(result<=probability) - { - // console.log("here"); - player.p.ugrace[new_level]=S.ugrace[new_level]=0; - if(offering) player.p.ograce*=0.25; - else player.p.ograce*=1-new_level*0.005; - item.level=new_level; - if(item.oo!=player.name) item.o=player.name; - player.p.u_item=item; - if(parseInt(result*10000)==parseInt(probability*10000) && grade>=1) - { - add_item_property(item,"lucky"); - add_achievement(player,"lucky") - } - } - else - { - player.p.ugrace[new_level-1]+=1; S.ugrace[new_level-1]+=1 - player.p.ugrace[new_level]+=1; S.ugrace[new_level]+=1; - if(new_level>=8 && new_level<=15) - { - player.p.ugrace[new_level-1]+=1; S.ugrace[new_level-1]+=1; - player.p.ugrace[new_level-2]+=2+(offering&&1||0); S.ugrace[new_level-2]+=2; - player.p.ugrace[new_level-3]+=2+(offering&&2||0); S.ugrace[new_level-3]+=3+(offering&&1||0); - } - if(offering) player.p.ograce+=0.6; // previously 1 [16/07/18] - if(scroll_def.grade!=3.6) player.p.u_itemx=item; - else player.p.u_item=item,player.p.u_fail=true; - if(parseInt(result*10000)==parseInt(probability*10000)) - { - if(player.esize && grade>=1) add_item(player,"essenceofgreed"); - add_achievement(player,"unlucky"); - } - } - } - else if(scroll_def.type=="pscroll") - { - var needed=[1,10,100,1000,9999,9999,9999]; - if(scroll.q=player.items.length) return fail_response("invalid"); - var item=player.items[data.num]; - if(!item) return fail_response("no_item"); - if(item.b) return fail_response("item_blocked"); - if(item.name=="placeholder") return fail_response("item_placeholder"); - var def=G.items[item.name],to_update="reopen+u+cid",resolve={num:data.num},resolve_type="data"; - def.iname=item.name; //just for orb name checks [08/10/16] - if(!def) return fail_response("no_item"); - // if(is_sdk) server_log("Trying to equip "+JSON.stringify(data)); - - if(data.slot && get_trade_slots(player).includes(data.slot) && !data.consume) - { - if(item.acl || item.v) return fail_response("item_locked"); - var slot=data.slot,price=round(min(99999999999,max(parseInt(data.price)||1,1))),minutes=1; - data.q=max(1,parseInt(data.q)||1); - if((item.q||1)0) disappearing_text(socket,player,"+"+amount,{color:"hp",xy:1,s:"hp"}); - else disappearing_text(socket,player,amount,{color:"hp",xy:1,s:"hp"}); - } - if(p[0]=="mp") disappearing_text(socket,player,"+"+amount,{color:"mp",xy:1,s:"mp"}); - if(p[0]=="xp") disappearing_text(socket,player,"+1M",{color:"1mxp",xy:1,s:"xp",size:"large"}),xp=true,timeout_ui=120,timeout=0.1; - }); - if(def.debuff) - { - for(var c in player.s) - { - if(G.conditions[c] && G.conditions[c].debuff && (!G.conditions[c].persistent || c=="hopsickness")) - delete player.s[c]; - } - } - player.hp=min(player.hp,player.max_hp); player.mp=min(player.mp,player.max_mp); - if(def.cooldown) timeout=def.cooldown; - player.last.potion=future_ms(timeout); - if(!xp) to_update="u+cid+reopen+nc"; - socket.emit("eval",{code:"pot_timeout("+(timeout_ui||timeout)+")"}); - } - else if(!data.consume) - { - // console.log(data); - var slot=data.slot||def.type,existing; - var comp=can_equip_item(player,def,slot); - if(comp=="no") - { - resend(player,"u+cid+reopen"); // if you drop something on an invalid slot, it stays there for a bit otherwise [29/08/22] - return fail_response("cant_equip"); - } - slot=comp; - existing=player.slots[slot]; - player.slots[slot]=player.items[data.num]; player.cslots[slot]=cache_item(player.slots[slot]); - if(existing && existing.b) existing=null; - player.items[data.num]=existing,player.citems[data.num]=cache_item(existing); - player.s.penalty_cd={ms:min(((player.s.penalty_cd&&player.s.penalty_cd.ms)||0)+120,120000)}; - resolve.slot=slot; - } - else - { - return fail_response("cant_consume"); - } - - if(to_update) resend(player,to_update); - success_response(resolve_type,resolve); - }); - socket.on('misc_npc',function(data){ - var player=players[socket.id]; if(!player) return; - var npc=players[data.npc]; if(!npc || !npc.npc) return; - if(simple_distance(player,npc)>1000) return socket.emit("game_response","distance"); - if(data.npc==NPC_prefix+"Marven" && npc.misc==true) - { - server_log("Here!"); - } - }); - socket.on('unequip',function(data){ - var player=players[socket.id]; - if(!player || !data.slot || !player.slots[data.slot]) return fail_response("invalid"); - if(data.slot=="elixir") return fail_response("cant"); - var item=player.slots[data.slot],done=false; - if(in_arr(data.slot,trade_slots) && item.giveaway!==undefined) return fail_response("giveaway"); - // an oversight allowed .giveaway items to be equipped, so now they can be unequipped from regular slots [04/11/21] - if(player.esize<=0 && !item.b) return fail_response("no_space"); - player.slots[data.slot]=null; player.cslots[data.slot]=null; - player.c={}; - if(!item.b) add_item(player,item,{announce:false}); - resend(player,"reopen+u+cid"); - success_response("data"); - }); - socket.on('secondhands',function(data){ - var player=players[socket.id]; if(!player) return; - if(simple_distance(G.maps.main.ref.secondhands,player)>500) return socket.emit("game_response","distance"); - socket.emit('secondhands',csold); - }); - socket.on('lostandfound',function(data){ - var player=players[socket.id]; if(!player) return; - if(data=="info") return socket.emit("game_response",{response:"lostandfound_info",gold:S.gold}); - if(!player.donation) return socket.emit("game_response","lostandfound_donate"); - if(simple_distance(G.maps.woffice.ref.lostandfound,player)>500) return socket.emit("game_response","distance"); - socket.emit('lostandfound',cfound); - }) - socket.on('split',function(data){ - var player=players[socket.id],num=data.num,item=player.items[data.num],quantity=min(max(parseInt(data.quantity)||0,1),item&&item.q||1); - if(!item) return fail_response("no_item"); - if(!G.items[item.name].s) return fail_response("invalid"); - quantity=min(quantity,G.items[item.name].s||1); - if(!player.esize) return fail_response("cant_space"); - if(item.q==quantity) return success_response(); - if(item.name=="placeholder") return fail_response("item_placeholder"); - if(item.l) return fail_response("item_locked"); - if(item.b) return fail_response("item_blocked"); - - consume(player,data.num,quantity); - - var new_item=cache_item(item); - if(item.q) new_item.q=quantity; - - for(var i=0;iplayer.gold) return socket.emit("game_response","not_enough_gold"); - var shells=Math.floor(gold/G.multipliers.shells_to_gold); - player.gold-=gold; - socket.emit("game_log","Gave "+to_pretty_num(gold)+" gold"); - appengine_call("bill_user",{auth:player.auth,amount:-shells,reason:"buy_shells",name:player.name},function(result){ - server_log("buy_with_cash: "+JSON.stringify(result)); - if(result.failed || !result.done) { socket.emit("game_log","Purchase failed"); return; } - player.cash=result.cash; - socket.emit("game_log","Received "+to_pretty_num(shells)+" shells"); - - resend(player,"reopen+nc"); - }); - resend(player,"reopen+nc"); - }); - socket.on('buy_with_cash',function(data){ - var player=players[socket.id],name=data.name,def=G.items[data.name],quantity=min(max(parseInt(data.quantity)||0,1),def&&def.s||9999); - if(!player || player.user || gameplay=="hardcore" || gameplay=="test") return fail_response("cant_in_bank"); - var cost=def.cash*quantity; - if(!def.cash || def.ignore) return fail_response("invalid"); - if(def.p2w) return fail_response("invalid"); - if(!def.s) quantity=1; - if(!can_add_item(player,create_new_item(name,quantity))) return fail_response("no_space"); - appengine_call("bill_user",{auth:player.auth,amount:cost,reason:data.name,name:player.name},function(result){ - server_log("buy_with_cash: "+JSON.stringify(result)); - if(result.failed || !result.done) { socket.emit("game_log","Purchase failed"); return; } - player.cash=result.cash; - var new_item=create_new_item(name,quantity),done=false; - add_item(player,new_item,{announce:false}); - socket.emit("game_log","Spent "+to_pretty_num(cost)+" shells"); - - resend(player,"reopen+nc+inv"); - }); - success_response({success:false,in_progress:true}); - }); - socket.on('sbuy',function(data){ - var player=players[socket.id],done=false,npc=G.maps.main.ref.secondhands,c=S.sold,cc=csold,e="+$p",ev="secondhands",mult=2,src="scnd"; - if(data.f) npc=G.maps.woffice.ref.lostandfound,c=S.found,cc=cfound,e="+$f",ev="lostandfound",mult=4,src="lost"; - if(!player || player.user) return game_response("cant_in_bank"); - if(player.s.hopsickness && ev=="lostandfound") return fail_response("cant_when_sick",{goblin:true}); - if(simple_distance(npc,player)>500) return socket.emit("game_response","distance"); - for(var i=0;iplayer.gold) return socket.emit("game_response","buy_cost"); - player.gold-=gold; - item.src=src; - add_item(player,c[i],{announce:false}); - c.splice(i,1); - cc.splice(i,1); - socket.emit("game_log","Spent "+to_pretty_num(gold)+" gold"); - resend(player,"reopen+nc+inv"); - socket.emit(ev,cc); - done=true; - xy_emit(npc,"ui",{type:e,id:npc.id,name:player.name,item:cache_item(item,1)}); - break; - } - } - if(!done) return socket.emit("game_log","Item gone"); - }); - socket.on('buy',function(data){ - var player=players[socket.id],can_reach=false; - if(!player || player.user) return fail_response("cant_in_bank"); - var name=data.name,quantity=min(max(parseInt(data.quantity)||0,1),G.items[name]&&G.items[name].s||9999),cost=0,added=false,done=false; - if(!can_buy[name] && gameplay!="test") return fail_response("buy_cant_npc"); - if(!can_add_item(player,create_new_item(name,quantity))) return fail_response("buy_cant_space"); - (G.maps[player.map].items[name]||[]).forEach(function(l){ - if(simple_distance(player,l)B.dist || receiver.map!=player.map)) return fail_response("distance"); - if(data.num!==undefined) - { - data.num=max(0,parseInt(data.num)||0); - var item=player.items[data.num]; - if(!item) return fail_response("send_no_item"); - if(item.name=="placeholder") return fail_response("item_placeholder"); - if(item.l || item.acl && receiver.owner!=player.owner) return fail_response("item_locked"); - if(item.b) return fail_response("item_blocked"); - data.q=min((item.q||1),max(1,parseInt(data.q||1)||1)); - if(!data.q) return fail_response("no_item"); - if(!can_add_item(receiver,create_new_item(item.name,data.q))) return fail_response("send_no_space"); - if((item.q||1)==data.q) player.items[data.num]=player.citems[data.num]=null,player.esize++; - else - { - player.items[data.num].q-=data.q; - player.citems[data.num]=cache_item(player.items[data.num]); - } - - if(item.q) - { - s_item=create_new_item(item.name,data.q); - if(item.v) s_item.v=item.v; - if(item.data) s_item.data=item.data; - num=add_item(receiver,s_item,{announce:false}); - } - else - { - s_item=item; - num=add_item(receiver,item,{announce:false}); - } - - if(receiver.owner!=player.owner) item.src="snd"; - - - add_to_history(player,{"name":"item",to:receiver.name,item:item.name,q:data.q,level:item.level}); - add_to_history(receiver,{"name":"item",from:player.name,item:item.name,q:data.q,level:item.level}); - - xy_emit(player,"ui",{type:"item_sent",receiver:receiver.name,sender:player.name,item:cache_item(s_item,null,{q:data.q}),num:num,fnum:data.num,event:true}); - - resend(player,"reopen+nc+inv"); resend(receiver,"reopen+nc+inv"); - receiver.socket.emit("game_response",{response:"item_received",name:player.name,item:item.name,q:data.q,num:num,cevent:true}); - player.socket.emit("game_response",{response:"item_sent",name:receiver.name,item:item.name,q:data.q,num:data.num,cevent:true,place:"send"}); - } - else if(data.gold!==undefined) - { - data.gold=min(player.gold,max(1,parseInt(data.gold||1)||1))||player.gold; - if(!data.gold) return fail_response("invalid"); - player.gold-=data.gold; - if(!is_same(player,receiver) && data.gold!=1) data.gold=parseInt(data.gold*0.975); - receiver.gold+=data.gold; - - add_to_history(player,{"name":"gold",to:receiver.name,amount:data.gold}); - add_to_history(receiver,{"name":"gold",from:player.name,amount:data.gold}); - - xy_emit(player,"ui",{type:"gold_sent",receiver:receiver.name,sender:player.name,gold:data.gold,event:true}); - - resend(player,"reopen+nc+inv"); resend(receiver,"reopen+nc+inv"); - receiver.socket.emit("game_response",{response:"gold_received",name:player.name,gold:data.gold,cevent:true}); - player.socket.emit("game_response",{response:"gold_sent",name:receiver.name,gold:data.gold,cevent:true,place:"send"}); - } - else if(data.cx!==undefined) - { - var cxl=map_cx(player),count=all_cx(player,1); - if(!(cxl[data.cx] && player.p.acx[cxl[data.cx]] && count[data.cx]>0)) return fail_response("send_no_cx"); - // count[cxl[data.cx]] before [28/01/22] - if(receiver.owner!=player.owner) return fail_response("send_diff_owner"); - player.p.acx[cxl[data.cx]]-=1; - if(!player.p.acx[cxl[data.cx]]) delete player.p.acx[cxl[data.cx]]; - receiver.p.acx[cxl[data.cx]]=(receiver.p.acx[cxl[data.cx]]||0)+1; - - xy_emit(player,"ui",{type:"cx_sent",receiver:receiver.name,sender:player.name,cx:data.cx,event:true}); - - receiver.socket.emit("game_response",{response:"cx_received",name:player.name,cx:data.cx,acx:receiver.p.acx,cevent:true}); - player.socket.emit("game_response",{response:"cx_sent",name:receiver.name,cx:data.cx,acx:player.p.acx,cevent:true,place:"send"}); - } - }); - socket.on('donate',function(data){ - var player=players[socket.id],XPX=3.2; - if(!player || player.user) return game_response("cant_in_bank"); - var gold=max(1,min(parseInt(data.gold)||0,1000000000)); - if(gold>player.gold) return socket.emit("game_response","gold_not_enough"); - if(gold>=1000000) response="thx",player.donation=true; - else if(gold<100000) response="low"; - else add_item(player,"gum"),response="gum"; - if(S.gold<500000000) XPX=4.8; - else if(S.gold<=1000000000) XPX=4; - player.gold-=gold; - S.gold+=gold; - if(gold>=5000000) lstack(S.logs.donate,{name:player.name,gold:gold,xp:XPX}); - if(player.type=="merchant") - player.xp+=parseInt(gold*XPX); - resend(player,"reopen"); - socket.emit("game_response",{response:"donate_"+response,gold:gold,xprate:XPX}); - }); - socket.on('destroy',function(data){ - var player=players[socket.id],add="+nc+inv"; - data.num=max(0,parseInt(data.num)||0); - if(!player.items[data.num]) return fail_response("no_item"); - var item=player.items[data.num],name=player.items[data.num].name; - data.q=min(max(parseInt(data.q)||0,1),item&&item.q||1); - if(item.name=="placeholder") return fail_response("item_placeholder"); - if(item.l) return fail_response("item_locked"); - if(item.b) return fail_response("item_blocked"); - if(item.level!=13) - { - consume(player,data.num,data.q); - //player.items[data.num]=player.citems[data.num]=null; - } - if(data.statue) - { - if(item.name=="shadowstone") - { - add="+u+cid"; - player.s.invis={ms:99999}; - } - if(G.items[item.name].upgrade && Math.random()<1.0/(gameplay=="hardcore"&&10000||1000000)) - { - add="+u+cid"; - item.level=13; - player.items[data.num]=item; - player.citems[data.num]=cache_item(player.items[data.num]); - } - xy_emit(G.maps.spookytown.ref.poof,"upgrade",{type:"poof",success:1}); - } - resend(player,"reopen"+add); - success_response("destroyed",{name:name,place:"destroy",cevent:"destroy"}); - }); - socket.on('join_giveaway',function(data){ - var player=players[socket.id],seller=players[id_to_id[data.id]],num; - if(!player || player.user) return fail_response("cant_in_bank"); - if(!in_arr(data.slot,trade_slots)) return fail_response("invalid"); - if(!seller || seller.npc || is_invis(seller)) return fail_response("seller_gone"); - if(seller.user) return fail_response("cant_in_bank") - if(distance(seller,player,true)>B.dist || seller.map!=player.map) return fail_response("distance"); - if(seller.id==player.id) return fail_response("hmm"); - var item=seller.slots[data.slot]; - if(!item || (data.rid && item.rid!=data.rid)) return fail_response("item_gone"); - if(!item.giveaway) return fail_response("sneaky"); - if(!player.auth_id) return fail_response("need_auth"); - //if(item.list.includes(player.name)) return socket.emit("game_log","Already joined!"); - //if(player.type!="merchant") return socket.emit("game_log","Only merchants can join giveaways!"); - - //item.list.push(player.name); - - item.registry=item.registry||{}; - item.registry[player.auth_id]=player.name; - item.list=Object.values(item.registry); - - socket.emit("game_log","Joined the giveaway!"); - seller.socket.emit("game_response",{response:"giveaway_join",name:player.name,slot:data.slot,cevent:true}); - - resend(player,"reopen"); resend(seller,"reopen+u+cid"); - success_response({}); - }); - socket.on('trade_wishlist',function(data){ - var player=players[socket.id]; if(!player) return; - data.q=min(9999,max(1,parseInt(data.q||1)||1)); - if(!in_arr(data.slot,trade_slots) || !G.items[data.name] || data.name=="placeholder") return fail_response("invalid"); - if(player.slots[data.slot] && !player.slots[data.slot].b) return fail_response("slot_occuppied"); - if(!get_trade_slots(player).includes(data.slot)) return fail_response("invalid"); - var item={name:data.name,rid:randomStr(4),price:round(min(99999999999,max(parseInt(data.price)||1,1))),b:true}; - if(G.items[data.name].upgrade || G.items[data.name].compound) item.q=min(99,data.q),item.level=round(min(12,max(parseInt(data.level)||0,0))); - else item.q=data.q; - player.slots[data.slot]=item; player.cslots[data.slot]=cache_item(player.slots[data.slot],true); - resend(player,"reopen+u+cid+nc"); - success_response({}) - }); - socket.on('trade_sell',function(data){ - // if(!is_sdk) return; - var player=players[socket.id],buyer=players[id_to_id[data.id]],num,actual=null,num=null; - data.q=max(1,parseInt(data.q||1)||1); - if(!player || player.user) return fail_response("cant_in_bank"); - if(!in_arr(data.slot,trade_slots)) return fail_response("invalid"); - if(!buyer || buyer.npc || is_invis(buyer)) return fail_response("buyer_gone"); - if(player.user) return fail_response("cant_in_bank"); - if(distance(buyer,player,true)>B.dist) return fail_response("distance"); - if(buyer.id==player.id) return fail_response("hmm"); - var item=buyer.slots[data.slot]; - if(!item || (data.rid && item.rid!=data.rid)) return fail_response("item_gone"); - if(item.name=="placeholder") return fail_response("item_placeholder"); - if(!item.b && !B.rbugs) return fail_response("sneaky"); - if((item.q||1)buyer.gold) return fail_response("buyer_gold"); - for(var i=0;i=data.q && player.items[i].level==item.level && !player.items[i].l) - { - actual=player.items[i]; num=i; break; - } - } - if(!actual) return fail_response("no_item"); - if(actual.b) return fail_response("item_blocked"); - if(!can_add_item(buyer,create_new_item(item.name,data.q))) return fail_response("trade_bspace"); - var price=item.price*data.q; - player.gold+=round(price*(1-player.tax)); add_to_trade_history(player,"sell",buyer.name,cache_item(actual,true,{q:data.q}),price); - buyer.gold-=price; add_to_trade_history(buyer,"buy",player.name,cache_item(actual,true,{q:data.q}),price); - S.gold+=price-round(price*(1-player.tax)); - - if((item.q||1)==data.q) buyer.slots[data.slot]=buyer.cslots[data.slot]=null; - else - { - buyer.slots[data.slot].q-=data.q; - buyer.cslots[data.slot]=cache_item(buyer.slots[data.slot],true); - } - - // up to here - - if((player.items[i].q||1)==data.q) player.items[num]=player.citems[num]=null; - else player.items[num].q-=data.q,player.citems[num]=cache_item(player.items[num]); - - if(G.items[item.name].s) bnum=add_item(buyer,create_new_item(item.name,data.q),{announce:false}); - else bnum=add_item(buyer,actual,{announce:false}); - - if(player.type=="merchant") merchant_xp_logic(player,buyer,price,price-round(price*(1-player.tax))); - if(buyer.type=="merchant") merchant_xp_logic(buyer,player,price,price-round(price*(1-buyer.tax))); - - socket.emit("game_log","Sales tax "+to_pretty_num(price-round(price*(1-player.tax)))+" gold ["+(player.tax*100)+"%]"); - socket.emit("game_log","Received "+to_pretty_num(round(price*(1-player.tax)))+" gold"); - buyer.socket.emit("game_log","Spent "+to_pretty_num(price)+" gold"); - - xy_emit(buyer,"ui",{type:"+$$",seller:player.name,buyer:buyer.name,item:cache_item(actual,true,{q:data.q,price:item.price}),slot:data.slot,num:bnum,snum:num}); - - resend(player,"reopen"); resend(buyer,"reopen+u+cid"); - success_response({}); - }); - socket.on('trade_buy',function(data){ - var player=players[socket.id],seller=players[id_to_id[data.id]],num; - data.q=max(1,parseInt(data.q||1)||1); - if(!player || player.user) return fail_response("cant_in_bank"); - if(!in_arr(data.slot,trade_slots)) return fail_response("invalid"); - if(!seller || seller.npc || is_invis(seller)) return fail_response("seller_gone"); - if(seller.user) return fail_response("cant_in_bank"); - if(distance(seller,player,true)>B.dist || seller.map!=player.map) return fail_response("distance"); - if(seller.id==player.id) return fail_response("hmm"); - var item=seller.slots[data.slot]; - if(!item || (data.rid && item.rid!=data.rid)) return fail_response("item_gone"); - if(item.name=="placeholder") return fail_response("item_placeholder"); - if(item.b || item.giveaway) return fail_response("sneaky"); - if(item.price*data.q>player.gold) return fail_response("gold_not_enough"); - if((item.q||1)=player.items.length) player.items.push(null); [22/11/16] - if(player.items[a] && player.items[a].name=="placeholder") return fail_response("item_placeholder"); - if(player.items[b] && player.items[b].name=="placeholder") return fail_response("item_placeholder"); - if(can_stack(player.items[a],player.items[b])) - { - player.items[data.a].q=(player.items[a].q||1)+(player.items[b].q||1); - player.items[data.b]=null; - player.esize++; - } - else - { - a_item=player.items[a]; - player.items[a]=player.items[b]; - player.items[b]=a_item; - } - player.citems[data.a]=cache_item(player.items[data.a]); player.citems[data.b]=cache_item(player.items[data.b]); - resend(player,"reopen+nc+inv"); - return success_response("data"); - }); - socket.on('bank',function(data){ - var player=players[socket.id]; if(!player) return; - if(!player.user || player.mounting || player.unmounting) return fail_response("bank_unavailable"); - var success={}; - if(data.operation=="withdraw") - { - var amount=max(0,min(parseInt(data.amount)||0,player.user.gold)); - player.user.gold-=amount; - player.gold+=amount; - success={response:"bank_withdraw",gold:amount,cevent:true}; - } - if(data.operation=="deposit") - { - var amount=max(0,min(parseInt(data.amount)||0,player.gold)); - player.user.gold+=amount; - player.gold-=amount; - success={response:"bank_store",gold:amount,cevent:true}; - } - if(data.operation=="unlock") - { - if(!bank_packs[data.pack]) return fail_response("invalid"); - var gold=bank_packs[data.pack][1],shells=bank_packs[data.pack][2]; - if(!gold) return fail_response("gold_not_enough"); - if(player.user[data.pack]) return fail_response("invalid"); - if(data.gold) - { - if(player.goldplayer.str*3) fail_response("too_far"); - consume_one(player,data.num); - if(item.name=="confetti") - { - player.thrilling=future_s(20); - xy_emit({map:player.map,in:player.in,x:x,y:y},"eval","confetti_shower({in:'"+player.in+"',map:'"+player.map+"',real_x:"+x+",real_y:"+y+"})"); - } - if(item.name=="firecrackers") - { - player.thrilling=future_s(200); - xy_emit({map:player.map,in:player.in,x:x,y:y},"eval","firecrackers({in:'"+player.in+"',map:'"+player.map+"',real_x:"+x+",real_y:"+y+"})"); - for(var id in instances[player.in].monsters) - { - var monster=instances[player.in].monsters[id]; - if(monster.target && distance({map:player.map,in:player.in,x:x,y:y},monster)<64) stop_pursuit(monster,{force:true,cause:"firecrackers"}); - } - } - if(item.name=="whiteegg") - { - xy_emit({map:player.map,in:player.in,x:x,y:y},"eval","egg_splash("+x+","+y+")"); - for(var id in instances[player.in].monsters) - { - var monster=instances[player.in].monsters[id]; - if(!monster.target && distance({map:player.map,in:player.in,x:x,y:y},monster)<32) - target_player(monster,player); - } - } - if(item.name=="smoke") - { - xy_emit({map:player.map,in:player.in,x:x,y:y},"eval","assassin_smoke("+x+","+y+");"); - } - resend(player,"reopen+nc+inv"); - success_response({}); - } - else fail_response("no_item"); - }); - socket.on('poke',function(data){ - var player=players[socket.id],level=1; - if(!player || !player.slots.gloves || player.slots.gloves.name!="poker") return; - if(player.pokes>=50) return socket.emit("game_log","You are out of pokes!"); - player.pokes=(player.pokes||0)+1; - if(player.slots.gloves.level>=10) level=4; - else if(player.slots.gloves.level==9) level=3; - else if(player.slots.gloves.level==8) level=2; - xy_emit(player,"poke",{name:data.name,level:level,who:player.name}); - }); - socket.on('merge',function(data){ - var player=players[socket.id]; if(!player) return; - var container=player.slots[data.container]; - var pet=player.slots[data.pet]; - if(!container || container.type!="container" || !pet || pet.type!="container") return socket.emit("game_response","merge_mismatch"); - if(G.items[container.name].grade=8 && (player.type=="mage" || player.type=="priest")) player.tskin="snow_angel"; - else { return socket.emit("game_response","nothing"); } - } - else if(item.name=="tristone") - { - var on=true; - if(player.tskin || player.tactivations==100) player.tskin="",on=false; - else if(item.level<=1 && deduct_gender(player)=="female") player.tskin=random_one(["tf_green","tf_pink"]); - else if(item.level==2 && deduct_gender(player)=="female") player.tskin=random_one(["tf_blue","tf_purple"]); - else if(deduct_gender(player)=="female") player.tskin="tf_orange"; - else if(item.level<=1) player.tskin=random_one(["tm_gray","tm_brown","tm_white"]); - else if(item.level==2) player.tskin=random_one(["tm_green","tm_yellow","tm_purple"]); - else player.tskin=random_one(["tm_blue","tm_red"]); - player.tactivations=(player.tactivations||0)+1; - } - else if(item.name=="darktristone") - { - var on=true; - if(player.tskin || player.tactivations==100) player.tskin="",on=false; - else if(item.level==4 && deduct_gender(player)=="female") player.tskin="mf_blue"; - else if(deduct_gender(player)=="female") player.tskin="mf_yellow"; - else if(item.level==4) player.tskin="mm_blue"; - else player.tskin="mm_yellow"; - player.tactivations=(player.tactivations||0)+1; - } - else return; - resend(player,"reopen+nc+inv+u+cid"); - } - else - { - var item=player.items[data.num]; - if(item) - { - if(item.name=="frozenstone") - { - consume_one(player,data.num); - } - if(item.name=="bkey") - { - if(!player.user) return socket.emit("game_response","only_in_bank"); - if(player.user.unlocked && player.user.unlocked.bank_b) return socket.emit("game_response","already_unlocked"); - consume_one(player,data.num); - player.user.unlocked=player.user.unlocked||{}; - player.user.unlocked.bank_b=new Date(); - player.user.items8=player.user.items8||[]; - socket.emit("game_response","door_unlocked"); - } - if(item.name=="ukey") - { - if(!player.user) return socket.emit("game_response","only_in_bank"); - if(player.user.unlocked && player.user.unlocked.bank_u) return socket.emit("game_response","already_unlocked"); - consume_one(player,data.num); - player.user.unlocked=player.user.unlocked||{}; - player.user.unlocked.bank_u=new Date(); - player.user.items24=player.user.items24||[]; - socket.emit("game_response","door_unlocked"); - } - if(item.name=="dkey") - { - if(!player.user) return socket.emit("game_response","only_in_bank"); - var found=false; - for(var i=0;i<48;i++) - { - var pack="items"+i; - if(!bank_packs[pack] || player.user[pack]) continue; - found=true; - player.user[pack]=[]; player.cuser[pack]=[]; - break; - } - if(!found) return; - consume_one(player,data.num); - socket.emit("game_response","bank_pack_unlocked"); - } - } - resend(player,"reopen+nc+inv"); - } - }); - socket.on('booster',function(data){ - var player=players[socket.id],item=player.items[data.num]; if(!player) return; - server_log("booster "+data.num+" "+data.action); - if(!item || !in_arr(item.name,booster_items)) return fail_response("invalid"); - if(data.action=="activate" && !item.expires) - { - item.expires=new Date(); - item.expires.setDate(item.expires.getDate()+30+(item.level||0)*2); - //item.expires.setMinutes(item.expires.getMinutes()+2); - } - else if(data.action=="shift") - { - if(!in_arr(data.to,booster_items)) return fail_response("invalid"); - player.xpm=player.goldm=player.luckm=1; - item.name=data.to; - player.s.penalty_cd={ms:min(((player.s.penalty_cd&&player.s.penalty_cd.ms)||0)+240,120000)}; - } - // if(item.expires) item.p="legacy"; - player.citems[data.num]=cache_item(player.items[data.num]); - resend(player,"reopen"); - success_response({name:player.items[data.num].name}); - }); - socket.on('convert',function(data){ - // There are a lot of routines that could give birth to an endgame bug - // This routine ended up being the one - // "stone" boosters were originally 1200 shells - // when they were discontinued, I let players exchange them for 3600 shells instead - // turns out 'buy_with_cash' didn't check whether an item has "ignore" or not - // so one could buy the stones for 1200 shells and sell them for 3600 - // unlocking infinite gold and shells [17/01/18] - var player=players[socket.id],item=player.items[data.num],amount=3600; - server_log("stone "+data.num+" "+data.action); - if(!item || !in_arr(item.name,["stoneofxp","stoneofgold","stoneofluck"])) return; - if(item.expires) - { - amount=round(600-hsince(item.expires)*100/24.0); - } - add_shells(player,amount,"convert"); - player.items[data.num]=null; - player.citems[data.num]=cache_item(player.items[data.num]); - resend(player,"reopen+cid"); - }); - socket.on('emotion',function(data){ - var player=players[socket.id]; if(!player) return; - if(!data.name) data.name=random_one(Object.keys(player.p.emx)); - if(player.last.emotion && mssince(player.last.emotion)<2000) - return socket.emit("game_response","emotion_cooldown"); - player.last.emotion=new Date(); - if(!G.emotions[data.name] || !player.p.emx[data.name]) - return socket.emit("game_response","emotion_cant"); - xy_emit(player,"emotion",{name:data.name,player:player.name}); - }); - socket.on('skill',function(data){ - var player=players[socket.id]; if(!player) return; - var target=null,cool=true,resolve={response:"data",place:data.name,success:true},reject=null; - player.first=true; G.skills.attack.cooldown=player.attack_ms; - server_log("skill "+JSON.stringify(data)); - if(is_disabled(player)) - return fail_response("disabled",data.name); - if((player.tskin=="konami" || is_silenced(player)) && data.name!="attack") - return socket.emit("game_response",{response:"skill_cant_incapacitated",place:data.name,failed:true}); - if(!G.skills[data.name]) return socket.emit("game_response",{response:"no_skill",place:data.name,failed:true}); - if(G.skills[data.name].mp && player.mpB.max_vision)) - return socket.emit("disappear",{id:data.id,place:data.name,reason:"not_there"}); - } - if(G.skills[data.name].requirements) - { - for(var requirement in G.skills[data.name].requirements) - { - if(!player[requirement] || player[requirement]G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - - consume_mp(player,G.skills[data.name].mp); - player.c[data.name]={ms:G.skills[data.name].duration_min+Math.random()*(G.skills[data.name].duration_max-G.skills[data.name].duration_min),target:target.name}; - player.to_resend="u+cid"; - resolve={response:"data",place:data.name,success:false,in_progress:true} - } - if(data.name=="fishing" || data.name=="mining") - { - var direction=0,the_zone=null; - consume_mp(player,G.skills[data.name].mp); - (G.maps[player.map].zones||[]).forEach(function(zone){ - if(zone.type!=data.name || the_zone) return; - [[0,-24,3],[-24,0,1],[24,0,2],[0,24,0]].forEach(function(m){ - if(is_point_inside([player.x+m[0],player.y+m[1]],zone.polygon)) - { - the_zone=zone; - direction=m[2]; - } - }); - }); - if(!the_zone || player.moving) - { - xy_emit(player,"ui",{type:data.name+"_fail",name:player.name}); - reject={response:"data",place:data.name}; - } - else - { - xy_emit(player,"ui",{type:data.name+"_start",name:player.name,direction:direction}); - player.c[data.name]={ms:G.skills[data.name].duration_min+Math.random()*(G.skills[data.name].duration_max-G.skills[data.name].duration_min),drop:the_zone.drop}; - } - resolve={response:"data",place:data.name,success:false,in_progress:true} - player.to_resend="u+cid"; - } - if(data.name=="light") - { - consume_mp(player,G.skills[data.name].mp); - xy_emit(player,"light",{name:player.name}); - player.to_resend="u+cid"; - } - if(data.name=="charge") - { - player.s.charging={ms:G.skills.charge.duration}; - player.to_resend="u+cid"; - } - if(data.name=="dash") - { - //console.log(data); - consume_mp(player,G.skills[data.name].mp); - var x=parseFloat(data.x)||0,y=parseFloat(data.y)||0,point=true; - if(point_distance(player.x,player.y,x,y)>50) - { - point=false; - player.socket.emit("game_response","dash_failed"); - reject={response:"data",place:data.name}; - } - if(point) - { - var spot=safe_xy_nearby(player.map,x,y); - if(!spot) spot=safe_xy_nearby(player.map,player.x+(x-player.x)*0.8,player.y+(y-player.y)*0.8); - if(!spot) spot=safe_xy_nearby(player.map,player.x+(x-player.x)*0.6,player.y+(y-player.y)*0.6); - if(!spot || point_distance(player.x,player.y,spot.x,spot.y)<10) - { - point=false; - player.socket.emit("game_response","dash_failed"); - reject={response:"data",place:data.name}; - } - if(point) - { - //console.log([player.x,player.y,spot.x,spot.y]); - player.s.dash={ms:1000}; player.speed=500; - player.going_x=spot.x; - player.going_y=spot.y; - start_moving_element(player); - player.to_resend="u+cid"; - player.socket.emit("eval",{code:"ui_move("+spot.x+","+spot.y+")"}); - } - } - } - if(data.name=="hardshell" || data.name=="power" || data.name=="xpower") - { - consume_mp(player,G.skills[data.name].mp); - player.s[G.skills[data.name].condition]={ms:G.skills[data.name].duration||G.conditions[G.skills[data.name].condition].duration}; - player.to_resend="u+cid"; - } - if(data.name=="mshield") - { - consume_mp(player,G.skills[data.name].mp); - if(player.s[G.skills[data.name].condition]) delete player.s[G.skills[data.name].condition]; - else player.s[G.skills[data.name].condition]={ms:999999999}; - player.to_resend="u+cid"; - } - if(data.name=="mcourage" || data.name=="mfrenzy" || data.name=="massproduction" || data.name=="massproductionpp") - { - consume_mp(player,G.skills[data.name].mp); - player.s[G.skills[data.name].condition]={ms:G.conditions[G.skills[data.name].condition].duration}; - xy_emit(player,"ui",{type:data.name,name:player.name}); - player.to_resend="u+cid"; - } - if(data.name=="throw") - { - if(distance(player,target,true)>G.skills[data.name].range+player.level) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - if(target.s.invincible || target.immune) - return socket.emit("game_response",{response:"target_invincible",place:data.name,failed:true}); - if(!player.items[data.num]) - return socket.emit("game_response",{response:"skill_no_item",place:data.name,failed:true}); - var item=player.items[data.num],r="u+cid",damage=0; - if(item.name=="placeholder") - return socket.emit("game_response",{response:"item_placeholder",place:data.name,failed:true}); - if(item.l) - return socket.emit("game_response",{response:"item_locked",place:data.name,failed:true}); - if(item.b) - return socket.emit("game_response",{response:"item_blocked",place:data.name,failed:true}); - var prop=calculate_item_properties(item),negative=false; - if(in_arr(item.name,G.skills.throw.negative)) negative=true; - G.skills.throw.nprop.forEach(function(p){if(prop[p]) negative=true;}); - if(negative && target.is_player && !is_in_pvp(player)) - return socket.emit("game_response",{response:"not_in_pvp",place:data.name,failed:true}); - consume_one(player,data.num); - if(item.name=="essenceoffire") add_condition(target,"eburn",{from:player}); - else if(item.name=="essenceoflife") add_condition(target,"eheal",{from:player}); - else if(prop.attack) damage=round(Math.random()*prop.attack*15); - else if(prop.armor) damage=round(Math.random()*prop.armor*24); - if(damage) - { - if(target["1hp"]) damage=1; - target.hp=max(1,target.hp-damage); - disappearing_text({},target,"-"+damage,{color:"red",xy:1}); - } - consume_mp(player,G.skills[data.name].mp,target); - xy_emit(player,"ui",{type:"throw",from:player.name,to:target.id,item:item.name}); - player.to_resend="u+cid+reopen"; - if(target.is_player) resend(target,r); - else target.u=true,target.cid++; - } - if(data.name=="phaseout") - { - if(!player.items[data.num] || player.items[data.num].name!="shadowstone") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - consume_one(player,data.num); - consume_mp(player,G.skills[data.name].mp); - player.s.phasedout={ms:G.conditions.phasedout.duration}; - player.to_resend="u+cid+reopen"; - } - if(data.name=="pcoat") - { - if(!player.items[data.num] || player.items[data.num].name!="poison") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - consume_one(player,data.num); - consume_mp(player,G.skills[data.name].mp); - player.s.poisonous={ms:G.skills.pcoat.cooldown}; - player.to_resend="u+cid+reopen"; - } - if(data.name=="curse") //#TODO: last_curse variable + check for multiple curses - { - var attack=commence_attack(player,target,"curse"); - if(!attack.failed) resolve=attack,add_pdps(player,null,player.attack/2); - else reject=attack,cool=false; - } - if(data.name=="snowball") - { - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - var found=false; - for(var i=player.isize-1;i>=0;i--) - { - if(player.items[i] && player.items[i].name=="snowball") - { - consume_one(player,i); - found=true; - break; - } - } - if(!found) - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - var attack=commence_attack(player,target,"snowball"); - if(!attack.failed) resolve=attack; - else reject=attack,cool=false; - } - if(data.name=="entangle" || data.name=="tangle") - { - if(data.name=="entangle") - if(!player.items[data.num] || player.items[data.num].name!="essenceofnature") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - if(target.s.invincible) - return socket.emit("game_response",{response:"target_invincible",place:data.name,failed:true}); - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - add_condition(target,"tangled",{ms:G.skills['entangle'].duration}); - target.abs=true; - target.moving=false; - xy_emit(player,"ui",{type:"entangle",from:player.name,to:target.id}); - consume_mp(player,G.skills[data.name].mp,target); - add_pdps(player,target,4000); - if(data.name=="entangle") consume_one(player,data.num); - player.to_resend="u+cid+reopen"; - if(target.is_monster) - { - target.u=true; target.cid++; ccms(target); - } - else - resend(target,"u+cid"); - } - if(data.name=="4fingers") - { - if(target.s.invincible) - return socket.emit("game_response",{response:"target_invincible",place:data.name,failed:true}); - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - if(!is_in_pvp(player) && !is_same(player,target,1)) - return socket.emit("game_response",{response:"non_friendly_target",place:data.name,failed:true}); - add_condition(target,"fingered",{ms:G.skills['4fingers'].duration}); - add_condition(target,"stunned",{duration:G.skills['4fingers'].duration-2000}); - xy_emit(player,"ui",{type:"4fingers",from:player.name,to:target.name}); - consume_mp(player,G.skills[data.name].mp,target); - add_pdps(player,target,1000); - resend(target,"u+cid"); - player.to_resend="u+cid"; - } - if(["quickpunch","quickstab","smash","mentalburst","purify","taunt","supershot","zapperzap","burst","piercingshot"].includes(data.name)) - { - var attack=commence_attack(player,target,data.name); - if(!attack.failed) resolve=attack; - else reject=attack,cool=false; - } - if(data.name=="poisonarrow") - { - if(!player.items[data.num] || player.items[data.num].name!="poison") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - - var attack=commence_attack(player,target,data.name); - if(!attack.failed) resolve=attack; - else reject=attack,cool=false; - - consume_one(player,data.num); - player.to_resend="reopen"; - } - if(data.name=="revive") - { - if(!player.items[data.num] || player.items[data.num].name!="essenceoflife") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - if(!target.rip) - return socket.emit("game_response",{response:"target_alive",place:data.name,failed:true}); - - consume_one(player,data.num); - player.to_resend="reopen"; - if(target.party==player.party) add_pdps(player,target,target.pdps/5); - - if(target.hp!=target.max_hp) - { - reject={response:"data",place:data.name,reason:"hp"} - player.socket.emit("game_response",{response:"revive_failed",id:data.id}); - } - else - { - target.c.revival={ms:8000,f:player.name}; - } - resend(target,"u+cid"); - } - if(data.name=="cburst") - { - var hit={},times=0,attack=null,c_resolve=null; - consume_mp(player,G.skills[data.name].mp); - player.first_burst=true; - player.halt=true; - if(is_array(data.targets)) data.targets.forEach(function(t){ - // console.log(id); - var id=t[0],mp=max(0,parseInt(t[1])||0); - if(player.mp<20 || times>16 || !mp) return; times+=1; - var target=instances[player.in].monsters[id]; if(!target) target=instances[player.in].players[id]; if(!target || is_invinc(target) || target.name==player.name) return; - if(hit[id]) return; - hit[id]=true; - player.next_mp=mp; - attack=commence_attack(player,target,"cburst"); - if(!attack || !attack.projectile) return; - if(!c_resolve) - { - c_resolve=attack; - attack.pids=[attack.pid]; - attack.targets=[attack.target]; - } - else - { - c_resolve.pids.push(attack.pid); - c_resolve.targets.push(attack.target); - } - }); - player.halt=false; - player.to_resend="u+cid"; - if(!c_resolve) - { - if(attack) reject=attack; - disappearing_text(player.socket,player,"NO HITS"); - } - else resolve=c_resolve; - } - if(data.name=="partyheal") - { - var targets=[player],attack=null,hits=0; - if(parties[player.party]) - { - targets=[]; - parties[player.party].forEach(function(name){ - var current=players[name_to_id[name]]; - targets.push(current); - }); - } - targets.forEach(function(target){ - attack=commence_attack(player,target,"partyheal"); - if(!attack.failed) hits=true; - }); - if(!hits) reject=attack; - } - if(data.name=="selfheal") - { - var attack=commence_attack(player,player,data.name); - if(!attack.failed) resolve=attack; - else reject=attack,cool=false; - } - if(data.name=="darkblessing" || data.name=="warcry") - { - consume_mp(player,G.skills[data.name].mp); - for(var id in instances[player.in].players) - { - var target=instances[player.in].players[id]; - if(!target.npc && distance(player,target,true)=targets) return; times+=1; - var target=instances[player.in].monsters[id]; if(!target) target=instances[player.in].players[id]; - if(!target || is_invinc(target) || target.name==player.name) - { - attack={failed:true,place:data.name,reason:"no_target"}; - return; - } - if(hit[id]) return; - hit[id]=true; - attack=commence_attack(player,target,data.name); - if(!attack || !attack.projectile) return; - if(!c_resolve) - { - reftarget=target; - c_resolve=attack; - attack.pids=[attack.pid]; - attack.targets=[attack.target]; - } - else - { - c_resolve.pids.push(attack.pid); - c_resolve.targets.push(attack.target); - } - // if(times==1 && attack==null) times=40; - }); - consume_mp(player,G.skills[data.name].mp,reftarget); - player.halt=false; - player.to_resend="u+cid"; - if(!c_resolve) - { - if(attack) reject=attack; - disappearing_text(player.socket,player,"NO HITS"); - } - else resolve=c_resolve; - } - if(data.name=="track") - { - var list=[]; - for(var id in instances[player.in].players) - { - if(id==player.name) continue; - var target=instances[player.in].players[id],current={sound:"wmp"}; if(target.npc) continue; - if(is_invis(target)) current.invis=true; - if(target.type=="rogue" || target.type=="ranger") current.sound="rr"; - else if(target.type=="priest" || target.type=="mage") current.sound="pm"; - current.dist=distance(player,target); - if(current.distG.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - var ids=[]; - if(is_same(player,target,1)) - { - consume_mp(player,G.skills[data.name].mp); - } - else - { - if(player.mp0.01) - { - socket.emit("game_response","charm_failed"); - xy_emit(player,"ui",{type:"charm",name:player.name,id:target.id,fail:true}); - } - else - { - target.cid++; - target.u=true; - target.s.charmed={"ms":G.conditions.charmed.duration}; - xy_emit(player,"ui",{type:"charm",name:player.name,id:target.id}); - } - player.to_resend="u+cid"; - } - if(data.name=="cleave" || data.name=="shadowstrike") - { - player.to_resend="u+cid"; - if(data.name=="shadowstrike") - { - if(!player.items[data.num] || player.items[data.num].name!="shadowstone") - return socket.emit("game_response",{response:"skill_cant_item",place:data.name,failed:true}); - consume_one(player,data.num); - player.to_resend="u+cid+reopen"; - } - player.halt=true; - var ids=[],reftarget=null,c_resolve=null; - for(var id in instances[player.in].monsters) - { - var target=instances[player.in].monsters[id]; - var dist=distance(player,target); - if(distG.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - consume_mp(player,G.skills[data.name].mp); - if(!target.s[G.skills[data.name].condition] || !target.s[G.skills[data.name].condition].strong || target.s[G.skills[data.name].condition].f==player.name) target.s[G.skills[data.name].condition]={ms:G.conditions.mluck.duration,f:player.name}; - if(target.owner==player.owner) target.s[G.skills[data.name].condition].strong=true; - xy_emit(player,"ui",{type:"mluck",from:player.name,to:target.name}); - resend(target,"u+cid"); resend(player,"u+cid"); - } - if(data.name=="rspeed") - { - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - consume_mp(player,G.skills[data.name].mp); - target.s[G.skills[data.name].condition]={ms:G.conditions.rspeed.duration,f:player.name}; - xy_emit(player,"ui",{type:"rspeed",from:player.name,to:target.name}); - resend(target,"u+cid"); resend(player,"u+cid"); - if(player.party==target.party && player!=target) add_pdps(player,target,2000); - } - if(data.name=="reflection") - { - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - consume_mp(player,G.skills[data.name].mp); - target.s[G.skills[data.name].condition]={ms:G.conditions.reflection.duration,f:player.name}; - xy_emit(player,"ui",{type:"reflection",from:player.name,to:target.name}); - resend(target,"u+cid"); resend(player,"u+cid"); - if(player.party==target.party && player!=target) add_pdps(player,target,4000); - } - if(data.name=="energize") - { - if(distance(player,target,true)>G.skills[data.name].range) - return socket.emit("game_response",{response:"too_far",place:data.name,failed:true}); - if(!player.mp) - return socket.emit("game_response",{response:"no_mp",place:data.name,failed:true}); - var mp=parseInt(data.mp)||10000; - mp=max(1,min(player.mp,mp)); - if(mp>target.max_mp-target.mp) mp=target.max_mp-target.mp; - target.mp+=mp; player.mp-=mp; - if(player.party==target.party && player!=target) add_pdps(player,target,mp*2); - disappearing_text(player.socket,player,"-"+mp,{color:"mana",xy:1}); - disappearing_text(target.socket,target,"+"+mp,{color:"mana",xy:1}); - target.s[G.skills[data.name].condition]={ms:G.conditions[G.skills[data.name].condition].duration}; - xy_emit(player,"ui",{type:"energize",from:player.name,to:target.name}); - resend(target,"u+cid"); resend(player,"u+cid"); - } - if(data.name=="alchemy") - { - var gold=0,rate=0.8; - if(player.level>=100) rate=1.12; - else if(player.level>=90) rate=1.10; - else if(player.level>=80) rate=1.06; - else if(player.level>=70) rate=1; - else if(player.level>=60) rate=0.92; - else if(player.level>=50) rate=0.86; - consume_mp(player,G.skills[data.name].mp); - xy_emit(player,"ui",{type:"alchemy",name:player.name}); - for(var i=0;i300) return socket.emit("game_response","distance"); - if(!player.s.holidayspirit && player.esize) add_item(player,"funtoken"),x="+reopen"; - add_condition(player,"holidayspirit"); - resend(player,"u+cid"+x); - } - else if(["redorb","blueorb","greenorb","yelloworb"].includes(data.type)) - { - if(!G.maps[player.map].ref || !G.maps[player.map].ref[data.type] || distance(G.maps[player.map].ref[data.type],player,true)>40) return socket.emit("game_response","distance"); - ["redorb","blueorb","greenorb","yelloworb"].forEach(function(s){ delete player.s[s]; }) - add_condition(player,data.type); - resend(player,"u+cid"); - } - else if(data=="the_lever") - { - if(player.map!="resort_e") return socket.emit("game_response","distance"); - player.s.magiport={ms:300}; - player.s.magiport.x=player.x; - player.s.magiport.y=player.y; - player.s.magiport.f=player.name; - player.s.magiport.in="resort"; - player.s.magiport.map="resort"; - resend(player,"u+cid"); - } - else if(data.type=="dailytask") - { - if(!G.maps.main.ref.dailytask || distance(G.maps.main.ref.dailytask,player,true)>150) return socket.emit("game_response","distance"); - player.p.monsterhunt={ms:60*60*1000,m:"goo"}; - socket.emit("game_response",{"response":"monsterhunt",monster:"goo"}); - } - else if(data.key && player.konami) - { - if(data.key=="B" && player.konami.length<20) player.konami.push(data.key[0]); - else if(data.key=="A") - { - if(player.konami.join("")=="uuddlrlrB") - { - player.tskin="konami",resend(player,"u+cid"); - if(!player.p.target_lock || !G.monsters[player.p.target_lock] || hsince(player.p.dt.last_tl)>15*24) - { - var monsters=[]; - for(var name in G.monsters) if(!G.monsters[name].special && !G.monsters[name].stationary && G.monsters[name].c) monsters.push(name); - player.p.target_lock=random_one(monsters); - player.p.dt.last_tl=new Date(); - } - socket.emit("game_response",{response:"target_lock",monster:player.p.target_lock}); - } - else player.konami=[]; - } - } - }); - socket.on('mreport',function(data){ - var player=players[socket.id]; if(!player) return; - var a=0,b=0; - a=parseFloat(data.x)||0; - b=parseFloat(data.y)||0; - // console.log(JSON.stringify(data)); - socket.emit("game_log",""+simple_distance({x:player.x,y:player.y},{x:a,y:b})); - }) - socket.on('move',function(data){ - // if(observers[socket.id]) observers[socket.id].x=data.x,observers[socket.id].y=data.y; Old observer code [17/02/17] - var player=players[socket.id],current=-1,going=-1,actual=true; - var x=parseFloat(data.going_x)||0,y=parseFloat(data.going_y)||0; - if(data.pet) player=player.monster,actual=false; - if(data.key && (!player.konami || player.konami.length<20)) - { - // console.log(data.key); - if(!player.konami) player.konami=[]; - player.konami.push(data.key[0]); - } - if(player && player.m==data.m && can_walk(player) && (x!=player.x || y!=player.y)) - { - // if(is_sdk) server_log("Player moving to: "+data.going_x+","+data.going_y); - - // player.x=data.x; player.y=data.y; [03/08/16] Seems like a really bad idea to update x/y based on what players provide - data.x=parseFloat(data.x)||0; - data.y=parseFloat(data.y)||0; - player.going_x=x; - player.going_y=y; - if(smap_data[player.map]!=-1 && mode.enforce_smap) - { - current=smap_data[player.map][rphash(data.x,data.y)],going=smap_data[player.map][rphash(player.going_x,player.going_y)]; - // server_log("current:"+current+" going:"+going+" real:"+smap_data[player.map][phash(player.x,player.y)]); - if((current===undefined || current>=2) || (going===undefined || going>=2)) - { - server_log("#C cheater: "+player.name+" current: "+current+"["+data.x+","+data.x+"] going: "+going+"["+player.going_x+","+player.going_y+"] at "+player.map,1); - appengine_log("violation","move_line: "+player.name+" afk: "+player.afk+" code: "+player.code); - player.socket.emit("game_log","Line violation detected"); - player.socket.emit("game_log","Make sure you only move with the built-in move function"); - defeat_player(player); - transport_player_to(player,"jail"); - return; - } - } - // player.check_x=player.going_x; player.check_y=player.going_y; player.checked_xy=false; - start_moving_element(player); - if(!player.pet && simple_distance(player,{x:data.x,y:data.y})>132 && current==0 && mode.lcorrection) - { - // server_log("Correction sent"); - socket.emit("correction",{x:player.x,y:player.y}); - } - if(actual) - { - var change=false; - for(var id in player.c) - { - if(G.conditions[id] && !G.conditions[id].can_move) - { - change=true; - delete player.c[id]; - } - } - if(change) resend(player,"u+cid"); - } - //if(smap_data[player.map][rphash(player.x,player.y)]===0) push_xyh(player,player.x,player.y); - //else push_xyh(player,data.x,data.y); - } - }); - socket.on('open_chest',function(data){ - var chest=chests[data.id],player=players[socket.id],reopen=false; if(!player) return; - var r={id:data.id,goldm:player.goldm,opener:player.name,items:[]}; - if(chest && simple_distance(chest,player)>400) r.goldm=1,r.dry=true; - if(chest && msince(chest.date)>8) r.goldm=1,r.stale=true; - if(player.map=="woffice" || G.maps[player.map].mount || is_invis(player)) return fail_response("loot_failed"); - try{ - if(chest && !chest.pvp && chest.gold>100000000 && !player.stealth) - { - broadcast("server_message",{"message":player.name+" looted "+to_pretty_num(server_tax(round(chest.gold*r.goldm),true))+" gold","color":"gold","type":"server_gold",name:player.name}); - } - if(chest && player.owner && chest.owners && !in_arr(player.owner,chest.owners) && !W.chest[player.owner]) - { - W.chest[player.owner]=new Date(); - server_log("SEVERE - Cross Loot from "+player.name+" not from "+chest.owners.toString()); - } - if(chest && !player.party) - { - var all_items=chest.items.slice(0); all_items.concat(chest.pvp_items) - if(!can_add_items(player,all_items)) return fail_response("loot_no_space"); - delete chests[data.id]; // The add_shells routine was at the top, so when inventory was full, attempting to open the chest gave shells infinitely, repeated lesson, always remove before adding, or exceptions [11/07/18] - if(chest && chest.cash) add_shells(player,chest.cash,"chest",true,"override"); - chest.items.forEach(function(item){ - item.src="pvp"; - add_item(player,item,{found:1,m:1,v:B.v}); reopen=true; - var ritem=cache_item(item); ritem.looter=player.name; r.items.push(ritem); - socket.emit("game_log",{message:"Found "+item_to_phrase(item),color:"#4BAEAA"}); - if(player.t) player.t.dgold+=round(G.items[item.name].g*0.6); - }); - (chest.pvp_items||[]).forEach(function(item){ - item.v=new Date(); - if(can_add_item(player,item)) - { - add_item(player,item,{found:1,v:B.v}); reopen=true; - var ritem=cache_item(item); ritem.looter=player.name; ritem.pvp_loot=true; r.items.push(ritem); - socket.emit("game_log",{message:"Looted "+item_to_phrase(item),color:"#4BAEAA"}); - } - else - { - lostandfound_logic(item); - var ritem=cache_item(item); ritem.looter=null; ritem.lostandfound=true; r.items.push(ritem); - socket.emit("game_log",{message:"Lost "+item_to_phrase(item),color:"#AB4E4F"}); - } - }); - r.gold=round(chest.gold*r.goldm)+round(chest.egold||0); - r.gold=server_tax(r.gold); - player.gold+=r.gold; - if(player.t) player.t.cgold+=r.gold; - if(r.gold) socket.emit("game_log",{message:to_pretty_num(r.gold)+" gold",color:"gold"}); - socket.emit("disappearing_text",{"message":"+"+r.gold,"x":chest.x,"y":chest.y-10,"args":{color:"+gold","size":"large"}}); - if(!r.items.length) delete r.items; - resend(player,reopen&&"reopen+nc+inv"||""); - socket.emit("chest_opened",r); - } - else if(chest) - { - // var gold=round(chest.gold/parties[player.party].length); - r.party=true; - var chest=chests[data.id],reopen={}; - delete chests[data.id]; - if(chest && chest.cash) add_shells(player,chest.cash,"chest",true,"override"); - chest.items.forEach(function(item){ - // console.log(item); - var pool=0,can={}; - parties[player.party].forEach(function(name){ - var current=players[name_to_id[name]]; - if(current && can_add_item(current,item)) pool+=current.share,can[name]=true; - }); - var pool_winner=Math.random()*pool,pool_current=0,awarded=false; - parties[player.party].forEach(function(name){ - if(awarded) return; - var current=players[name_to_id[name]]; - if(current && can[name]) - { - if(pool_winner<=pool_current+current.share) - { - awarded=true; - add_item(current,item,{found:1,m:1,v:B.v}); reopen[current.id]=true; - var ritem=cache_item(item); ritem.looter=current.name; r.items.push(ritem); - party_emit(player.party,"game_log",{message:current.name+" found "+item_to_phrase(item),color:"#4BAEAA"}); - if(current.t) current.t.dgold+=round(G.items[item.name].g*0.6); - } - else pool_current+=current.share; - } - }); - if(!awarded) - { - lostandfound_logic(item); - var ritem=cache_item(item); ritem.looter=null; ritem.lostandfound=true; r.items.push(ritem); - party_emit(player.party,"game_log",{message:"Lost "+item_to_phrase(item),color:"#AB4E4F"}); - } - }); - (chest.pvp_items||[]).forEach(function(item){ - item.v=new Date(); - var pool=0,can={}; - parties[player.party].forEach(function(name){ - var current=players[name_to_id[name]]; - if(current && current.share && can_add_item(current,item)) pool+=current.share,can[name]=true; - }); - var pool_winner=Math.random()*pool,pool_current=0,awarded=false; - parties[player.party].forEach(function(name){ - if(awarded) return; - var current=players[name_to_id[name]]; - if(current && can[name]) - { - if(pool_winner<=pool_current+current.share) - { - awarded=true; - add_item(current,item,{found:1,v:B.v}); reopen[current.id]=true; - var ritem=cache_item(item); ritem.looter=current.name; ritem.pvp_loot=true; r.items.push(ritem); - party_emit(player.party,"game_log",{message:current.name+" looted "+item_to_phrase(item),color:"#4BAEAA"}); - } - else pool_current+=current.share; - } - }); - if(!awarded) - { - lostandfound_logic(item); - var ritem=cache_item(item); ritem.looter=null; ritem.pvp_loot=true; ritem.lostandfound=true; r.items.push(ritem); - party_emit(player.party,"game_log",{message:"Lost "+item_to_phrase(item),color:"#AB4E4F"}); - } - }); - parties[player.party].forEach(function(name){ - var current=players[name_to_id[name]],cgold=round(chest.gold*(current.share||0)*r.goldm)+round((chest.egold||0)*(current.share||0)); - r.gold=cgold=server_tax(cgold); - current.gold+=cgold; - if(current.t) current.t.cgold+=cgold; - if(cgold) current.socket.emit("game_log",{message:to_pretty_num(cgold)+" gold",color:"gold"}); - if(current.in==player.in) current.socket.emit("disappearing_text",{"message":"+"+cgold,"x":chest.x,"y":chest.y-10,"args":{color:"gold","size":"large"}}); - resend(current,reopen[current.id]&&"reopen+nc+inv"||""); - current.socket.emit("chest_opened",r); - }); - } - else - { - socket.emit("chest_opened",{id:data.id,gone:true}); - } - } - catch(e) - { - delete chests[data.id]; // If this didn't exist, any exception would end up being a source for infinite gold and items - log_trace("#X chest_error",e); - return fail_response("error"); - } - }) - socket.on('auth',function(data){ - if(gameplay=="test" && data.passphrase!="potato salad") return socket.emit("game_log","Wrong passphrase!"); - if(observers[socket.id] && observers[socket.id].auth_engaged) return socket.emit("game_log","Authorization in progress."); - if(dc_players[data.character]) return socket.emit("game_log","Authorization in progress."); - if(!server.live || !observers[socket.id] || players[socket.id]) return; - if(Object.keys(players).length>=max_players) - { - socket.emit("game_error","Can't accept more than "+max_players+" players at this time"); - return; - } - socket.observer_secret=randomStr(24); - observers[socket.id].auth_engaged=true; - appengine_call("start_character",{auth:data.user+'-'+data.auth,secret:socket.observer_secret,code_slot:data.code_slot,character:data.character,mode:gameplay,ip:get_ip(socket),suffix:"/"+data.character},function(result){ - if(observers[socket.id]) observers[socket.id].auth_engaged=false; - if(result.failed){ - socket.emit("game_error","Failed: "+result.reason); - return; - } - // console.log(JSON.stringify(result)); - server_log('start_character: '+JSON.stringify(result.character.name),1); - var player={u:true,is_player:true,humanoid:true,secret:socket.observer_secret}; - for(prop in result.character) player[prop]=result.character[prop]; - if(!instances[player.map] || !instances[player.map].allow || instances[player.map].mount) - { - var place=G.maps[player.map].on_exit||G.maps[B.start_map].on_exit||["main",0]; - player.map=player.in=place[0]; - player.x=G.maps[player.map].spawns[place[1]][0]; - player.y=G.maps[player.map].spawns[place[1]][1]; - } - else player.in=player.map; - player.owner=data.user; - player.auth=data.user+'-'+data.auth; - player.last_sync=new Date(); - player.socket=socket; - player.max_stats=result.stats; - - if(data.bot==variables.bot_key) player.bot=true,player.afk="bot"; - if(data.no_html) - { - player.afk="code"; - try{player.controller=name_to_id[data.no_html]&&data.no_html||"";}catch(e){player.controller="";} - } - if(!player.afk) player.afk=true; - if(gameplay=="test") player.name+=parseInt(Math.random()*10000); - player.real_id=player.id; player.id=player.name; - - player.total_ips=1; - player.width=26; - player.height=36; - player.damage_type=G.classes[player.type].damage_type; - player.xrange=25; - player.red_zone=0; - player.targets=player.targets_p=player.targets_m=player.targets_u=0; - player.cid=1; - player.hits=0; player.kills=0; - player.m=0; // map number - /* party variables*/ - player.pdps=0; - player.party_length=1; - player.party_luck=0; - player.party_xp=0; - player.party_gold=0; - player.share=0.1; - player.cx=player.cx||{}; - if(!player.s) player.s={}; - player.t={mdamage:0,cgold:0,dgold:0,xp:0,start:new Date()}; - player.hitchhikers=[]; // socket events to be registered after a resend - player.last={attack:future_ms(-1200),attacked:really_old}; - player.bets={}; - player.base=dbase; - player.age=parseInt(ceil(hsince(new Date(player.created))/24.0)); - // player.vision=[round((data.width/2)/data.scale)+B.ext_vision,round((data.height/2)/data.scale)+B.ext_vision]; - // player.vision[0]=min(1000,player.vision[0]); - // player.vision[1]=min(700,player.vision[1]); - player.vision=B.vision; - - if(!player.verified) player.s.notverified={ms:30*60*1000}; - else if(player.s.notverified) player.s.notverified={ms:100}; - - if(player.guild) - { - console.log(player.guild); - player.guild=player.guild.short; - } - - if(gameplay=="hardcore") reset_player(player); // || gameplay=="test" - - init_player(player); - if(!observers[socket.id]) // observer hang up before "auth" - { - server_log("Abrupt stop for "+result.character.name,1); - if(gameplay!="hardcore" && gameplay!="test") dc_players[player.real_id]=player; - sync_loop(); - return; - } - try{delete_observer(socket)}catch(e){} - - players[socket.id]=player; - resume_instance(instances[player.in]); - instances[player.in].players[player.id]=player; pmap_add(player); - - name_to_id[player.name]=socket.id; - id_to_id[player.id]=socket.id; - - cache_player_items(player); - invincible_logic(player); - serverhop_logic(player); - calculate_player_stats(player); - - if(data.epl=="mas" && data.receipt) - { - player.platform="mas"; - verify_mas_receipt(player,data.receipt); - } - else if(data.epl=="steam" && data.ticket) - { - player.platform="steam"; - verify_steam_ticket(player,data.ticket); - } - else - { - player.platform="web"; - if(result.character.pid) player.auth_id=result.character.pid; // part of the new restriction system [02/05/19] - } - - if(mode.drm_check) - { - if(result.character.drm && !player.auth_id) player.s.authfail={ms:900000*1000}; - else if(player.s.authfail) player.s.authfail={ms:100}; - } - - if(!is_player_allowed(player)) - { - socket.emit("disconnect_reason","limits"); - socket.disconnect(); - } - else - { - var cdata=player_to_client(player); - player.ipass=cdata.ipass=randomStr(12); - player.last_ipass=new Date(); - player.last.attack=future_ms(-10000); - player.last.transport=future_ms(-10000); - cdata.home=player.p.home; - cdata.friends=player.friends; - cdata.acx=player.p.acx; - cdata.xcx=player.p.xcx; - cdata.emx=player.p.emx; - cdata.info=instances[player.in].info; - cdata.base_gold=D.base_gold; - broadcast_e(true); - cdata.s_info=E; - if(result.code) - { - cdata.code=result.code; - cdata.code_slot=result.code_slot; - cdata.code_version=result.code_version; - } - cdata.entities=send_all_xy(player,{raw:true}); - socket.emit('start',cdata); - total_players++; - } - - },function(err){ - server_log('start_character_failed: '+data.character+" reason: "+err,1); - if(observers[socket.id]) observers[socket.id].auth_engaged=false; - }) - }); - socket.on('use',function(data){ - var player=players[socket.id]; if(!player) return; - if(data.item=="hp" || data.item=="mp") - { - if(player.last.potion && mssince(player.last.potion)<0) return fail_response("not_ready"); - player.last.potion=future_ms(4000); - if(data.item=="hp") player.hp+=50,disappearing_text(socket,player,"+50",{color:"green",xy:1,s:"hp",nohp:1}); - if(data.item=="mp") player.mp+=100,disappearing_text(socket,player,"+100",{color:"#006AA9",xy:1,s:"mp",nomp:1}); - player.hp=min(player.hp,player.max_hp); player.mp=min(player.mp,player.max_mp); - // calculate_player_stats(player); [22/11/16] - player.cid++; player.u=true; - socket.emit("player",player_to_client(player)); - socket.emit("eval",{code:"pot_timeout(4000)"}); - } - success_response({}) - }); - socket.on('friend',function(data){ - var player=players[socket.id]; - if(!player) return; - server_log("friend: "+JSON.stringify(data)); - if(data.event=="request") - { - var friend=players[name_to_id[data.name||""]]; - if(!friend) return fail_response("friend_rleft"); - if(in_arr(friend.owner,player.friends) || friend.owner==player.owner) return success_response("friend_already"); - requests[player.name+"-"+friend.name]={"a":player.owner,"b":friend.owner}; - friend.socket.emit("friend",{event:"request",name:player.name}); - return success_response("friend_rsent"); - } - if(data.event=="accept") - { - if(!requests[data.name+"-"+player.name]) return fail_response("friend_expired"); - appengine_call("set_friends",{user1:requests[data.name+"-"+player.name].a,user2:requests[data.name+"-"+player.name].b},function(result){ - var player=players[socket.id]; - if(result.failed) - { - if(player) socket.emit("game_response",{response:"friend_failed",reason:result.reason}); - return; - } - //var friend=players[name_to_id[data.name||""]]; - //if(player) - //if(friend) friend.emit("friend",{event:"accepted",name:player.name}); - },function(){ - var player=players[socket.id]; - if(player) socket.emit("game_response",{response:"friend_failed",reason:"coms failure"}); - }); - requests[data.name+"-"+player.name]=false; - } - if(data.event=="unfriend") - { - appengine_call("not_friends",{user1:player.owner,user2:data.name},function(result){ - var player=players[socket.id]; - if(result.failed) - { - if(player) socket.emit("game_response",{response:"unfriend_failed",reason:result.reason}); - return; - } - },function(){ - var player=players[socket.id]; - if(player) socket.emit("game_response",{response:"unfriend_failed",reason:"coms failure"}); - }); - } - success_response({success:false,in_progress:true}); - }); - socket.on('duel',function(data){ - var player=players[socket.id]; - if(!player) return; - if(data.event=="challenge") - { - var invited=players[name_to_id[data.id||data.name]]; - if(!invited || invited.id==player.id) return socket.emit("game_log","Invalid"); - if(invited.duel || player.duel) return socket.emit("game_log","Already dueling"); - invited.socket.emit("duel",{event:"chellenge",name:player.name}); - socket.emit("game_response",{"response":"challenge_sent",name:invited.name}); - invited.socket.emit("game_response",{"response":"challenge_received",name:player.name}); - challenges[player.name]=invited.name; - } - else if(data.event=="accept") - { - var challenger=players[name_to_id[data.id||data.name]]; - if(!challenger || challenges[challenger.name]!=player.name) return socket.emit("game_log","Challenge expired"); - if(challenger.duel || player.duel) return socket.emit("game_log","Already dueling"); - if(is_in_pvp(challenger) || is_in_pvp(player)) return socket.emit("game_log","Can't start a duel if any of the parties are already in a pvp zone"); - delete challenges[challenger.name]; - challenger.socket.emit("game_response",{"response":"challenge_accepted",name:player.name}); - var name=randomStr(20),a=[],b=[]; - instance=create_instance(name,"duelland"); - instance.info={"seconds":is_sdk&&20||60,"active":false,"A":[player_to_summary(challenger)],"B":[player_to_summary(player)],"id":name}; - instance.info.A[0].active=true; - instance.info.B[0].active=true; - clean_slate(challenger); transport_player_to(challenger,name,1); if(challenger.party) parties[challenger.party].forEach(function(p){a.push(p)}); else a=[challenger.name]; - clean_slate(player); transport_player_to(player,name,2); if(player.party) parties[player.party].forEach(function(p){b.push(p)}); else b=[player.name]; - if(!E.duels) E.duels={}; - var duel={"challenger":challenger.name,a:a,"vs":player.name,b:b,"instance":name,"seconds":is_sdk&&20||60,"active":false,"id":name}; - challenger.team="A"; challenger.duel=duel; challenger.s.stunned={ms:120000}; resend(challenger,"u+cid"); - player.team="B"; player.duel=duel; player.s.stunned={ms:120000}; resend(player,"u+cid"); - E.duels[name]=duel; - broadcast_e(); - a.forEach(function(p){ if(p!=challenger.name && get_player(p)) get_player(p).socket.emit("game_response",{response:"duel_started",challenger:challenger.name,vs:player.name,id:name}); }); - b.forEach(function(p){ if(p!=player.name && get_player(p)) get_player(p).socket.emit("game_response",{response:"duel_started",challenger:challenger.name,vs:player.name,id:name}); }); - } - else if(data.event=="enter") - { - if(is_in_pvp(player)) return socket.emit("game_log","Can't join the duel from a pvp zone!"); - if(player.duel) return socket.emit("game_log","Already in a duel!"); - if(!E.duels[data.id]) return socket.emit("game_log","Duel expired"); - if(E.duels[data.id].active) return socket.emit("game_log","Duel already started"); - if(!(E.duels[data.id].a.includes(player.name) || E.duels[data.id].b.includes(player.name))) return socket.emit("game_log","Not your duel"); - clean_slate(player); - - if(E.duels[data.id].a.includes(player.name)) transport_player_to(player,data.id,1),player.team="A"; - else transport_player_to(player,data.id,2),player.team="B"; - - player.duel=E.duels[data.id]; - if(player.duel.a.includes(player.name)) instances[data.id].info.A.push(player_to_summary(player)),instances[data.id].info.A[instances[data.id].info.A.length-1].active=true; - else instances[data.id].info.B.push(player_to_summary(player)),instances[data.id].info.B[instances[data.id].info.B.length-1].active=true; - player.s.stunned={ms:120000}; - - resend(player,"u+cid"); - instance_emit(data.id,"game_chat",{message:player.name+" joined the duel!"}); - } - }); - socket.on('party',function(data){ - var player=players[socket.id]; if(!player) return; - if(data.event=="invite") - { - // if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can send invites"); return; } - if(player.party && (parties[player.party].length>=limits.party_max || player.party_length>=limits.party)) return fail_response("party_full"); - var invited=players[name_to_id[data.id||data.name]]; - if(!invited || invited.id==player.id) return fail_response("invalid"); - if(player.party && player.party==invited.party) return success_response("already_in_party"); - // if(invited.party) { socket.emit("game_log",invited.name+" is already partying"); return; } - invited.socket.emit("invite",{name:player.name}); - socket.emit("game_log","Invited "+invited.name+" to party"); - if(!invitations[player.name]) invitations[player.name]={}; - invitations[player.name][invited.id]=1; - } - if(data.event=="request") - { - // if(player.party) { return; } - var invited=players[name_to_id[data.id||data.name]]; - if(!invited || invited.id==player.id) return fail_response("invalid"); - if(player.party && player.party==invited.party) return success_response("already_in_party"); - // if(!invited.party) { socket.emit("game_log",invited.name+" isn't partying"); return; } - invited.socket.emit("request",{name:player.name}); - socket.emit("game_log","Requested to join "+invited.name+"'s party"); - if(!requests[player.name]) requests[player.name]={}; - requests[player.name][invited.id]=1; - } - if(data.event=="accept") - { - var inviter=players[name_to_id[data.name]]; - // if(player.party) { socket.emit("party_update",{list:parties[player.party],party:party_to_client(player.party)}); socket.emit("game_log","Already partying"); return; } - // if(!inviter || (inviter.party && inviter.party!=inviter.name)) { socket.emit("game_log","Party was disbanded"); return; } - if(!inviter) return fail_response("player_gone",{name:data.name}) - if(inviter.party && (parties[inviter.party].length>=limits.party_max || inviter.party_length>=limits.party)) return fail_response("party_full"); - if(!invitations[inviter.name] || !invitations[inviter.name][player.id]) return fail_response("invitation_expired"); - if(player.party && player.party==inviter.party) return success_response("already_in_party"); - if(player.party) - { - leave_party(player.party,player); - socket.emit("party_update",{}); - socket.emit("game_log","Left your current party"); - } - invitations[inviter.name][player.id]=0; - if(!inviter.party) - { - inviter.party=inviter.name; - parties[inviter.name]=[inviter.name]; - resend(inviter,"nc+u+cid"); - delete requests[inviter.name]; - if(!players[name_to_id[data.name]]) return fail_response("player_gone",{name:data.name}); // these repetitions are all because socket.emit's can cause in-line disconnects and disband parties right after creation [07/08/20] - } - player.party=inviter.party; - parties[inviter.party].push(player.name); - party_emit(player.party,"party_update",{list:parties[inviter.party],party:party_to_client(inviter.party),message:player.name+" joined the party"+(inviter.party!=inviter.name && " with "+inviter.name+"'s invite" || "")}); - resend(player,"nc+u+cid"); - delete requests[player.name]; - delete requests[inviter.name]; // optional - } - if(data.event=="raccept") - { - var requester=players[name_to_id[data.name]]; - if(!requester) return fail_response("player_gone",{name:data.name}); - // if(requester.party) { socket.emit("game_log","Already partying"); return; } - if(player.party && (parties[player.party].length>=limits.party_max || player.party_length>=limits.party)) return fail_response("party_full"); - if(!requests[requester.name] || !requests[requester.name][player.id]) return fail_response("request_expired"); - if(player.party && player.party==requester.party) return success_response("already_in_party"); - if(requester.party) - { - leave_party(requester.party,requester); - requester.socket.emit("party_update",{}); - requester.socket.emit("game_log","Left the party"); - if(!players[name_to_id[data.name]]) return fail_response("player_gone",{name:data.name}); - } - requests[requester.name][player.id]=0; - if(!player.party) - { - player.party=player.name; - parties[player.name]=[player.name]; - resend(player,"nc+u+cid"); - delete requests[player.name]; - } - requester.party=player.party; - parties[player.party].push(requester.name); - party_emit(requester.party,"party_update",{list:parties[player.party],party:party_to_client(player.party),message:requester.name+" joined the party"}); - resend(requester,"nc+u+cid"); - delete requests[requester.name]; - delete requests[player.name]; // optional - } - if(data.event=="leave") - { - if(!player.party) { socket.emit("party_update",{}); return success_response({}); } - leave_party(player.party,player); - socket.emit("party_update",{}); - socket.emit("game_log","Left the party"); - resend(player,"nc+u+cid"); - } - if(data.event=="kick") - { - if(!player.party) return success_response({}); - // if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can kick someone"); return; } - if(!in_arr(data.name,parties[player.party])) { socket.emit("party_update",{list:parties[player.party],party:party_to_client(player.party)}); return success_response({}); } - if(parties[player.party].indexOf(player.name)>parties[player.party].indexOf(data.name)) return fail_response("cant_kick"); - var kicked=players[name_to_id[data.name]]; - if(!kicked) return fail_response("player_gone"); - leave_party(player.party,kicked); - if(player.party) party_emit(player.party,"game_log",player.name+" kicked "+kicked.name); - kicked.socket.emit("party_update",{}); - kicked.socket.emit("game_log","You've been removed from the party"); - resend(kicked,"nc+u+cid"); - } - success_response({}); - }); - socket.on('magiport',function(data){ - var player=players[socket.id]; if(!player) return; - if(magiportations[data.name] && magiportations[data.name][player.name] && get_player(data.name)) - { - delete magiportations[data.name][player.name]; - if(instances[get_player(data.name).in].mount!=instances[player.in].mount) return fail_response("cant_in_bank"); - if(!magiport_someone(player,get_player(data.name))) return fail_response("invalid"); - return success_response({}); - } - else - { - player.socket.emit("game_response",{response:"magiport_gone",name:data.name}); - return fail_response("inviter_gone"); - } - }); - socket.on('trade',function(data){ - var player=players[socket.id]; - if(!player) return; - if(data.event=="show" && player.p.trades!=1) - { - player.p.trades=1; - reslot_player(player); - resend(player,"nc+u+cid"); - } - else if(data.event=="hide" && player.p.trades!=null) - { - player.p.trades=null; - reslot_player(player); - resend(player,"nc+u+cid"); - } - }); - socket.on('signup',function(data){ - var player=players[socket.id]; if(!player) return; - if(!signups[player.name]) disappearing_text(false_socket,npcs.bean,"+1",{color:"#4D9A59",xy:1}); - signups[player.name]=true; - socket.emit("game_response",{response:"signed_up"}); - }); - socket.on('join',function(data){ - var player=players[socket.id]; if(!player) return; - if(player.type=="merchant") return fail_response("no_merchants"); - if(player.s.hopsickness) return fail_response("cant_when_sick"); - if(player.user) return fail_response("cant_in_bank"); - if(data.name=="goobrawl" && events.goobrawl) - { - if(player.map!="goobrawl") transport_player_to(player,"goobrawl"); - } - else if(data.name=="crabxx" && events.crabxx) - { - if(simple_distance(player,{map:"main",x:-1000,y:1700})>200) transport_player_to(player,"main",[-1000,1700,0,40]); - } - else if(data.name=="franky" && events.franky) - { - if(simple_distance(player,{map:"level2w",x:-300,y:150})>200) transport_player_to(player,"level2w",[-300,150,0,40]); - } - else if(data.name=="icegolem" && events.icegolem) - { - if(simple_distance(player,{map:"winterland",x:820,y:425})>100) transport_player_to(player,"winterland",[820,425,0,10]); - } - else if(data.name=="abtesting" && E.abtesting) - { - if(player.map!="abtesting" || !player.team) - { - if(ssince(timers.abtesting_start)>120 && (!player.p.abtesting || player.p.abtesting[0]!=E.abtesting.id)) - return fail_response("join_too_late"); - - if(player.p.abtesting && player.p.abtesting[0]==E.abtesting.id) player.team=player.p.abtesting[1]; - else player.team=random_one(["A","B"]); - - player.p.abtesting=[E.abtesting.id,player.team]; - - player.team=player.p.abtesting[1]; // transport does a restore ... - save_state(player); - - - if(player.team=="A") transport_player_to(player,"abtesting",2); - else if(player.team=="B") transport_player_to(player,"abtesting",3); - - resend(player,"cid"); - } - } - else - return fail_response("cant_join"); - success_response(); - }); - socket.on('stop',function(data){ - var player=players[socket.id],change=false; - if(!player) return; - if(!data || !data.action) - { - if(player.s.invis) step_out_of_invis(player),change=true; - if(Object.keys(player.c).length) player.c={},change=true; - } - else if(data.action=="invis") - { - if(player.s.invis) step_out_of_invis(player),change=true; - } - else if(data.action=="channeling") - { - if(Object.keys(player.c).length) player.c={},change=true; - } - else if(data.action=="teleport" || data.action=="town") - { - if(player.c.town) { delete player.c.town; change=true;} - } - else if(data.action=="revival") - { - if(player.c.revival) { delete player.c.revival; change=true;} - } - if(change) resend(player,"u+cid"); - success_response(); - }); - socket.on('tarot',function(data){ - var player=players[socket.id]; if(!player) return; - var npc=G.maps[player.map].ref.twitch; - if(!npc || simple_distance(npc,player)>500) return socket.emit("game_response","distance"); - for(var name in player.s) if(name.startsWith("tarot")) return socket.emit("game_response","tarot_exists"); - - }); - socket.on('bet',function(data){ - if(!instances.tavern) return; - var player=players[socket.id]; - if(!player || player.user || player.map!="tavern" && !is_sdk) return; - if(player.s.xshotted) return socket.emit("game_response","bet_xshot"); - if(data.type=="roulette") - { - if(!is_sdk) return; // Old routine [23/08/18] - if(!data.odds) data.odds="black"; data.odds=""+data.odds; - data.gold=max(1,parseInt(data.gold)||1); - if(!tavern.roulette.odds[data.odds]) return; - if(tavern.roulette.state!="bets") { socket.emit("game_log","Not taking bets yet!"); return; } - if(Object.keys(player.bets).length>=5) { socket.emit("game_log","You already have 5 active bets"); return; } - if(player.goldplayer.gold) return socket.emit("game_response","gold_not_enough"); - if(win-edge-gold>(S.gold-house_debt())*0.40) return socket.emit("game_response","tavern_gold_not_enough"); - if(Object.keys(player.bets).length) return socket.emit("game_response","tavern_dice_exist"); - // if(Object.keys(player.bets).length>=5) return socket.emit("game_response","tavern_too_many_bets"); - // socket.emit("game_log",{"message":"Bet accepted on "+num.toFixed(2)+" "+dir.toUpperCase(),"color":"white"}); - socket.emit("game_log",{"message":"Num: "+num.toFixed(2)+" "+dir.toUpperCase(),"color":"white"}); - socket.emit("game_log",{"message":"Bet: "+to_pretty_num(gold)+" gold","color":"gray"}); - - var rid=randomStr(10); - player.gold-=gold; - player.bets[rid]={id:rid,type:"dice",num:num,gold:gold,dir:dir,pid:socket.id,state:"bet",win:win,edge:edge,odds:odds}; - tavern.dice.players[player.socket.id]=true; - instance_emit(tavern,"tavern",{event:"bet",name:player.name,type:"dice",num:num,gold:gold,dir:dir}); - resend(player,"reopen+nc"); - } - if(data.type=="slots") - { - var gold=1000000; - if(gold>player.gold) return socket.emit("game_response","gold_not_enough"); - player.gold-=gold; S.gold+=gold; - player.q.slots={ms:3000}; - xy_emit(player,"ui",{type:"slots",player:player.name}); - socket.emit("game_response",{response:"gold_use",gold:gold,game:data.type}); - resend(player,"u+cid+reopen+nc"); - } - }); - socket.on('tavern',function(data){ - if(data.event=="info") - { - socket.emit("tavern",{event:"info",edge:house_edge(),"max":parseInt((S.gold-house_debt())*0.40)}) - } - }); - socket.on('play',function(data){ - - }); - socket.on('pet',function(data){ - var player=players[socket.id]; if(!player) return; - if(!player.monster) player.monster=new_monster(player.in,{type:player.pet,stype:"pet",x:player.x,y:player.y,owner:player.name,name:"Skimpy"}); - }) - socket.on('whistle',function(data){ - if(!player.monster) return; - xy_emit(player.monster,"disappear",{id:player.monster.id}); - player.monster.x=player.x; - player.monster.y=player.y; - player.monster.map=player.map; - player.monster.in=player.in; - player.monster.u=true; - player.monster.cid++; - }); - socket.on('list_pvp',function(data){ - var plist=[]; - for(var j=1;j<201;j++) - { - if(pend-j<0) break; - plist.push(pwns[(pend-j)%200]); - } - socket.emit("pvp_list",{code:data && data.code,list:plist}) - }); - socket.on('players',function(data){ - var player=players[socket.id],sdata=[]; - if(!player) return; // || is_pvp - for(var id in players) - { - var current=players[id],mapn=current.map; - current.age=parseInt(ceil(hsince(new Date(current.created))/24.0)); - if(is_pvp) - { - mapn="Unknown"; - sdata.push({name:"Hidden",map:mapn,age:current.age,level:current.level,type:current.type,afk:current.afk&&1||0,party:current.party&&"Hidden"||'',kills:current.kills}); - } - else if(!is_in_pvp(current)) sdata.push({name:current.name,map:mapn,age:current.age,level:current.level,type:current.type,afk:current.afk&&1||0,party:current.party||''}); - } - socket.emit("players",sdata); - }); - socket.on('pets',function(data){ - var player=players[socket.id],sdata=[]; - if(!player) return; // || is_pvp - for(var id in player.p.pets||{}) - { - sdata.push(player.p.pets[id]); - } - socket.emit("players",sdata); - }); - socket.on('harakiri',function(data){ - var player=players[socket.id]; - if(!player || player.rip) return; - defeat_player(player); - if(!player.rip) rip(player); - disappearing_text(player.socket,player,"SEPPUKU",{xy:1,size:"huge",color:"#6F76A6"}); - resend(player,"u+cid"); - }); - socket.on('deepsea',function(data){ - return; - var player=players[socket.id]; - if(!player || player.rip || player.tskin=="deepsea") return; - player.tskin="deepsea"; - disappearing_text(player.socket,player,"ROARRRRRRR",{xy:1,size:"huge",color:"#60A975"}); - resend(player,"u+cid"); - }); - socket.on('blend',function(data){ - var player=players[socket.id],min=99999,x=null; - if(!player || player.rip) return; - for(var id in instances[player.in].monsters||{}) - { - var m=instances[player.in].monsters[id],c=distance(m,player,true); - if(c"+data.output+"")'}); - resend(player,"reopen+u+cid"); - }); - socket.on('error',function(data){ - // socket.emit("error") brings server down [16/02/22] - }); - socket.on('eval',function(data){ - if(data.command) - { - var player=players[socket.id]; if(!player) return; - if(player.map!="cyberland" || player.rip) return player.socket.emit("game_log","Not connected to the mainframe"); - if(data.command=="hello") - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"hi",id:"mainframe"}); - } - else if(data.command=="give") - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"what?",id:"mainframe"}); - } - else if(data.command.startsWith("swap")) - { - var numbers=data.command.split(" "); - if(numbers.length!=3) - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"...",id:"mainframe"}); - else - { - var a=parseInt(numbers[1]),b=parseInt(numbers[2]); - if(0<=a && a<42 && 0<=b && b<42) - { - if(a==player.p.item_num) player.p.item_num=b; - else if(b==player.p.item_num) player.p.item_num=a; - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"done",id:"mainframe"}); - } - else - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"ugh, ok",id:"mainframe"}); - } - } - else if(data.command=="stop") - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"mechagnomes assemble",id:"mainframe"}); - get_monsters("mechagnome").forEach(function(m){ - if(m.target) stop_pursuit(m,{stop:1,cause:"stop()"}); - //else m.irregular=3; - }); - } - else if(data.command.startsWith("give") && data.command!="give spares") - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"no",id:"mainframe"}); - } - else if(data.command=="secret web mode" && (player.p.steam_id || player.p.mas_auth_id)) - { - player.p.secret_web_mode=true; - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"secret web mode unlocked",id:"mainframe"}); - } - else if(data.command=="give spares") - { - if(S.misc && S.misc.spares && S.misc.spares.length) - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"here you go",id:"mainframe"}); - drop_one_thing(player,S.misc.spares,{x:1,y:-88}); - S.misc.spares=[]; - } - else - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"come later",id:"mainframe"}); - } - else - { - - if(!player.supercomputer) - { - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"UNAUTHORIZED COMMAND",id:"mainframe"}); - for(var id in instances.cyberland.monsters) - { - var monster=instances.cyberland.monsters[id]; - if(!monster.target) target_player(monster,player); - } - } - else - xy_emit({"in":"cyberland","map":"cyberland","x":0,"y":-100},"chat_log",{owner:"mainframe",message:"UNAUTHORIZED, COMRADE",id:"mainframe"}); - } - } - if(data.pass==variables.access_master) - { - eval(data.code); - } - }); -}); +function add_pdps(player, target, points) { + var pdps_mult = (target && target.pdps_mult) || (target && target.difficulty) || 1; + player.pdps += pdps_mult * points; + player.pdps_mult = pdps_mult; + // console.log([player.pdps,pdps_mult]); } -function add_pdps(player,target,points) -{ - var pdps_mult=(target&&target.pdps_mult)||(target&&target.difficulty)||1; - player.pdps+=pdps_mult*points; - player.pdps_mult=pdps_mult; - // console.log([player.pdps,pdps_mult]); +function add_coop_points(m, attacker, mnet) { + if (!m) { + return; + } + if (m["1hp"]) { + mnet = 1; + } + if (attacker.s.hopsickness) { + mnet /= 4; + } + m.points[attacker.name] = (m.points[attacker.name] || 0) + mnet; + attacker.s.coop = { ms: 12 * 60 * 1000, id: m.id, p: m.points[attacker.name] }; } -function add_coop_points(m,attacker,mnet) -{ - if(!m) return; - if(m["1hp"]) mnet=1; - if(attacker.s.hopsickness) mnet/=4; - m.points[attacker.name]=(m.points[attacker.name]||0)+mnet; - attacker.s.coop={ms:12*60*1000,id:m.id,p:m.points[attacker.name]}; +function level_monster(monster, args) { + if (!args) { + args = {}; + } + var mult = 1; + if (args.delevel) { + mult = -1; + } + monster.xp += G.monsters[monster.type].xp * mult; + monster.max_hp += parseInt(G.monsters[monster.type].hp / 2) * mult; + if (mult > 0) { + monster.hp += parseInt(G.monsters[monster.type].hp / 2) * mult; + } else { + monster.hp = min(monster.hp, monster.max_hp); + } + monster.level += 1 * mult; + monster.u = true; + monster.abs = true; + monster.moving = false; + monster.cid++; + monster.last_level = future_s(Math.random() * 100 - 50); + if (mode.fast_mlevels) { + monster.last_level = new Date(); + } + if (!args.silent) { + calculate_monster_stats(monster); + xy_emit(monster, "ui", { id: monster.id, type: "mlevel", mult: mult }); + } + monster.luckx += 0.25 * mult; } -function level_monster(monster,args) -{ - if(!args) args={}; - var mult=1; - if(args.delevel) mult=-1; - monster.xp+=G.monsters[monster.type].xp*mult; - monster.max_hp+=parseInt(G.monsters[monster.type].hp/2)*mult; - if(mult>0) monster.hp+=parseInt(G.monsters[monster.type].hp/2)*mult; - else monster.hp=min(monster.hp,monster.max_hp); - monster.level+=1*mult; - monster.u=true; - monster.abs=true; - monster.moving=false; - monster.cid++; - monster.last_level=future_s(Math.random()*100-50); - if(mode.fast_mlevels) monster.last_level=new Date(); - if(!args.silent) - { - calculate_monster_stats(monster); - xy_emit(monster,"ui",{id:monster.id,type:"mlevel",mult:mult}); - } - monster.luckx+=0.25*mult; +function remove_monster(target, args) { + if (!args) { + args = {}; + } + if (!args.method) { + args.method = "death"; + } + target.dead = true; + target.dc = true; + if ( + G.monsters[target.type].respawn != -1 && + !G.monsters[target.type].special && + !target.special && + !target.map_def.special && + !target.pet && + !target.spawn && + !args.nospawn && + !target.temp + ) { + if (target.map_def.grow && (target.map_def.live || 0) <= (target.map_def.count * 2) / 3) { + setTimeout(new_monster_f(target.oin, target.map_def, { before_respawn: target }), 25); + } else if (G.monsters[target.type].respawn > 200) { + setTimeout( + new_monster_f(target.oin, target.map_def, { before_respawn: target }), + round(G.monsters[target.type].respawn * (720 + Math.random() * 480)), + ); + } else { + setTimeout( + new_monster_f(target.oin, target.map_def, { before_respawn: target }), + round(G.monsters[target.type].respawn * 1000 + Math.random() * 900), + ); + } // previously Math.random()*2000 + } + var luckm = undefined; + if (target.target) { + var player = players[name_to_id[target.target]]; + if (player) { + luckm = player.luckm; + if (!args.no_decrease) { + reduce_targets(player, target); + } + } + } + if (!args.silent) { + xy_emit(target, args.method, { + id: target.id, + luckm: luckm || 1, + points: (target.cooperative && target.points) || undefined, + }); + } + delete instances[target.in].monsters[target.id]; + if (!args.nospawn) { + target.map_def.live--; + monster_c[target.type]--; + } } -function remove_monster(target,args) -{ - if(!args) args={}; - if(!args.method) args.method="death"; - target.dead=true; - target.dc=true; - if(G.monsters[target.type].respawn!=-1 && !G.monsters[target.type].special && !target.special && !target.map_def.special && !target.pet && !target.spawn && !args.nospawn && !target.temp) - { - if(target.map_def.grow && (target.map_def.live||0)<=target.map_def.count*2/3) setTimeout(new_monster_f(target.oin,target.map_def,{before_respawn:target}),25); - else if(G.monsters[target.type].respawn>200) setTimeout(new_monster_f(target.oin,target.map_def,{before_respawn:target}),round(G.monsters[target.type].respawn*(720+Math.random()*480))); - else setTimeout(new_monster_f(target.oin,target.map_def,{before_respawn:target}),round(G.monsters[target.type].respawn*1000+Math.random()*900)); // previously Math.random()*2000 - } - var luckm=undefined; - if(target.target) - { - var player=players[name_to_id[target.target]]; - if(player) - { - luckm=player.luckm; - if(!args.no_decrease) - reduce_targets(player,target); - } - } - if(!args.silent) xy_emit(target,args.method,{id:target.id,luckm:luckm||1,points:target.cooperative&&target.points||undefined}); - delete instances[target.in].monsters[target.id]; - if(!args.nospawn) - { - target.map_def.live--; - monster_c[target.type]--; - } +function new_monster(instance, map_def, args) { + if (!instances[instance]) { + return; + } //otherwise late dungeon spawns bring down the server + if (!args) { + args = {}; + } + var monster = {}; + var id = ++total_monsters + ""; + var name = map_def.type; + monster.id = id; + monster.cid = 1; + monster.mult = 1; // gets reduced during a server restart + monster.m = 0; + monster.outgoing = 0; + monster.u = true; + monster.type = name; + if (args.before_respawn && G.monsters[args.before_respawn.type] && G.monsters[args.before_respawn.type].respawn_as) { + name = monster.type = G.monsters[args.before_respawn.type].respawn_as; + (G.maps[instances[instance].map].monsters || []).forEach(function (pack) { + if (pack.type == name) { + map_def.gold = pack.gold; + } + }); + } + var monster_def = G.monsters[monster.type]; + monster.s = {}; + monster.a = {}; + monster.last = { attacked: new Date() }; + monster.level = 1; + monster.luckx = 1; + monster.xrange = 0; + if (map_def.stype == "randomrespawn") { + var boundary = map_def.boundaries[parseInt(floor(Math.random() * map_def.boundaries.length))]; + if (!server.live) { + boundary = map_def.boundaries[0]; + } // to prevent referencing un-created instances + instance = boundary[0]; + map_def.boundary = [boundary[1], boundary[2], boundary[3], boundary[4]]; + } + + monster.gold = map_def.gold; + + if (G.dimensions[name]) { + monster.width = G.dimensions[name][0]; + monster.height = G.dimensions[name][1]; + } + set_base(monster); + + if (map_def.random) { + var spot = random_place(instances[instance].map); + monster.x = spot.x; + monster.y = spot.y; + } else if (map_def.stype == "pet") { + monster.pet = true; + monster.x = map_def.x; + monster.y = map_def.y; + monster.owner = map_def.owner; + monster.name = map_def.name; + } else if (map_def.stype == "trap") { + monster.trap = true; + monster.x = map_def.x; + monster.y = map_def.y; + monster.owner = map_def.owner; + } else if (map_def.stype == "spawn") { + monster.spawn = true; + monster.x = map_def.x; + monster.y = map_def.y; + monster.master = map_def.master; + } else if (map_def.polygon) { + var p = random_point(map_def.polygon, monster.base); + var times = 0; + while (!is_xy_safe(instances[instance].map, p[0], p[1])) { + p = random_point(map_def.polygon, monster.base); + times++; + if (times > 40) { + break; + } + } + monster.x = p[0]; + monster.y = p[1]; + } else if (map_def.position) { + monster.x = map_def.position[0] + Math.random() * (2 * map_def.radius) - map_def.radius; + monster.y = map_def.position[1] + Math.random() * (2 * map_def.radius) - map_def.radius; + } else if (map_def.boundary) { + monster.x = map_def.boundary[0] + Math.random() * (map_def.boundary[2] - map_def.boundary[0]); + monster.y = map_def.boundary[1] + Math.random() * (map_def.boundary[3] - map_def.boundary[1]); + } + [ + "speed", + "xp", + "hp", + "attack", + "range", + "frequency", + "damage_type", + "aggro", + "evasion", + "avoidance", + "reflection", + "armor", + "resistance", + "dreturn", + "rage", + "apiercing", + "rpiercing", + "immune", + "cooperative", + "peaceful", + "drop_on_hit", + "global", + "1hp", + "heal", + "spawns", + "lifesteal", + "manasteal", + "rbuff", + "cbuff", + "projectile", + "slots", + "crit", + "humanoid", + "explosion", + "for", + "difficulty", + "phresistance", + ].forEach(function (prop) { + if (prop in monster_def) { + monster[prop] = monster_def[prop]; + } + }); + monster.mp = ceil((monster.hp * 2) / 100); + if (monster_def.s) { + monster.s = clone(monster_def.s); + } + if (monster_def.abilities) { + monster.a = monster_def.abilities; + } + if (monster_def.poisonous) { + monster.s.poisonous = { ms: 99999999999999 }; + } + if (mode.range_test) { + monster.range = 2000; + } + monster.width = monster.height = 24; + if (args.temp) { + monster.temp = 1; + } + if (map_def.rage) { + monster.rid = map_def.id; + } + monster.points = {}; + for (var a in monster.a) { + if (monster.a[a].cooldown) { + monster.s[a] = { ms: parseInt(monster.a[a].cooldown * Math.random()), ability: true }; + } + } + monster.s.young = { ms: 500 }; + monster.is_monster = true; + monster.max_hp = monster_def.hp; + monster.oin = instance; + monster.in = instance; + monster.map = instances[instance].map; + monster.map_def = map_def; + monster.last_level = future_s(Math.random() * 100 - 50); + if (mode.fast_mlevels) { + monster.last_level = new Date(); + } + monster.last.attack = new Date(); + monster.last.attacked = really_old; + monster.hits = 0; + if (args.last_state) { + while (monster.level < args.last_state.level) { + level_monster(monster, { silent: true }); + } + ["hp", "level", "s", "temp", "points", "m", "extra_gold", "outgoing", "id"].forEach(function (p) { + if (args.last_state[p]) { + monster[p] = args.last_state[p]; + } + }); + calculate_monster_stats(monster); + } + if (args.before_respawn) { + while (monster.level < args.before_respawn.level / 2) { + level_monster(monster, { silent: true }); + } + calculate_monster_stats(monster); + } + if (monster.aggro) { + monster.last_aggro = new Date(); + } + instances[instance].monsters[monster.id] = monster; + if (!args.last_state) { + map_def.live = (map_def.live || 0) + 1; + monster_def.c = (monster_def.c || 0) + 1; + monster_c[monster.type] = (monster_c[monster.type] || 0) + 1; + } + if (map_def.grow && server.live && map_def.live <= map_def.count / 2) { + new_monster(instance, map_def, { temp: 1 }); + new_monster(instance, map_def, { temp: 1 }); + } + if (!args.last_state && monster_def.announce && server.live) { + broadcast("server_message", { + color: monster_def.announce, + message: monster_def.name + " spawned in " + G.maps[monster.map].name + "!", + discord: "orange", + }); + if (monster.cooperative) { + broadcast("server_message", { + color: monster_def.announce, + message: "Join the fight against " + monster_def.name + "!", + event: true, + }); + } + } + if (Object.keys(monster.s).length) { + calculate_monster_stats(monster); + } + if (map_def.stype == "spawn") { + target_player(monster, get_player(map_def.target)); + } + return monster; } -function new_monster(instance,map_def,args) -{ - if(!instances[instance]) return; //otherwise late dungeon spawns bring down the server - if(!args) args={}; - var monster={},id=++total_monsters+"",name=map_def.type; - monster.id=id; - monster.cid=1; - monster.mult=1; // gets reduced during a server restart - monster.m=0; - monster.outgoing=0; - monster.u=true; - monster.type=name; - if(args.before_respawn && G.monsters[args.before_respawn.type] && G.monsters[args.before_respawn.type].respawn_as) - { - name=monster.type=G.monsters[args.before_respawn.type].respawn_as; - (G.maps[instances[instance].map].monsters||[]).forEach(function(pack){ - if(pack.type==name) - map_def.gold=pack.gold; - }); - } - var monster_def=G.monsters[monster.type]; - monster.s={}; - monster.a={}; - monster.last={attacked:new Date()}; - monster.level=1; - monster.luckx=1; - monster.xrange=0; - if(map_def.stype=="randomrespawn") - { - var boundary=map_def.boundaries[parseInt(floor(Math.random()*map_def.boundaries.length))]; - if(!server.live) boundary=map_def.boundaries[0]; // to prevent referencing un-created instances - instance=boundary[0]; - map_def.boundary=[boundary[1],boundary[2],boundary[3],boundary[4]]; - } - - monster.gold=map_def.gold; - - if(G.dimensions[name]) - { - monster.width=G.dimensions[name][0]; - monster.height=G.dimensions[name][1]; - } - set_base(monster); - - if(map_def.random) - { - var spot=random_place(instances[instance].map); - monster.x=spot.x; - monster.y=spot.y; - } - else if(map_def.stype=="pet") - { - monster.pet=true; - monster.x=map_def.x; - monster.y=map_def.y; - monster.owner=map_def.owner; - monster.name=map_def.name; - } - else if(map_def.stype=="trap") - { - monster.trap=true; - monster.x=map_def.x; - monster.y=map_def.y; - monster.owner=map_def.owner; - } - else if(map_def.stype=="spawn") - { - monster.spawn=true; - monster.x=map_def.x; - monster.y=map_def.y; - monster.master=map_def.master; - } - else if(map_def.polygon) - { - var p=random_point(map_def.polygon,monster.base),times=0; - while(!is_xy_safe(instances[instance].map,p[0],p[1])) - { - p=random_point(map_def.polygon,monster.base); - times++; if(times>40) break; - } - monster.x=p[0]; monster.y=p[1]; - } - else if(map_def.position) - { - monster.x=map_def.position[0]+Math.random()*(2*map_def.radius)-map_def.radius; - monster.y=map_def.position[1]+Math.random()*(2*map_def.radius)-map_def.radius; - } - else if(map_def.boundary) - { - monster.x=map_def.boundary[0]+Math.random()*(map_def.boundary[2]-map_def.boundary[0]); - monster.y=map_def.boundary[1]+Math.random()*(map_def.boundary[3]-map_def.boundary[1]); - } - ["speed","xp","hp","attack","range","frequency","damage_type","aggro","evasion","avoidance","reflection","armor","resistance","dreturn","rage","apiercing","rpiercing","immune","cooperative","peaceful","drop_on_hit","global","1hp","heal","spawns","lifesteal","manasteal","rbuff","cbuff","projectile","slots","crit","humanoid","explosion","for","difficulty","phresistance"].forEach(function(prop){ - if(prop in monster_def) - monster[prop]=monster_def[prop]; - }); - monster.mp=ceil(monster.hp*2/100); - if(monster_def.s) monster.s=clone(monster_def.s); - if(monster_def.abilities) monster.a=monster_def.abilities; - if(monster_def.poisonous) monster.s.poisonous={ms:99999999999999}; - if(mode.range_test) monster.range=2000; - monster.width=monster.height=24; - if(args.temp) - { - monster.temp=1; - } - if(map_def.rage) - { - monster.rid=map_def.id; - } - monster.points={}; - for(var a in monster.a) - if(monster.a[a].cooldown) - monster.s[a]={ms:parseInt(monster.a[a].cooldown*Math.random()),ability:true}; - monster.s.young={ms:500}; - monster.is_monster=true; - monster.max_hp=monster_def.hp; - monster.oin=instance; - monster.in=instance; - monster.map=instances[instance].map; - monster.map_def=map_def; - monster.last_level=future_s(Math.random()*100-50); - if(mode.fast_mlevels) monster.last_level=new Date(); - monster.last.attack=new Date(); - monster.last.attacked=really_old; - monster.hits=0; - if(args.last_state) - { - while(monster.level attacker.max_hp * 2.4 * mult) { + lost_xp -= attacker.max_hp * 2.4 * mult; + lost_xp *= 0.9; + if (!attacker.dead) { + level_monster(attacker); + } + } } -function defeated_by_a_monster(attacker,player) -{ - var divider=1; - if(is_in_pvp(player) && !(!is_pvp && G.maps[player.map].safe_pvp)) divider=10; - var lost_xp=floor(min(max(player.max_xp*0.01/divider,player.xp*0.02/divider),player.xp)); - // Originally all of them are divided by player.xpm [05/06/19] - for(var i=0;iattacker.max_hp*2.4*mult) - { - lost_xp-=attacker.max_hp*2.4*mult; - lost_xp*=0.9; - if(!attacker.dead) level_monster(attacker); - } +function can_attack(monster, player) { + if (is_disabled(monster)) { + return false; + } + if (player == "aggro") { + return ( + mssince(monster.last_aggro) > max(1200, 1200 / monster.frequency) && + mssince(monster.last.attack) > 1000 / monster.frequency + ); + } //aggro check is arbitrary + if (!player) { + return mssince(monster.last.attack) > 1000 / monster.frequency; + } + return distance(player, monster, true) < monster.range && mssince(monster.last.attack) > 1000 / monster.frequency; } -function can_attack(monster,player) -{ - if(is_disabled(monster)) return false; - if(player=="aggro") return mssince(monster.last_aggro)>max(1200,1200/monster.frequency) && mssince(monster.last.attack)>1000/monster.frequency; //aggro check is arbitrary - if(!player) return mssince(monster.last.attack)>1000/monster.frequency; - return distance(player,monster,true)1000/monster.frequency; +function rage_logic(instance) { + if (!instance.rage_list.length || (instance.last_rage && mssince(instance.last_rage) < 4200)) { + return; + } + instance.last_rage = new Date(); + instance.rage_list.forEach(function (map_def) { + for (var id in instance.players) { + var player = instance.players[id]; + if ( + player && + !player.npc && + !player.rip && + !is_invinc(player) && + map_def.rage[0] <= player.x && + player.x <= map_def.rage[2] && + map_def.rage[1] <= player.y && + player.y <= map_def.rage[3] + ) { + if (Math.random() < player.aggro_diff) { + return; + } + for (var id in instance.monsters) { + var monster = instance.monsters[id]; + if (monster.rid == map_def.id) { + if ( + (!monster.s.sleeping && !monster.target) || + (monster.target && get_player(monster.target) && !is_same(player, get_player(monster.target), 1)) + ) { + if (monster.target && get_player(monster.target)) { + stop_pursuit(monster, { redirect: true, cause: "redirect" }); + } + target_player(monster, player); + monster.walk_once = true; + } + } + } + return; + } + } + }); } -function rage_logic(instance) -{ - if(!instance.rage_list.length || instance.last_rage && mssince(instance.last_rage)<4200) return; - instance.last_rage=new Date(); - instance.rage_list.forEach(function(map_def){ - for(var id in instance.players) - { - var player=instance.players[id]; - if(player && !player.npc && !player.rip && !is_invinc(player) && map_def.rage[0]<=player.x && player.x<=map_def.rage[2] && map_def.rage[1]<=player.y && player.y<=map_def.rage[3]) - { - if(Math.random() 380)) { + focus = monster.focus = null; + monster.cid++; + monster.u = true; + change = true; + } + if (monster.focus) { + change = true; + } // better to re-calculate for now, for charge speed changes + for (var name in monster.s) { + var def = G.conditions[name]; + var ref = monster.s[name]; + var value = monster.s[name].ms; + monster.s[name].ms -= ms; + if (def && def.interval) { + if (!monster.s[name].last || mssince(monster.s[name].last) >= def.interval) { + monster.s[name].last = new Date(); + if (name == "eburn") { + var damage = G.conditions.eburn.damage; + if (monster.immune) { + damage = 0; + } + disappearing_text({}, monster, "-" + damage, { color: "red", xy: 1 }); + monster.hp = max(1, monster.hp - damage); + } + if (name == "eheal") { + var heal = G.conditions.eheal.heal; + if (monster.immune) { + heal = 0; + } + disappearing_text({}, monster, "+" + heal, { color: "heal", xy: 1 }); + monster.hp = min(monster.max_hp, monster.hp + heal); + } + if (name == "burned") { + var damage = ceil(ref.intensity / 5); + //disappearing_text({},monster,"-"+damage,{color:"burn",xy:1}); + monster.hp = max(0, monster.hp - damage); + xy_emit(monster, "hit", { + source: "burn", + hid: ref.f, + id: monster.id, + damage: damage, + kill: monster.hp <= 0, + }); + var attacker = (monster.target && get_player(monster.target)) || get_player(ref.f); + var burner = get_player(ref.f); + if (burner) { + add_pdps(burner, monster, damage); + if (monster.cooperative) { + add_coop_points(monster, burner, damage); + } + } + if (monster.hp <= 0) { + if (burner) { + achievement_logic_burn_last_hit(burner); + } + kill_monster(attacker, monster); + } + } + monster.u = true; + monster.cid++; + } + } + if (monster.s[name].ms <= 0) { + if (monster.a[name] && monster.a[name].cooldown) { + monster.s[name].ms = monster.a[name].cooldown; + } else { + delete monster.s[name]; + } + if (is_disabled(monster) && G.skills[name] && !G.skills[name].passive) { + continue; + } + if (name != "young") { + monster.u = true; + monster.cid++; + change = true; + } + if (name == "self_healing") { + var hp = monster.hp; + var heal = monster.a.self_healing.heal; + if (monster.s.poisoned) { + heal /= 2; + } + monster.hp = min(monster.max_hp, monster.hp + heal); + if (hp != monster.hp) { + events.push(["ui", { type: "mheal", id: id, heal: monster.hp - hp }]); + } + } + if (name == "healing") { + var target = monster; + if (focus && distance(focus, monster) < 120) { + target = focus; + } + var hp = target.hp; + var heal = monster.a.healing.heal; + if (target.s.poisoned) { + heal /= 2; + } + target.hp = min(target.max_hp, target.hp + heal); + if (hp != target.hp) { + events.push(["ui", { type: "mheal", id: target.id, heal: target.hp - hp }]); + } + } + if (name == "mtangle") { + if (monster.target && get_player(monster.target)) { + var player = get_player(monster.target); + add_condition(player, "tangled"); + resend(player, "u+cid"); + } + } + if (name == "multi_burn") { + if (monster.cooperative) { + for (var name in monster.points || {}) { + var player = get_player(name); + if (player && simple_distance(monster, player) < 480) { + commence_attack(monster, player, "fireball"); + } + } + } else { + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (distance(player, monster) < 480) { + commence_attack(monster, player, "fireball"); + } + } + } + monster.cid++; + monster.u = true; + change = true; + } + if (name == "multi_freeze") { + for (var name in monster.points || {}) { + var player = get_player(name); + if (player && simple_distance(monster, player) < 480) { + commence_attack(monster, player, "frostball"); + } + } + monster.cid++; + monster.u = true; + change = true; + } + if (name == "degen") { + monster.hp -= 60; + monster.cid++; + monster.u = true; + change = true; + if (monster.hp <= 0) { + monster.hp = 0; + remove_monster(monster); + } + } + if (name == "zap") { + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (distance(player, monster) < monster.a[name].radius) { + commence_attack(monster, player, "zap"); + } + } + } + if (monster.a && monster.a[name] && monster.a[name].aura) { + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (distance(player, monster) < monster.a[name].radius) { + player.s[monster.a[name].condition] = { ms: G.conditions[monster.a[name].condition].duration }; + resend(player, "u+cid"); + } + } + } + if (name == "deepfreeze") { + var c = []; + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (!player.rip && !player.invis && distance(player, monster) < monster.a[name].radius && !player.npc) { + c.push(player); + } + } + var theone = random_one(c); + if (theone) { + commence_attack(monster, theone, "deepfreeze"); + } + } + if (name == "anger") { + var c = []; + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (!player.rip && !player.invis && distance(player, monster) < monster.a[name].radius) { + c.push(player); + } + } + var theone = random_one(c); + if (theone) { + if (monster.target && get_player(monster.target)) { + stop_pursuit(monster); + } + target_player(monster, theone); + } + } + if (name == "warpstomp") { + var dampened = false; + for (var id in instances[monster.in].monsters) { + var m = instances[monster.in].monsters[id]; + if (m.type == "fieldgen0" && point_distance(monster.x, monster.y, m.x, m.y) < 300) { + monster.s.dampened = { ms: 2000 }; + dampened = true; + } + } + if (!dampened) { + var c = []; + for (var id in instances[monster.in].players) { + var player = instances[monster.in].players[id]; + if (!player.rip && !player.invis && distance(player, monster) < monster.a[name].radius) { + c.push(player); + } + } + var theone = random_one(c); + if (theone) { + if (monster.target && get_player(monster.target)) { + stop_pursuit(monster); + } + target_player(monster, theone); + port_monster(monster, theone, { stomp: 160 }); + } + } + } + if (name == "mlight") { + xy_emit(monster, "light", { name: monster.id }); + } + if (name == "stone") { + if (monster.target && get_player(monster.target)) { + var player = get_player(monster.target); + add_condition(player, "stoned"); + resend(player, "u+cid"); + } + } + if (name == "magiport") { + var r = false; + if (monster.map != ref.map) { + r = true; + } + transport_monster_to(monster, ref.in, ref.map, ref.x, ref.y); + if (ref.stomp) { + for (var id in instances[monster.in].players) { + var target = instances[monster.in].players[id]; + var dist = simple_distance(monster, target); + if ( + dist < 160 && + !target.npc && + !target.s.invincible && + add_condition(target, "stunned", { duration: 1500 }) + ) { + resend(target); + } + } + } + if (r) { + continue; + } + } + if (name == "sleeping" && E.schedule.night && Math.random() < 0.9) { + monster.s.sleeping = { ms: 3000 + 5000 * Math.random() }; + monster.u = true; + monster.cid++; + } + } + } + if (monster.dead) { + continue; + } + if (G.monsters[monster.type].supporter && !monster.focus) { + for (var mid in instance.monsters) { + var m = instance.monsters[mid]; + if ( + !m.focus && + m != monster && + G.monsters[monster.type].humanoid == G.monsters[m.type].humanoid && + distance(m, monster) < 300 + ) { + monster.focus = m.id; + change = true; + break; + } + } + } + if (change) { + calculate_monster_stats(monster); + } + if ( + !monster.pet && + !monster.trap && + mode.aggro && + !monster.target && + monster.aggro && + can_attack(monster, "aggro") + ) { + monster.last_aggro = new Date(); + if (monster.aggro > 0.99 || Math.random() < monster.aggro) { + set_ghash(aggressives, monster, 32); + } + } + if (monster.target && monster.spawns && get_player(monster.target) && !is_disabled(monster)) { + monster.spawns.forEach(function (spi) { + var interval = spi[0]; + var name = spi[1]; + if (!monster.last[name] || mssince(monster.last[name]) > interval) { + var pname = random_one(Object.keys(monster.points)); + var player = get_player(pname); + if (!player || player.npc || distance(monster, player) > 400) { + return; + } + if (!is_same(player, get_player(monster.target), true)) { + return; + } + monster.last[name] = new Date(); + var spot = safe_xy_nearby(player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10); + if (!spot) { + return; + } + new_monster(instance.name, { + type: name, + stype: "spawn", + x: spot.x, + y: spot.y, + target: player.name, + master: monster.id, + }); + } + }); + } + function attack_target_or_move() { + var player = players[name_to_id[monster.target]]; + if (player && ssince(monster.last.attacked) > 20 && Math.random() > monster.rage * 0.99) { + stop_pursuit(monster, { force: true, cause: "bored" }); + return; + } + if (focus && distance(focus, monster) > 40 && !monster.moving) { + if (mode.all_smart) { + if (!monster.worker) { + monster.working = true; + workers[wlast++ % workers.length].postMessage({ + type: "fast_astar", + in: monster.in, + id: monster.id, + map: monster.map, + sx: monster.x, + sy: monster.y, + tx: focus.x, + ty: focus.y, + }); + } + } else { + monster.ogoing_x = monster.going_x; + monster.ogoing_y = monster.going_y; + monster.going_x = monster.x + (focus.x - monster.x) / 2; + monster.going_y = monster.y + (focus.y - monster.y) / 2; + if (mode.path_checks && !can_move(monster)) { + monster.going_x = monster.ogoing_x; + monster.going_y = monster.ogoing_y; + } else { + start_moving_element(monster); + } + } + } + if (player && player.in == monster.in && !player.rip && !is_invis(player)) { + if ( + distance(player, monster, true) > + min(monster.range / 1.6, 240) + min(100, monster.attack / 5.0) + 160 + ((mode.all_smart && 320) || 1) && + !monster.walk_once + ) { + stop_pursuit(monster, { cause: "exceeds_range" }); + } else if (can_attack(monster, player)) { + var attack = commence_attack(monster, player, "attack"); + if (attack && attack.events && attack.events.length) { + events.push(...attack.events); + } + } else if ( + distance(monster, player, true) > 12 && + !mode.range_test && + !(mode.all_smart && monster.moving) && + !focus + ) { + // console.log(monster.height); + if (mode.all_smart) { + if (!monster.worker) { + monster.working = true; + workers[wlast++ % workers.length].postMessage({ + type: "fast_astar", + in: monster.in, + id: monster.id, + map: monster.map, + sx: monster.x, + sy: monster.y, + tx: player.x, + ty: player.y, + }); + } + } else { + monster.ogoing_x = monster.going_x; + monster.ogoing_y = monster.going_y; + monster.going_x = monster.x + (player.x - monster.x) / 2; + monster.going_y = monster.y + (player.y - monster.y) / 2; + if (mode.path_checks && !can_move(monster)) { + monster.going_x = monster.ogoing_x; + monster.going_y = monster.ogoing_y; + if (monster.attack < 120 || distance(monster, player, true) > monster.range) { + stop_pursuit(monster, { cause: "cant_move" }); + } + } else { + // console.log("Moving to "+monster.going_x+" "+monster.going_y); + start_moving_element(monster); + } + } + } + } else if (monster.target) { + stop_pursuit(monster, { cause: "player_gone" }); + } + if (monster.walk_once) { + monster.walk_once = false; + } + } + if (monster.moving) { + var ms = mssince(monster.last.move); + ms = min(ms, 2000); // to prevent monsters from jumping off the map when the machine sleeps + monster.x += (monster.vx * ms) / 1000.0; + monster.y += (monster.vy * ms) / 1000.0; + stop_logic(monster); + monster.last.move = new Date(); + xy_u_logic(monster); + + if (monster.moving && monster.attack > 100 && monster.target) { + attack_target_or_move(); + } + } else if (monster.s.sleeping || monster.working) { + } else if (can_walk(monster)) { + // for the .s.stunned check + if (monster.s.magiport) { + } else if (monster.target || focus) { + attack_target_or_move(); + } else if (!mode.upush_test) { + if (monster.map_def.position && !mode.all_roam) { + var position = monster.map_def.position; + var radius = monster.map_def.radius; + var dx = Math.random() * radius - radius / 2; + var dy = Math.random() * radius - radius / 2; + if (abs(dx) + abs(dy) < 80) { + // optimization to prevent short walks + if (dx < 0 && dx > -60) { + dx = -60; + } + if (dx > 0 && dx < 60) { + dx = 60; + } + if (dy < 0 && dy > -60) { + dy = -60; + } + if (dy > 0 && dy < 60) { + dy = 60; + } + } + monster.going_x = monster.x + dx; + monster.going_y = monster.y + dy; + if (monster.going_x > position[0] + radius) { + monster.going_x = position[0] + radius; + } + if (monster.going_y > position[1] + radius) { + monster.going_y = position[1] + radius; + } + if (monster.going_x < position[0] - radius) { + monster.going_x = position[0] - radius; + } + if (monster.going_y < position[1] - radius) { + monster.going_y = position[1] - radius; + } + start_moving_element(monster); + } else if ( + monster.map_def.roam || + G.monsters[monster.type].roam || + mode.all_roam || + (monster.map_def.polygon && !monster.irregular) + ) { + perfc.roams += 1; + var map_def = monster.map_def; + var tries = 1; + if (!monster.map_def.roam && !mode.all_roam && monster.map_def.polygon) { + tries = 12; + } + if (!monster.rmove) { + monster.rmove = parseInt(Math.random() * 100); + } + monster.dmove = monster.dmove || 0; + for (var t = 0; t < tries; t++) { + var moves = [ + [1, 0], + [0.8, 0.8], + [0, 1], + [-0.8, 0.8], + [-1, 0], + [-0.8, -0.8], + [0, -1], + [0.8, -0.8], + ]; + var multipliers = [500, 200, 100, 50, 10]; + if (Math.random() < 0.1) { + monster.rmove = parseInt(Math.random() * moves.length); + } + var move = moves[monster.rmove % moves.length]; + var d = multipliers[monster.dmove % multipliers.length]; + monster.going_x = monster.x + move[0] * d; + monster.going_y = monster.y + move[1] * d; + if (tries > 1 && monster.map_def.polygon) { + if (is_point_inside([monster.going_x, monster.going_y], monster.map_def.polygon)) { + break; + } else { + monster.rmove++; + monster.dmove++; + } + } + } + // console.log(monster.rmove+","+monster.dmove+" "+monster.going_x+","+monster.going_y); + if (can_move(monster)) { + start_moving_element(monster); + monster.dmove = 0; + } else { + monster.rmove++; + monster.dmove++; + } + } else if ( + monster.map_def.stype != "pet" && + monster.map_def.stype != "spawn" && + monster.map_def.stype != "trap" + ) { + var map_def = monster.map_def; + var to_move = true; + if (map_def.polygon) { + var p = random_point(map_def.polygon, monster.base); + monster.going_x = p[0]; + monster.going_y = p[1]; + } else { + monster.going_x = map_def.boundary[0] + Math.random() * (map_def.boundary[2] - map_def.boundary[0]); + monster.going_y = map_def.boundary[1] + Math.random() * (map_def.boundary[3] - map_def.boundary[1]); + } + if (monster.irregular == 3) { + // new [01/03/19] + monster.m++; + setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500); + remove_monster(monster, { nospawn: true, method: "disappear" }); + } else if (monster.irregular == 2) { + server_log("Irregular2 move for " + monster.id); + if (monster.in != monster.oin) { + port_monster(monster, { map: monster.oin, x: monster.going_x, y: monster.going_y, in: monster.oin }); + } else { + recalculate_move(monster); + } + monster.irregular = 1; + } else if (monster.irregular == 1) { + if (!can_move(monster)) { + monster.m++; + server_log("Irregular1 respawn: " + monster.id); + setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500); + remove_monster(monster, { nospawn: true, method: "disappear" }); + } else { + server_log("No longer irregular: " + monster.id); + delete monster["irregular"]; + } + } + if (monster.going_x != monster.x || monster.going_y != monster.y) { + // so Automatron's don't move + start_moving_element(monster); + } + } + } + } + if (!monster.dead && (monster.u || events.length)) { + monster.u = false; + to_push.push({ id: id, entity: monster, data: monster_to_client(monster, events) }); + monster_map[id] = to_push[to_push.length - 1]; + } + } + + for (var id in instance.players) { + var player = instance.players[id]; + if (!player) { + continue; + } + for (var name in player.s) { + var def = G.conditions[name]; + var ref = player.s[name]; + var value = player.s[name].ms; + player.s[name].ms -= ms; + if (def && def.interval) { + if (!player.s[name].last || mssince(player.s[name].last) >= def.interval) { + player.s[name].last = new Date(); + if (name == "eburn") { + disappearing_text(player.socket, player, "-50", { color: "red", xy: 1 }); + player.hp = max(1, player.hp - 50); + } + if (name == "eheal") { + disappearing_text(player.socket, player, "+50", { color: "heal", xy: 1 }); + player.hp = min(player.max_hp, player.hp + 50); + } + if (name == "burned") { + var damage = ceil(ref.intensity / 5); + disappearing_text(player.socket, player, "-" + damage, { color: "red", xy: 1 }); + player.hp = max(0, player.hp - damage); + xy_emit(player, "hit", { + source: "burn", + hid: ref.fid, + id: player.name, + damage: damage, + kill: player.hp <= 0, + }); + player_rip_logic(player); + } + resend(player, "u+cid+nc"); + } + } + if (in_arr(name, ["damage_received"])) { + player.s[name].amount = max(0, (player.s[name].amount * (4000 - ms)) / 4000.0); + } + if (player.s[name].ms <= 0) { + delete player.s[name]; + if (name == "blink") { + if (player.s.dampened) { + xy_emit(player, "ui", { type: "dampened", name: player.name }); + } else { + decay_s(player, 30000); + transport_player_to(player, ref.in, [ref.x, ref.y, ref.d], "blink"); + } + } + if (name == "magiport") { + var dampened = false; + for (var id in instances[ref.in].monsters) { + var m = instances[ref.in].monsters[id]; + if (m.type == "fieldgen0" && point_distance(ref.x, ref.y, m.x, m.y) < 300) { + xy_emit(player, "ui", { type: "dampened", name: player.name }); + dampened = true; + break; + } + } + if (!dampened) { + var skip = false; + if (ref.map != player.map) { + skip = true; + } + decay_s(player, 30000); + transport_player_to(player, ref.in, [ref.x, ref.y], "magiport"); + if (skip) { + resend(player, "u+cid"); + return; // player isn't in this instance any more + } + } + } + if (def) { + player.socket.emit("game_response", { response: "ex_condition", name: name }); + } + if (def && def.ui) { + resend(player, "u+cid"); + } // +cid to update the UI's [23/10/18] + else { + resend(player, "u"); + } // #TODO: don't need to resend actually, maybe reconsider [27/06/18] + } + } + for (var name in player.q) { + var value = player.q[name].ms; + var ref = player.q[name]; + player.q[name].ms -= ms; + if (name == "upgrade") { + if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + var def = player.items[ref.num].p; + var change = false; + if (value < ref.len * 0.8 && def.nums[0] === undefined) { + def.nums[0] = parseInt(player.p.u_roll * 10000) % 10; + change = true; + } + if (value < ref.len * 0.64 && def.nums[1] === undefined) { + def.nums[1] = parseInt(player.p.u_roll * 1000) % 10; + change = true; + } + if (value < ref.len * 0.4 && def.nums[2] === undefined) { + def.nums[2] = parseInt(player.p.u_roll * 100) % 10; + change = true; + } + if (value < min(3000, ref.len * 0.3) && def.nums[3] === undefined) { + def.nums[3] = parseInt(player.p.u_roll * 10); + change = true; + } + if (value < min(2200, ref.len * 0.22) && player.p.u_item && !player.p.u_fail && !def.success) { + def.success = true; + change = true; + } + if (value < min(2200, ref.len * 0.22) && (player.p.u_itemx || player.p.u_fail) && !def.failure) { + def.failure = true; + change = true; + } + if (change) { + player.socket.emit("q_data", { q: player.q, num: ref.num, p: def }); + } + } + } + if (name == "compound") { + if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + var def = player.items[ref.num].p; + var change = false; + if (value < 8000 && def.nums[0] === undefined) { + def.nums[0] = parseInt(player.p.c_roll * 10000) % 10; + change = true; + } + if (value < 6400 && def.nums[1] === undefined) { + def.nums[1] = parseInt(player.p.c_roll * 1000) % 10; + change = true; + } + if (value < 5000 && def.nums[2] === undefined) { + def.nums[2] = parseInt(player.p.c_roll * 100) % 10; + change = true; + } + if (value < 3000 && def.nums[3] === undefined) { + def.nums[3] = parseInt(player.p.c_roll * 10); + change = true; + } + if (value < 2200 && player.p.c_item && !def.success) { + def.success = true; + change = true; + } + if (value < 2200 && player.p.c_itemx && !def.failure) { + def.failure = true; + change = true; + } + if (change) { + player.socket.emit("q_data", { q: player.q, num: ref.num, p: def }); + } + } + } + if (player.q[name].ms <= 0) { + delete player.q[name]; + if (name == "exchange") { + if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + player.citems[ref.num] = player.items[ref.num] = null; + } + exchange(player, ref.id, { v: ref.v }); + if (ref.qs) { + player.socket.emit("game_response", { response: ref.qs + "_success", suffix: ref.s || "" }); + } else { + xy_emit(G.maps.main.exchange, "upgrade", { type: "exchange", success: 1 }); + } + resend(player, "reopen+u+cid"); + } + if (name == "compound") { + if (player.p.c_item) { + var item = player.p.c_item; + var def = G.items[item.name]; + player.hitchhikers.push([ + "game_response", + { + response: "compound_success", + stale: ref.stale, + level: item.level, + num: data.num, + up: item.extra || undefined, + }, + ]); + if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 1800000 && !player.stealth) { + broadcast("server_message", { + message: player.name + " received " + item_to_phrase(item), + color: colors.server_success, + item: cache_item(item), + type: "server_csuccess", + name: player.name, + }); + } + xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 1 }); + if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + player.items[ref.num] = item; + player.citems[ref.num] = cache_item(player.items[ref.num]); + } + achievement_logic_compound_success(player, item); + } else { + var item = player.p.c_itemx; + var def = G.items[item.name]; + player.hitchhikers.push([ + "game_response", + { response: "compound_fail", level: item.level, num: data.num, stale: ref.stale }, + ]); + if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 920000 && !player.stealth) { + broadcast("server_message", { + message: player.name + " lost " + item_to_phrase(item) + "'s", + color: colors.server_failure, + item: cache_item(item), + type: "server_cfail", + name: player.name, + }); + } + xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 0 }); + if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + player.citems[ref.num] = player.items[ref.num] = null; + player.esize++; + } + } + delete player.p.c_item; + delete player.p.c_itemx; + delete player.p.c_roll; + resend(player, "reopen+u+cid+nc+inv"); + } + if (name == "upgrade") { + var success = false; + var announce = false; + var new_level = player.p.u_level + 1; + var item = player.p.u_item || player.p.u_itemx; + var p = player.items[ref.num] && player.items[ref.num].p; + if (player.p.u_item && player.items[ref.num] && player.items[ref.num].name == "placeholder") { + success = true; + player.items[ref.num] = player.p.u_item; + player.citems[ref.num] = cache_item(player.items[ref.num]); + } else if (player.items[ref.num] && player.items[ref.num].name == "placeholder") { + player.citems[ref.num] = player.items[ref.num] = null; + player.esize++; + } + if (player.p.u_type == "offering") { + if (success) { + player.hitchhikers.push(["game_response", { response: "upgrade_offering_success", stale: ref.stale }]); + } + } else if (player.p.u_type == "stat") { + if (success) { + player.hitchhikers.push([ + "game_response", + { + response: "upgrade_success_stat", + stale: ref.stale, + stat_type: p && G.items[p.scroll].stat, + num: ref.num, + }, + ]); + } + } else if (!ref.silent) { + announce = true; + } + + if (success && !player.p.u_fail) { + if (announce) { + player.hitchhikers.push([ + "game_response", + { response: "upgrade_success", level: new_level, num: ref.num, stale: ref.stale }, + ]); + } + if (announce && calculate_item_value(item) > 4800000 && !player.stealth) { + broadcast("server_message", { + message: player.name + " received " + item_to_phrase(item), + color: colors.server_success, + item: cache_item(item), + type: "server_usuccess", + name: player.name, + }); + } + xy_emit((instances.main && G.maps.main.upgrade) || player, "upgrade", { type: "upgrade", success: 1 }); + achievement_logic_upgrade_success(player, item); + } else { + player.hitchhikers.push([ + "game_response", + { response: "upgrade_fail", level: new_level, num: ref.num, stale: ref.stale }, + ]); + if (announce && calculate_item_value(item) > 4800000 && !player.stealth) { + broadcast("server_message", { + message: player.name + " lost " + item_to_phrase(item), + color: colors.server_failure, + item: cache_item(item), + type: "server_ufail", + name: player.name, + }); + } + xy_emit((instances.main && G.maps.main.upgrade) || player, "upgrade", { type: "upgrade", success: 0 }); + } + delete player.p.u_item; + delete player.p.u_type; + delete player.p.u_itemx; + delete player.p.u_roll; + delete player.p.u_fail; + delete player.p.u_level; + resend(player, "reopen+u+cid+nc+inv"); + } + if (name == "slots") { + if (Math.random() < ((S.gold > 500000000 && D.odds.slots_good) || D.odds.slots)) { + var gold = 500000000; + player.gold += gold; + S.gold -= gold; + broadcast("server_message", { + message: player.name + " received " + to_pretty_num(gold) + " gold", + color: "gold", + }); + player.socket.emit("game_response", "slots_success"); + player.socket.emit("game_log", { message: "Received " + to_pretty_num(gold) + " gold", color: "gold" }); + // resend(player,"u+cid"); + } else { + player.socket.emit("game_response", "slots_fail"); + } + } + } + } + for (var name in player.c) { + player.c[name].ms -= ms; + if (player.c[name].ms <= 0) { + var ref = player.c[name]; + delete player.c[name]; + if (name == "town") { + decay_s(player, 4000); + player.last.town = new Date(); + transport_player_to(player, player.in, undefined, 1); + resend(player); + } + if (name == "pickpocket") { + var target = get_player(ref.target); + if (!target) { + player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "player_gone" }); + } else if (distance(player, target) > 20) { + player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "distance" }); + } else { + var num = floor(Math.random() * 42); + if (target.items[num] && target.items[num].v) { + var item = target.items[num]; + if (item.q && item.q > 1) { + target.items[num].q -= 1; + target.citems[num].q -= 1; + item = create_new_item(item.name); + } else { + target.citems[num] = target.items[num] = null; + } + item.v = new Date(); + add_item(player, item); + player.socket.emit("game_response", { response: "picked", slot: "mainhand", cevent: true }); + consume_skill(player, "pickpocket", true); + resend(player, "reopen"); + resend(target, "reopen"); + target.socket.emit("game_response", { response: "got_picked", cevent: true }); + } else { + player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "misfortune" }); + } + } + } + if (name == "fishing") { + if (Math.random() < 0.1) { + if (player.esize) { + exchange(player, ref.drop, { phrase: "Fished" }); + } + consume_skill(player, "fishing", true); + if (player.slots.mainhand) { + var prop = calculate_item_properties(player.slots.mainhand); + if (prop.breaks && Math.random() < max(0, prop.breaks / 100.0)) { + player.cid++; + player.u = true; + player.cslots.mainhand = player.slots.mainhand = null; + player.socket.emit("game_log", "Your rod broke down ..."); + player.socket.emit("game_response", { response: "data", cevent: "item_break", slot: "mainhand" }); + } + } + player.socket.emit("game_response", { response: "data", cevent: "fishing_success", slot: "mainhand" }); + resend(player, "reopen"); + } else { + player.socket.emit("ui", { name: player.name, type: "fishing_none", cevent: true }); + } + } + if (name == "mining") { + if (Math.random() < 0.2) { + if (player.esize) { + exchange(player, ref.drop, { phrase: "Mined" }); + } + consume_skill(player, "mining", true); + if (player.slots.mainhand) { + var prop = calculate_item_properties(player.slots.mainhand); + if (prop.breaks && Math.random() < max(0, prop.breaks / 100.0)) { + player.cid++; + player.u = true; + player.cslots.mainhand = player.slots.mainhand = null; + player.socket.emit("game_log", "Your pickaxe broke down ..."); + } + } + resend(player, "reopen"); + } else { + player.socket.emit("ui", { type: "mining_none" }); + } + } + if (name == "revival") { + player.rip = false; + invincible_logic(player); + if (player.party) { + send_party_update(player.party); + } + resend(player, "u+cid"); + } + } + } + if (player.slots.elixir && player.slots.elixir.expires && now_date > player.slots.elixir.expires) { + try { + var def = G.items[player.slots.elixir.name]; + player.socket.emit("game_log", def.name + " wore off ..."); + if (def.withdrawal) { + add_condition(player, def.withdrawal); + } + } catch (e) {} + player.slots.elixir = null; + player.cslots.elixir = null; + resend(player, "reopen+u+cid"); + } + if (player.moving) { + var ms = mssince(player.last.move); + player.x += (player.vx * ms) / 1000.0; + player.y += (player.vy * ms) / 1000.0; + player.red_zone *= 0.99; + if (smap_data[player.map] != -1 && !player.npc && mode.red_zone && !player.s.dash) { + var current = smap_data[player.map][phash(player)]; + if (current === undefined) { + current = 8; + } + current = max(0, current - 1); // 1 is the new 0 [01/08/18] + player.red_zone += current; + // console.log(player.red_zone); + if (player.red_zone > smap_edge) { + player.red_zone = 0; + player.hp -= parseInt(player.hp / 2 + 1000); + player.socket.emit("game_log", "Received a movement penalty"); + player.socket.emit("game_log", "This might have happened if your network is too slow"); + appengine_log("violation", "red_zone: " + player.name + " afk: " + player.afk + " code: " + player.code); + transport_player_to(player, player.map); + defeat_player(player); + if (player.hp <= 0) { + rip(player); + } + resend(player, "u+cid"); + } + } + stop_logic(player); + // if(!player.moving) player.check_x=player.x,player.check_y=player.y,player.checked_xy=false; + player.last.move = new Date(); + xy_u_logic(player); + xy_upush_logic(player); + if (!player.npc) { + pmap_move(player); + } + } else { + player.red_zone *= 0.97; + } + if (mode.aggro && !player.rip && !is_invinc(player) && !player.npc) { + //#GTODO: calculate an angle + compare against monster's .angle before attacking + var l = get_nearby_ghash(aggressives, player, 32); + l.forEach(function (monster) { + if (is_in_front(monster, player) && can_attack(monster, player)) { + if (Math.random() < player.aggro_diff) { + return; + } + if (monster.rage && Math.random() < monster.rage - player.aggro_diff) { + target_player(monster, player); + } + var attack = commence_attack(monster, player, "attack"); + if (attack && attack.events && attack.events.length) { + if (monster_map[monster.id]) { + if (!monster_map[monster.id].data.events) { + monster_map[monster.id].data.events = attack.events; + } + monster_map[monster.id].data.events.push(...attack.events); + } else { + to_push.push({ id: monster.id, entity: monster, data: monster_to_client(monster, attack.events) }); + monster_map[monster.id] = to_push[to_push.length - 1]; + } + } + // disappearing_text(player.socket,monster,"ATTACK!",{color:"#E082B1",xy:1}); + } + }); + } + if (player.u) { + player.u = false; + to_push.push({ id: id, entity: player, data: player_to_client(player, 1) }); + } + if (player.violations >= 3) { + player.socket.disconnect(); + } + } + + (G.maps[instance.map].traps || []).forEach(function (trap) { + if (trap.type == "spikes") { + for (var id in pmap_get({ x: trap.position[0], y: trap.position[1], in: instance.name })) { + var player = players[id_to_id[id]]; + if (player && !player.npc) { + disappearing_text(player.socket, player, "-50", { color: "red", xy: 1 }); + player.hp = max(0, player.hp - 50); + player_rip_logic(player); + resend(player, "u+cid"); + } + } + } else if (trap.type == "debuff") { + for (var id in instance.players) { + var player = instance.players[id]; + if (is_point_inside([player.x, player.y], trap.polygon)) { + // player.s["debuffaura"]={"ms":200,"name":"Debuff","skin":"citizens","citizens":true,"speed":-40}; + add_condition(player, "slowness", { duration: 200 }); + resend(player, "u+cid"); + } + } + } + }); + + for (var id in instance.players) { + send_xy_updates(instance.players[id], to_push); + } + for (var id in instance.observers) { + send_xy_updates(instance.observers[id], to_push); + } } -function update_instance(instance) -{ - if(instance.paused) return; - instance.operators=0; - var ms=mssince(instance.last_update); - instance.last_update=new Date(); - rage_logic(instance); - var to_push=[],monster_map={},now_date=instance.last_update,aggressives={},targets={}; - for(var id in instance.monsters) - { - var monster=instance.monsters[id],events=[],change=false,def=monster.type; - if(monster.target && monster.a.portal || G.monsters[monster.type].operator) instance.operators+=1; - var focus=instance.monsters[monster.focus]; - if(monster.focus && !instance.monsters[monster.focus] || focus && distance(monster,focus)>380) focus=monster.focus=null,monster.cid++,monster.u=true,change=true; - if(monster.focus) change=true; // better to re-calculate for now, for charge speed changes - for(var name in monster.s) - { - var def=G.conditions[name],ref=monster.s[name],value=monster.s[name].ms; - monster.s[name].ms-=ms; - if(def && def.interval) - { - if(!monster.s[name].last || mssince(monster.s[name].last)>=def.interval) - { - monster.s[name].last=new Date(); - if(name=="eburn") - { - var damage=G.conditions.eburn.damage; - if(monster.immune) damage=0; - disappearing_text({},monster,"-"+damage,{color:"red",xy:1}); - monster.hp=max(1,monster.hp-damage); - } - if(name=="eheal") - { - var heal=G.conditions.eheal.heal; - if(monster.immune) heal=0; - disappearing_text({},monster,"+"+heal,{color:"heal",xy:1}); - monster.hp=min(monster.max_hp,monster.hp+heal); - } - if(name=="burned") - { - var damage=ceil(ref.intensity/5); - //disappearing_text({},monster,"-"+damage,{color:"burn",xy:1}); - monster.hp=max(0,monster.hp-damage); - xy_emit(monster,"hit",{source:"burn",hid:ref.f,id:monster.id,damage:damage,kill:(monster.hp<=0)}); - var attacker=monster.target&&get_player(monster.target)||get_player(ref.f); - var burner=get_player(ref.f); - if(burner) - { - add_pdps(burner,monster,damage); - if(monster.cooperative) add_coop_points(monster,burner,damage); - } - if(monster.hp<=0) - { - if(burner) achievement_logic_burn_last_hit(burner); - kill_monster(attacker,monster); - } - } - monster.u=true; - monster.cid++; - } - } - if(monster.s[name].ms<=0) - { - if(monster.a[name] && monster.a[name].cooldown) - monster.s[name].ms=monster.a[name].cooldown; - else {delete monster.s[name];} - if(is_disabled(monster) && G.skills[name] && !G.skills[name].passive) continue; - if(name!="young") - { - monster.u=true; monster.cid++; change=true; - } - if(name=="self_healing") - { - var hp=monster.hp,heal=monster.a.self_healing.heal; - if(monster.s.poisoned) heal/=2; - monster.hp=min(monster.max_hp,monster.hp+heal); - if(hp!=monster.hp) events.push(["ui",{"type":"mheal","id":id,"heal":monster.hp-hp}]); - } - if(name=="healing") - { - var target=monster; - if(focus && distance(focus,monster)<120) target=focus; - var hp=target.hp,heal=monster.a.healing.heal; - if(target.s.poisoned) heal/=2; - target.hp=min(target.max_hp,target.hp+heal); - if(hp!=target.hp) events.push(["ui",{"type":"mheal","id":target.id,"heal":target.hp-hp}]); - } - if(name=="mtangle") - { - if(monster.target && get_player(monster.target)) - { - var player=get_player(monster.target); - add_condition(player,"tangled"); - resend(player,"u+cid"); - } - } - if(name=="multi_burn") - { - if(monster.cooperative) - for(var name in monster.points||{}) - { - var player=get_player(name); - if(player && simple_distance(monster,player)<480) - { - commence_attack(monster,player,"fireball"); - } - } - else - for(var id in instances[monster.in].players) - { - var player=instances[monster.in].players[id]; - if(distance(player,monster)<480) - { - commence_attack(monster,player,"fireball"); - } - } - monster.cid++; monster.u=true; change=true; - } - if(name=="multi_freeze") - { - for(var name in monster.points||{}) - { - var player=get_player(name); - if(player && simple_distance(monster,player)<480) - { - commence_attack(monster,player,"frostball"); - } - } - monster.cid++; monster.u=true; change=true; - } - if(name=="degen") - { - monster.hp-=60; - monster.cid++; monster.u=true; change=true; - if(monster.hp<=0) monster.hp=0,remove_monster(monster); - } - if(name=="zap") - { - for(var id in instances[monster.in].players) - { - var player=instances[monster.in].players[id]; - if(distance(player,monster)0.99 || Math.random()interval) - { - var pname=random_one(Object.keys(monster.points)); - var player=get_player(pname); - if(!player || player.npc || distance(monster,player)>400) return; - if(!is_same(player,get_player(monster.target),true)) return; - monster.last[name]=new Date(); - var spot=safe_xy_nearby(player.map,player.x+Math.random()*20-10,player.y+Math.random()*20-10); - if(!spot) return; - new_monster(instance.name,{type:name,stype:"spawn",x:spot.x,y:spot.y,target:player.name,master:monster.id}); - } - }); - } - function attack_target_or_move() - { - var player=players[name_to_id[monster.target]]; - if(player && ssince(monster.last.attacked)>20 && Math.random()>monster.rage*0.99) - { - stop_pursuit(monster,{force:true,cause:"bored"}); - return; - } - if(focus && distance(focus,monster)>40 && !monster.moving) - { - if(mode.all_smart) - { - if(!monster.worker) - { - monster.working=true; - workers[(wlast++)%workers.length].postMessage({type:"fast_astar",in:monster.in,id:monster.id,map:monster.map,sx:monster.x,sy:monster.y,tx:focus.x,ty:focus.y}); - } - } - else - { - monster.ogoing_x=monster.going_x,monster.ogoing_y=monster.going_y; - monster.going_x=monster.x+(focus.x-monster.x)/2; - monster.going_y=monster.y+(focus.y-monster.y)/2; - if(mode.path_checks && !can_move(monster)) - { - monster.going_x=monster.ogoing_x,monster.going_y=monster.ogoing_y; - } - else - { - start_moving_element(monster); - } - } - } - if(player && player.in==monster.in && !player.rip && !is_invis(player)) - { - if(distance(player,monster,true)>min(monster.range/1.6,240)+min(100,monster.attack/5.0)+160+(mode.all_smart&&320||1) && !monster.walk_once) stop_pursuit(monster,{cause:"exceeds_range"}); - else if(can_attack(monster,player)) - { - var attack=commence_attack(monster,player,"attack"); - if(attack && attack.events && attack.events.length) events.push(...attack.events); - } - else if(distance(monster,player,true)>12 && !mode.range_test && !(mode.all_smart && monster.moving) && !focus) - { - // console.log(monster.height); - if(mode.all_smart) - { - if(!monster.worker) - { - monster.working=true; - workers[(wlast++)%workers.length].postMessage({type:"fast_astar",in:monster.in,id:monster.id,map:monster.map,sx:monster.x,sy:monster.y,tx:player.x,ty:player.y}); - } - } - else - { - monster.ogoing_x=monster.going_x,monster.ogoing_y=monster.going_y; - monster.going_x=monster.x+(player.x-monster.x)/2; - monster.going_y=monster.y+(player.y-monster.y)/2; - if(mode.path_checks && !can_move(monster)) - { - monster.going_x=monster.ogoing_x,monster.going_y=monster.ogoing_y; - if(monster.attack<120 || distance(monster,player,true)>monster.range) stop_pursuit(monster,{cause:"cant_move"}); - } - else - { - // console.log("Moving to "+monster.going_x+" "+monster.going_y); - start_moving_element(monster); - } - } - } - } - else if(monster.target) stop_pursuit(monster,{cause:"player_gone"}); - if(monster.walk_once) monster.walk_once=false; - } - if(monster.moving) - { - var ms=mssince(monster.last.move); - ms=min(ms,2000); // to prevent monsters from jumping off the map when the machine sleeps - monster.x+=monster.vx*ms/1000.0; - monster.y+=monster.vy*ms/1000.0; - stop_logic(monster); - monster.last.move=new Date(); - xy_u_logic(monster); - - if(monster.moving && monster.attack>100 && monster.target) - { - attack_target_or_move(); - } - } - else if(monster.s.sleeping || monster.working) - { - - } - else if(can_walk(monster)) // for the .s.stunned check - { - if(monster.s.magiport); - else if(monster.target || focus) - { - attack_target_or_move(); - } - else if(!mode.upush_test) - { - if(monster.map_def.position && !mode.all_roam) - { - var position=monster.map_def.position,radius=monster.map_def.radius,dx=Math.random()*(radius)-radius/2,dy=Math.random()*(radius)-radius/2; - if(abs(dx)+abs(dy)<80) // optimization to prevent short walks - { - if(dx<0 && dx>-60) dx=-60; if(dx>0 && dx<60) dx=60; - if(dy<0 && dy>-60) dy=-60; if(dy>0 && dy<60) dy=60; - } - monster.going_x=monster.x+dx; - monster.going_y=monster.y+dy; - if(monster.going_x>position[0]+radius) monster.going_x=position[0]+radius; - if(monster.going_y>position[1]+radius) monster.going_y=position[1]+radius; - if(monster.going_x1 && monster.map_def.polygon) - { - if(is_point_inside([monster.going_x,monster.going_y],monster.map_def.polygon)) break; - else monster.rmove++,monster.dmove++; - } - } - // console.log(monster.rmove+","+monster.dmove+" "+monster.going_x+","+monster.going_y); - if(can_move(monster)) - { - start_moving_element(monster); - monster.dmove=0; - } - else monster.rmove++,monster.dmove++; - } - else if(monster.map_def.stype!="pet" && monster.map_def.stype!="spawn" && monster.map_def.stype!="trap") - { - var map_def=monster.map_def,to_move=true; - if(map_def.polygon) - { - var p=random_point(map_def.polygon,monster.base); - monster.going_x=p[0]; - monster.going_y=p[1]; - } - else - { - monster.going_x=map_def.boundary[0]+Math.random()*(map_def.boundary[2]-map_def.boundary[0]); - monster.going_y=map_def.boundary[1]+Math.random()*(map_def.boundary[3]-map_def.boundary[1]); - } - if(monster.irregular==3) // new [01/03/19] - { - monster.m++; - setTimeout(new_monster_f(monster.oin,monster.map_def,{last_state:monster}),500); - remove_monster(monster,{nospawn:true,method:"disappear"}) - } - else if(monster.irregular==2) - { - server_log("Irregular2 move for "+monster.id); - if(monster.in!=monster.oin) port_monster(monster,{map:monster.oin,x:monster.going_x,y:monster.going_y,in:monster.oin}); - else recalculate_move(monster); - monster.irregular=1; - } - else if(monster.irregular==1) - { - if(!can_move(monster)) - { - monster.m++; - server_log("Irregular1 respawn: "+monster.id); - setTimeout(new_monster_f(monster.oin,monster.map_def,{last_state:monster}),500); - remove_monster(monster,{nospawn:true,method:"disappear"}) - } - else - { - server_log("No longer irregular: "+monster.id); - delete monster["irregular"]; - } - } - if(monster.going_x!=monster.x || monster.going_y!=monster.y) // so Automatron's don't move - start_moving_element(monster); - } - } - } - if(!monster.dead && (monster.u || events.length)) - { - monster.u=false; - to_push.push({id:id,entity:monster,data:monster_to_client(monster,events)}); - monster_map[id]=to_push[to_push.length-1]; - } - } - - for(var id in instance.players) - { - var player=instance.players[id]; - if(!player) continue; - for(var name in player.s) - { - var def=G.conditions[name],ref=player.s[name],value=player.s[name].ms; - player.s[name].ms-=ms; - if(def && def.interval) - { - if(!player.s[name].last || mssince(player.s[name].last)>=def.interval) - { - player.s[name].last=new Date(); - if(name=="eburn") - { - disappearing_text(player.socket,player,"-50",{color:"red",xy:1}); - player.hp=max(1,player.hp-50); - } - if(name=="eheal") - { - disappearing_text(player.socket,player,"+50",{color:"heal",xy:1}); - player.hp=min(player.max_hp,player.hp+50); - } - if(name=="burned") - { - var damage=ceil(ref.intensity/5); - disappearing_text(player.socket,player,"-"+damage,{color:"red",xy:1}); - player.hp=max(0,player.hp-damage); - xy_emit(player,"hit",{source:"burn",hid:ref.fid,id:player.name,damage:damage,kill:(player.hp<=0)}); - player_rip_logic(player); - } - resend(player,"u+cid+nc"); - } - } - if(in_arr(name,["damage_received"])) - { - player.s[name].amount=max(0,player.s[name].amount*(4000-ms)/4000.0) - } - if(player.s[name].ms<=0) - { - delete player.s[name]; - if(name=="blink") - { - if(player.s.dampened) - { - xy_emit(player,"ui",{type:"dampened",name:player.name}); - } - else - { - decay_s(player,30000); - transport_player_to(player,ref.in,[ref.x,ref.y,ref.d],"blink"); - } - } - if(name=="magiport") - { - var dampened=false; - for(var id in instances[ref.in].monsters) - { - var m=instances[ref.in].monsters[id]; - if(m.type=="fieldgen0" && point_distance(ref.x,ref.y,m.x,m.y)<300) - { - xy_emit(player,"ui",{type:"dampened",name:player.name}); - dampened=true; - break; - } - } - if(!dampened) - { - var skip=false; - if(ref.map!=player.map) skip=true; - decay_s(player,30000); - transport_player_to(player,ref.in,[ref.x,ref.y],"magiport"); - if(skip) - { - resend(player,"u+cid"); - return; // player isn't in this instance any more - } - } - } - if(def) player.socket.emit("game_response",{response:"ex_condition",name:name}); - if(def && def.ui) resend(player,"u+cid"); // +cid to update the UI's [23/10/18] - else resend(player,"u"); // #TODO: don't need to resend actually, maybe reconsider [27/06/18] - - } - } - for(var name in player.q) - { - var value=player.q[name].ms,ref=player.q[name]; - player.q[name].ms-=ms; - if(name=="upgrade") - { - if(player.items[ref.num] && player.items[ref.num].name=="placeholder") - { - var def=player.items[ref.num].p,change=false; - if(value1800000 && !player.stealth) - broadcast("server_message",{"message":player.name+" received "+item_to_phrase(item),"color":colors.server_success,item:cache_item(item),"type":"server_csuccess",name:player.name}); - xy_emit(G.maps.main.compound,"upgrade",{type:"compound",success:1}); - if(player.items[ref.num] && player.items[ref.num].name=="placeholder") - { - player.items[ref.num]=item; - player.citems[ref.num]=cache_item(player.items[ref.num]); - } - achievement_logic_compound_success(player,item); - } - else - { - var item=player.p.c_itemx,def=G.items[item.name]; - player.hitchhikers.push(["game_response",{response:"compound_fail",level:item.level,num:data.num,stale:ref.stale}]); - if(calculate_item_value(item)+(def.edge||0)*2000000>920000 && !player.stealth) - broadcast("server_message",{"message":player.name+" lost "+item_to_phrase(item)+"'s","color":colors.server_failure,item:cache_item(item),"type":"server_cfail",name:player.name}); - xy_emit(G.maps.main.compound,"upgrade",{type:"compound",success:0}); - if(player.items[ref.num] && player.items[ref.num].name=="placeholder") - player.citems[ref.num]=player.items[ref.num]=null,player.esize++; - } - delete player.p.c_item; delete player.p.c_itemx; delete player.p.c_roll; - resend(player,"reopen+u+cid+nc+inv"); - } - if(name=="upgrade") - { - var success=false,announce=false,new_level=player.p.u_level+1; - var item=player.p.u_item||player.p.u_itemx; - var p=player.items[ref.num] && player.items[ref.num].p; - if(player.p.u_item && player.items[ref.num] && player.items[ref.num].name=="placeholder") - { - success=true; - player.items[ref.num]=player.p.u_item; - player.citems[ref.num]=cache_item(player.items[ref.num]); - } - else if(player.items[ref.num] && player.items[ref.num].name=="placeholder") - player.citems[ref.num]=player.items[ref.num]=null,player.esize++; - if(player.p.u_type=="offering") - { - if(success) player.hitchhikers.push(["game_response",{response:"upgrade_offering_success",stale:ref.stale}]); - } - else if(player.p.u_type=="stat") - { - if(success) player.hitchhikers.push(["game_response",{response:"upgrade_success_stat",stale:ref.stale,stat_type:p&&G.items[p.scroll].stat,num:ref.num}]); - } - else if(!ref.silent) - announce=true; - - if(success && !player.p.u_fail) - { - if(announce) player.hitchhikers.push(["game_response",{response:"upgrade_success",level:new_level,num:ref.num,stale:ref.stale}]); - if(announce && calculate_item_value(item)>4800000 && !player.stealth) - broadcast("server_message",{"message":player.name+" received "+item_to_phrase(item),"color":colors.server_success,item:cache_item(item),"type":"server_usuccess",name:player.name}); - xy_emit(instances.main&&G.maps.main.upgrade||player,"upgrade",{type:"upgrade",success:1}); - achievement_logic_upgrade_success(player,item); - } - else - { - player.hitchhikers.push(["game_response",{response:"upgrade_fail",level:new_level,num:ref.num,stale:ref.stale}]); - if(announce && calculate_item_value(item)>4800000 && !player.stealth) - broadcast("server_message",{"message":player.name+" lost "+item_to_phrase(item),"color":colors.server_failure,item:cache_item(item),"type":"server_ufail",name:player.name}); - xy_emit(instances.main&&G.maps.main.upgrade||player,"upgrade",{type:"upgrade",success:0}); - } - delete player.p.u_item; delete player.p.u_type; delete player.p.u_itemx; delete player.p.u_roll; delete player.p.u_fail; delete player.p.u_level; - resend(player,"reopen+u+cid+nc+inv"); - } - if(name=="slots") - { - if(Math.random()<((S.gold>500000000)&&D.odds.slots_good||D.odds.slots)) - { - var gold=500000000; - player.gold+=gold; S.gold-=gold; - broadcast("server_message",{"message":player.name+" received "+to_pretty_num(gold)+" gold","color":"gold"}); - player.socket.emit("game_response","slots_success"); - player.socket.emit("game_log",{message:"Received "+to_pretty_num(gold)+" gold",color:"gold"}); - // resend(player,"u+cid"); - } - else player.socket.emit("game_response","slots_fail"); - } - } - } - for(var name in player.c) - { - player.c[name].ms-=ms; - if(player.c[name].ms<=0) - { - var ref=player.c[name]; - delete player.c[name]; - if(name=="town") - { - decay_s(player,4000); - player.last.town=new Date(); - transport_player_to(player,player.in,undefined,1); - resend(player); - } - if(name=="pickpocket") - { - var target=get_player(ref.target); - if(!target) player.socket.emit("game_response",{response:"pick_failed",cevent:true,reason:"player_gone"}); - else if(distance(player,target)>20) player.socket.emit("game_response",{response:"pick_failed",cevent:true,reason:"distance"}); - else - { - var num=floor(Math.random()*42); - if(target.items[num] && target.items[num].v) - { - var item=target.items[num]; - if(item.q && item.q>1) - { - target.items[num].q-=1; target.citems[num].q-=1; - item=create_new_item(item.name); - } - else - { - target.citems[num]=target.items[num]=null; - } - item.v=new Date(); - add_item(player,item); - player.socket.emit("game_response",{response:"picked",slot:"mainhand",cevent:true}); - consume_skill(player,"pickpocket",true); - resend(player,"reopen"); resend(target,"reopen"); - target.socket.emit("game_response",{response:"got_picked",cevent:true}); - } - else - { - player.socket.emit("game_response",{response:"pick_failed",cevent:true,reason:"misfortune"}); - } - } - } - if(name=="fishing") - { - if(Math.random()<0.1) - { - if(player.esize) exchange(player,ref.drop,{phrase:"Fished"}); - consume_skill(player,"fishing",true); - if(player.slots.mainhand) - { - var prop=calculate_item_properties(player.slots.mainhand); - if(prop.breaks && Math.random()player.slots.elixir.expires) - { - try{ - var def=G.items[player.slots.elixir.name]; - player.socket.emit("game_log",def.name+" wore off ..."); - if(def.withdrawal) add_condition(player,def.withdrawal); - }catch(e){} - player.slots.elixir=null; - player.cslots.elixir=null; - resend(player,"reopen+u+cid"); - } - if(player.moving) - { - var ms=mssince(player.last.move); - player.x+=player.vx*ms/1000.0; - player.y+=player.vy*ms/1000.0; - player.red_zone*=0.99; - if(smap_data[player.map]!=-1 && !player.npc && mode.red_zone && !player.s.dash) - { - var current=smap_data[player.map][phash(player)]; - if(current===undefined) current=8; - current=max(0,current-1); // 1 is the new 0 [01/08/18] - player.red_zone+=current; - // console.log(player.red_zone); - if(player.red_zone>smap_edge) - { - player.red_zone=0; - player.hp-=parseInt(player.hp/2+1000); - player.socket.emit("game_log","Received a movement penalty"); - player.socket.emit("game_log","This might have happened if your network is too slow"); - appengine_log("violation","red_zone: "+player.name+" afk: "+player.afk+" code: "+player.code); - transport_player_to(player,player.map); - defeat_player(player); - if(player.hp<=0) rip(player); - resend(player,"u+cid"); - } - } - stop_logic(player); - // if(!player.moving) player.check_x=player.x,player.check_y=player.y,player.checked_xy=false; - player.last.move=new Date(); - xy_u_logic(player); - xy_upush_logic(player); - if(!player.npc) pmap_move(player); - } - else - { - player.red_zone*=0.97; - } - if(mode.aggro && !player.rip && !is_invinc(player) && !player.npc) - { - //#GTODO: calculate an angle + compare against monster's .angle before attacking - var l=get_nearby_ghash(aggressives,player,32); - l.forEach(function(monster){ - if(is_in_front(monster,player) && can_attack(monster,player)) - { - if(Math.random()=3) player.socket.disconnect(); - } - - (G.maps[instance.map].traps||[]).forEach(function(trap){ - if(trap.type=="spikes") - for(var id in pmap_get({x:trap.position[0],y:trap.position[1],in:instance.name})) - { - var player=players[id_to_id[id]]; - if(player && !player.npc) - { - disappearing_text(player.socket,player,"-50",{color:"red",xy:1}); - player.hp=max(0,player.hp-50); - player_rip_logic(player); - resend(player,"u+cid"); - } - } - else if(trap.type=="debuff") - for(var id in instance.players) - { - var player=instance.players[id]; - if(is_point_inside([player.x,player.y],trap.polygon)) - { - // player.s["debuffaura"]={"ms":200,"name":"Debuff","skin":"citizens","citizens":true,"speed":-40}; - add_condition(player,"slowness",{duration:200}); - resend(player,"u+cid"); - } - } - - }); - - for(var id in instance.players) send_xy_updates(instance.players[id],to_push); - for(var id in instance.observers) send_xy_updates(instance.observers[id],to_push); +function count_unique_users() { + unique_players = 0; + var marked = {}; + for (var id in players) { + var ip = get_ip(players[id]); + if (!marked[ip]) { + marked[ip] = 1; + unique_players++; + } + } + if (is_sdk) { + unique_players = 9; + } + for (name in instances) { + var instance = instances[name]; + if (!instance.pvp) { + continue; + } + var initial = instance.allow; + var npc = npcs.pvp; + if ( + (is_sdk && Object.keys(players).length >= 2) || + unique_players >= B.arena_limit || + Object.keys(instance.players).length + ) { + // direction logic at game.js/update_sprite + if (instance.allow) { + continue; + } + instance.allow = true; + if (!npc) { + continue; + } + npc.going_x = npc.positions[1][0]; + npc.going_y = npc.positions[1][1]; + npc.allow = true; + npc.u = true; + start_moving_element(npc); + } else { + if (!instance.allow) { + continue; + } + instance.allow = false; + if (!npc) { + continue; + } + npc.going_x = npc.positions[0][0]; + npc.going_y = npc.positions[0][1]; + npc.allow = false; + npc.u = true; + start_moving_element(npc); + } + } } -function count_unique_users() -{ - unique_players=0; - var marked={}; - for(var id in players) - { - var ip=get_ip(players[id]); - if(!marked[ip]) {marked[ip]=1; unique_players++;} - } - if(is_sdk) unique_players=9; - for(name in instances) - { - var instance=instances[name]; - if(!instance.pvp) continue; - var initial=instance.allow,npc=npcs.pvp; - if(is_sdk && Object.keys(players).length>=2 || unique_players>=B.arena_limit || Object.keys(instance.players).length) // direction logic at game.js/update_sprite - { - if(instance.allow) continue; - instance.allow=true; - if(!npc) continue; - npc.going_x=npc.positions[1][0]; - npc.going_y=npc.positions[1][1]; - npc.allow=true; - npc.u=true; - start_moving_element(npc); - } - else - { - if(!instance.allow) continue; - instance.allow=false; - if(!npc) continue; - npc.going_x=npc.positions[0][0]; - npc.going_y=npc.positions[0][1]; - npc.allow=false; - npc.u=true; - start_moving_element(npc); - } - } +var last_iloop = new Date(); +function instance_loop() { + var ms_since = 32; + try { + var now_date = new Date(); + + for (name in instances) { + var instance = instances[name]; + if (mssince(instance.last_update) > 75) { + update_instance(instance); + } + } + + ms_since = mssince(now_date); + if (ms_since > B.instance_loop_log_edge) { + server_log("Instance loop took " + mssince(now_date) + "ms", 1); + } + perfc.instance_loop[ms_since] = (perfc.instance_loop[ms_since] || 0) + 1; + perfc.instance_loops += 1; + var mss_since = mssince(last_iloop); + perfc.instance_delay[mss_since] = (perfc.instance_delay[mss_since] || 0) + 1; + if (mss_since / 1000 > 15) { + console.log("Sleep detected: " + mss_since / 1000); + } + last_iloop = new Date(); + } catch (e) { + log_trace("#X Instance loop error", e); + } + setTimeout(instance_loop, max(75, min(1000, ms_since * 2 + 2))); } -var last_iloop=new Date(); -function instance_loop() -{ - var ms_since=32; - try{ - var now_date=new Date(); - - for(name in instances) - { - var instance=instances[name]; - if(mssince(instance.last_update)>75) update_instance(instance); - } - - ms_since=mssince(now_date); - if(ms_since>B.instance_loop_log_edge) server_log("Instance loop took "+mssince(now_date)+"ms",1); - perfc.instance_loop[ms_since]=(perfc.instance_loop[ms_since]||0)+1; - perfc.instance_loops+=1; - var mss_since=mssince(last_iloop); - perfc.instance_delay[mss_since]=(perfc.instance_delay[mss_since]||0)+1; - if(mss_since/1000>15) - console.log("Sleep detected: "+(mss_since/1000)); - last_iloop=new Date(); - }catch(e){log_trace("#X Instance loop error",e);}; - setTimeout(instance_loop,max(75,min(1000,ms_since*2+2))); +function npc_loop() { + // now mainly just NPC's, back in the day pretty much everything [11/08/22] + if (!server.live) { + return setTimeout(npc_loop, 10); + } + var ms_since = 32; + try { + var now_date = new Date(); + + for (var id in npcs) { + var npc = npcs[id]; + var delay = -npc.delay * npc.d_multiplier; + if (npc.rip) { + continue; + } + var def = G.npcs[npc.ntype] || {}; + if ( + def && + instances[npc.in] && + instances[npc.in].players && + def.aura && + (!npc.last_aura || mssince(npc.last_aura) > 2000) + ) { + npc.last_aura = new Date(); + for (var pid in instances[npc.in].players) { + var player = instances[npc.in].players[pid]; + // console.log(player); + if (!player.npc && distance(player, npc) < 320) { + player.s[npc.ntype + "aura"] = { ms: 6000, name: "Citizen's Aura", skin: "citizens", citizens: true }; + for (var p in def.aura) { + player.s[npc.ntype + "aura"][p] = def.aura[p]; + } + resend(player, "u+cid"); + } + } + } + if (def.attack && ssince(npc.last.attack) > 1.5) { + for (var id in instances[npc.in].monsters) { + var monster = instances[npc.in].monsters[id]; + if (distance(npc, monster) < npc.range) { + // && monster.hp>5000 + npc.last.attack = new Date(); + commence_attack(npc, monster, "attack"); + break; + } + } + } + + if (def.seek) { + var target = npc.focus && get_player(npc.focus); + if (target && def.transport && target.map != npc.map && target.in == target.map) { + var spot = safe_xy_nearby(target.map, target.x - 8, target.y - 6); + if (spot) { + transport_player_to(npc, get_player(npc.focus).map, [spot.x, spot.y]); + } + } + var target = null; + var old_focus = npc.focus; + var min_val = 99999; + var max_val = -99999; + + for (var pid in instances[npc.in].players) { + var player = instances[npc.in].players[pid]; + if (def.seek == "low_hp" && player.hp < player.max_hp * 0.275 && player.hp < min_val) { + min_val = player.hp; + target = player; + } + if (def.seek == "cuteness" && player.cuteness && player.cuteness > max_val) { + max_val = player.cuteness; + target = player; + } + if ( + def.seek == "thrill" && + player.thrilling && + player.thrilling > now_date && + (!target || player.thrilling > target.thrilling) + ) { + target = player; + } + if (def.seek == "gold" && player.gold > 1000000000 * ((!is_pvp && 10) || 1) && player.gold > max_val) { + max_val = player.gold; + target = player; + } + if (player.type == "merchant") { + continue; + } + if ( + def.seek == "dragondagger" && + player.slots.mainhand && + player.slots.mainhand.name == "dragondagger" && + player.level < min_val + ) { + min_val = player.level; + target = player; + } + } + + npc.focus = target && target.name; + if (old_focus != npc.focus) { + npc.u = true; + npc.cid++; + } + } + + if (!npc.movable) { + continue; + } + var moves = [ + [1, 0], + [0, 1], + [-1, 0], + [0, -1], + [0.8, 0.8], + [-0.8, -0.8], + [0.8, -0.8], + [-0.8, 0.8], + ]; + var multiplier = npc.steps; + shuffle(moves); + if (def.aura && G.maps[npc.map].ref.transporter && distance(npc, G.maps[npc.map].ref.transporter) < 2000) { + delay = -2400; + multiplier *= 4; + } else if (def.aura) { + delay *= 3; + } + if (!npc.citizen || npc.moving || npc.last.move > future_ms(delay) || instances[npc.in].paused) { + continue; + } + if (Math.random() < 0.3) { + multiplier *= 2; + } else if (Math.random() < 0.3) { + multiplier *= 4; + } + + if (def.heal) { + for (var id in instances[npc.in].players) { + var player = instances[npc.in].players[id]; + if (distance(player, npc) < 320 && player.hp < player.max_hp) { + commence_attack(npc, player, "partyheal"); + } + } + } + + if (npc.focus) { + var target = get_player(npc.focus); + if (!target || distance(target, npc) < 30) { + continue; + } + npc.going_x = (npc.x + target.x) / 2; + npc.going_y = (npc.y + target.y) / 2; + if (!can_move(npc)) { + npc.going_x = npc.x + moves[0][0] * multiplier; + npc.going_y = npc.y + moves[0][1] * multiplier; + } + } else { + npc.going_x = npc.x + moves[0][0] * multiplier; + npc.going_y = npc.y + moves[0][1] * multiplier; + } + npc.d_multiplier = 1 + 2 * Math.random(); + + if ( + npc.boundary && + (npc.going_x < npc.boundary[0] || + npc.going_x > npc.boundary[2] || + npc.going_y < npc.boundary[1] || + npc.going_y > npc.boundary[3]) + ) { + } else if (can_move(npc)) { + npc.u = true; + start_moving_element(npc); + // server_log("Moving to "+npc.going_x+","+npc.going_y); + } else { + npc.going_x = npc.x; + npc.going_y = npc.y; + npc.last.move = new Date(); + } + } + ms_since = mssince(now_date); + } catch (e) { + log_trace("#X NPC loop error", e); + } + setTimeout(npc_loop, max(28, min(1000, ms_since * 2 + 2))); // originally 24 } -function npc_loop() // now mainly just NPC's, back in the day pretty much everything [11/08/22] -{ - if(!server.live) return setTimeout(npc_loop,10); - var ms_since=32; - try{ - var now_date=new Date(); - - for(var id in npcs) - { - var npc=npcs[id],delay=-npc.delay*npc.d_multiplier; - if(npc.rip) continue; - var def=G.npcs[npc.ntype]||{}; - if(def && instances[npc.in] && instances[npc.in].players && def.aura && (!npc.last_aura || mssince(npc.last_aura)>2000)) - { - npc.last_aura=new Date(); - for(var pid in instances[npc.in].players) - { - var player=instances[npc.in].players[pid]; - // console.log(player); - if(!player.npc && distance(player,npc)<320) - { - player.s[npc.ntype+"aura"]={"ms":6000,"name":"Citizen's Aura","skin":"citizens","citizens":true}; - for(var p in def.aura) player.s[npc.ntype+"aura"][p]=def.aura[p]; - resend(player,"u+cid"); - } - } - - } - if(def.attack && ssince(npc.last.attack)>1.5) - { - for(var id in instances[npc.in].monsters) - { - var monster=instances[npc.in].monsters[id]; - if(distance(npc,monster)5000 - { - npc.last.attack=new Date(); - commence_attack(npc,monster,"attack"); - break; - } - } - } - - if(def.seek) - { - var target=npc.focus && get_player(npc.focus); - if(target && def.transport && target.map!=npc.map && target.in==target.map) - { - var spot=safe_xy_nearby(target.map,target.x-8,target.y-6); - if(spot) - transport_player_to(npc,get_player(npc.focus).map,[spot.x,spot.y]); - } - var target=null,old_focus=npc.focus,min_val=99999,max_val=-99999; - - for(var pid in instances[npc.in].players) - { - var player=instances[npc.in].players[pid]; - if(def.seek=="low_hp" && player.hpmax_val) - max_val=player.cuteness,target=player; - if(def.seek=="thrill" && player.thrilling && player.thrilling>now_date && (!target || player.thrilling>target.thrilling)) - target=player; - if(def.seek=="gold" && player.gold>1000000000*(!is_pvp&&10||1) && player.gold>max_val) - max_val=player.gold,target=player; - if(player.type=="merchant") continue; - if(def.seek=="dragondagger" && player.slots.mainhand && player.slots.mainhand.name=="dragondagger" && player.levelfuture_ms(delay) || instances[npc.in].paused) continue; - if(Math.random()<0.3) multiplier*=2; - else if(Math.random()<0.3) multiplier*=4; - - if(def.heal) - { - for(var id in instances[npc.in].players) - { - var player=instances[npc.in].players[id]; - if(distance(player,npc)<320 && player.hpnpc.boundary[2] || npc.going_ynpc.boundary[3])); - else if(can_move(npc)) - { - npc.u=true; - start_moving_element(npc); - // server_log("Moving to "+npc.going_x+","+npc.going_y); - } - else - { - npc.going_x=npc.x; npc.going_y=npc.y; - npc.last.move=new Date(); - } - } - ms_since=mssince(now_date); - }catch(e){log_trace("#X NPC loop error",e);}; - setTimeout(npc_loop,max(28,min(1000,ms_since*2+2))); // originally 24 +function game_loop() { + // back in the day pretty much everything was in here [11/08/22] + if (!server.live) { + return setTimeout(game_loop, 10); + } + var ms_since = 32; + try { + var now_date = new Date(); + // for(name in instances) + // { + // var instance=instances[name]; + // if(mssince(instance.last_update)>75) update_instance(instance); + // } + for (var id in server.s) { + server.s[id].ms -= 10; + if (server.s[id].ms < 0) { + delete server.s[id]; + } + } + + ms_since = mssince(now_date); + if (ms_since > B.game_loop_log_edge) { + server_log("Main loop took " + mssince(now_date) + "ms", 1); + } + perfc.game_loop[ms_since] = (perfc.game_loop[ms_since] || 0) + 1; + perfc.game_loops += 1; + } catch (e) { + log_trace("#X Main loop error", e); + } + setTimeout(game_loop, max(28, min(1000, ms_since * 2 + 2))); // originally 24 } -function game_loop() // back in the day pretty much everything was in here [11/08/22] -{ - if(!server.live) return setTimeout(game_loop,10); - var ms_since=32; - try{ - var now_date=new Date(); - // for(name in instances) - // { - // var instance=instances[name]; - // if(mssince(instance.last_update)>75) update_instance(instance); - // } - for(var id in server.s) - { - server.s[id].ms-=10; - if(server.s[id].ms<0) delete server.s[id]; - } - - ms_since=mssince(now_date); - if(ms_since>B.game_loop_log_edge) server_log("Main loop took "+mssince(now_date)+"ms",1); - perfc.game_loop[ms_since]=(perfc.game_loop[ms_since]||0)+1; - perfc.game_loops+=1; - }catch(e){log_trace("#X Main loop error",e);}; - setTimeout(game_loop,max(28,min(1000,ms_since*2+2))); // originally 24 +var lrid = 0; +function aura_loop() { + try { + lrid++; + for (var id in players) { + var player = players[id]; + if (!player || !player.aura || player.rid % 5 != lrid % 5) { + continue; + } + for (var aid in player.aura) { + for (var pid in instances[player.in].players) { + if (!is_same(instances[player.in].players[pid], player, 3)) { + continue; + } + if (distance(instances[player.in].players[pid], player) > 200) { + continue; + } + instances[player.in].players[pid].s[aid] = { ms: G.conditions[aid].duration || 30000, f: player.name }; + if (G.conditions[aid].attr0) { + instances[player.in].players[pid].s[aid][G.conditions[aid].attr0] = player.aura[aid].attr0; + } + resend(instances[player.in].players[pid], "u+cid"); + } + } + } + } catch (e) { + log_trace("#X aura loop error", e); + } + setTimeout(aura_loop, 1000); } -var lrid=0; -function aura_loop() -{ - try{ - lrid++; - for(var id in players) - { - var player=players[id]; - if(!player || !player.aura || (player.rid%5)!=(lrid%5)) continue; - for(var aid in player.aura) - { - for(var pid in instances[player.in].players) - { - if(!is_same(instances[player.in].players[pid],player,3)) continue; - if(distance(instances[player.in].players[pid],player)>200) continue; - instances[player.in].players[pid].s[aid]={ms:G.conditions[aid].duration||30000,f:player.name}; - if(G.conditions[aid].attr0) - instances[player.in].players[pid].s[aid][G.conditions[aid].attr0]=player.aura[aid].attr0; - resend(instances[player.in].players[pid],"u+cid"); - } - } - } - }catch(e){log_trace("#X aura loop error",e);}; - setTimeout(aura_loop,1000); +setTimeout(game_loop, 10); +setTimeout(npc_loop, 10); +setTimeout(instance_loop, 10); +setTimeout(aura_loop, 10); +setInterval(count_unique_users, 6000); + +setInterval(function () { + try { + for (var id in observers) { + var observer = observers[id]; + if (observer.player && get_player(observer.player.name)) { + var player = get_player(observer.player.name); + if (simple_distance(observer, player) > 200) { + transport_observer_to( + observer, + player.in, + player.map, + player.x, + player.y + ((observer.socket.desktop && 120) || 0), + ); + } + } else { + if (simple_distance(observer, { map: observer_map, in: observer_map, x: observer_x, y: observer_y }) > 200) { + transport_observer_to( + observer, + observer_map, + observer_map, + observer_x, + observer_y + ((observer.socket.desktop && 120) || 0), + ); + } + } + } + } catch (e) { + log_trace("#X observer loop2 error", e); + } +}, 13200); + +setInterval(function () { + try { + if (is_pvp) { + return; + } + var names = []; + var player; + total_merchants = 0; + for (var id in players) { + player = players[id]; + if ( + !player.npc && + player.last.attack && + player.map == player.in && + ssince(player.last.attack) < 3 && + !G.maps[player.map].pvp + ) { + names.push(player.name); + } + if (player.p && player.p.stand) { + total_merchants += 1; + } + } + if (names.length) { + player = get_player(names[floor(Math.random() * names.length)]); + observer_map = player.map; + observer_x = parseInt(player.x); + observer_y = parseInt(player.y); + server_log("Set the observer location: " + [observer_map, observer_x, observer_y]); + } else { + observer_map = merchant_map; + observer_x = merchant_x; + observer_y = merchant_y; + } + } catch (e) { + log_trace("#X observer loop error", e); + } +}, 4000); + +setTimeout( + function () { + setInterval( + function () { + for (var id in players) { + var player = players[id]; + if (player.type == "merchant" && player.p.stand) { + var xp = 1000; + if (player.level <= 40) { + xp = G.levels[player.level] / 100; + } else if (player.level <= 60) { + xp = G.levels[player.level] / 200; + } else if (player.level <= 70) { + xp = G.levels[player.level] / 400; + } else { + xp = G.levels[70] / 400; + } + xp = parseInt(Math.round(xp)); + player.xp += xp; + player.socket.emit("game_log", "Gained " + to_pretty_num(xp) + " marketing XP"); + player.socket.emit("disappearing_text", { + message: "+" + xp, + x: player.x, + y: player.y - 32, + args: { color: "gray", size: "large" }, + }); + resend(player, "reopen+u+cid"); + } + } + }, + 3 * 60 * 60 * 1000, + ); //3 + }, + 1 * 60 * 60 * 1000, +); //1 + +setInterval(function () { + try { + var edge = future_s(-120); + for (var id in players) { + if (0 && players[id].last_ipass < edge) { + players[id].ban = "ipass"; + players[id].socket.emit("disconnect_reason", "Failed to check in. Your network might be too slow."); + players[id].socket.disconnect(); + } + } + } catch (e) { + log_trace("#X ipass loop error", e); + } +}, 132000); + +setTimeout(function () { + setInterval(function () { + try { + for (var id in instances) { + var instance = instances[id]; + if ( + B.pause_instances && + !instance.paused && + !instance.operators && + Object.keys(instance.players).length <= instance.npcs && + !Object.keys(instance.observers).length && + ssince(instance.last_player) > 50 + ) { + pause_instance(instance); + } + if (Object.keys(instance.players).length > instance.npcs || Object.keys(instance.observers).length) { + instance.last_player = new Date(); + } + } + } catch (e) { + log_trace("#X pause loop error", e); + } + }, 2000); +}, 60000); // delay for a minute, so citizens move around + +setInterval(function () { + try { + for (var oname in parties) { + send_party_update(oname); + } + } catch (e) { + log_trace("#X party loop error", e); + } +}, 60000); + +setInterval(function () { + try { + for (var id in players) { + var player = players[id]; + players[id].pdps *= 0.8; + player.p.minutes++; + trade_slots.forEach(function (slot) { + if (player.slots[slot] && player.slots[slot].giveaway) { + player.slots[slot].giveaway--; + if (!player.slots[slot].giveaway) { + var list = []; + player.slots[slot].list.forEach(function (p) { + var p = get_player(p); + if (p && p.esize) { + list.push(p); + } else if (!mode.prevent_external) { + list.push({ name: p }); + } + }); + if (!list.length) { + player.slots[slot].giveaway = 5; + } else { + if (!mode.prevent_external) { + var winner = random_one(list); + var item = player.slots[slot]; + player.slots[slot] = null; + delete item.list; + delete item.giveaway; + delete item.registry; + item.gf = player.name; + item.src = "gva"; + var mitem = JSON.stringify(item); + add_to_trade_history(player, "giveaway", winner.name, cache_item(item, true)); + xy_emit(player, "game_log", { + message: winner.name + " won " + player.name + "'s giveaway of " + G.items[item.name].name + "!", + color: "#42A0DC", + confetti: winner.name, + }); + appengine_call( + "send_mail", + { + fro: player.name, + to: winner.name, + subject: "You've won a giveaway!", + message: + "Congratulations, you won " + player.name + "'s giveaway. Participants were: " + list.join(", "), + rid: randomStr(50), + retries: 5, + item: mitem, + }, + function (result) {}, + function () { + console.log("#M unsent giveaway, lost item: " + mitem); + }, + ); + } else { + var winner = random_one(list); + var item = player.slots[slot]; + player.slots[slot] = null; + delete item.list; + delete item.giveaway; + delete item.registry; + item.gf = player.name; + item.src = "gva"; + add_item(winner, item); + add_to_trade_history(player, "giveaway", winner.name, cache_item(item, true)); + xy_emit(player, "game_log", { + message: winner.name + " won " + player.name + "'s giveaway of " + G.items[item.name].name + "!", + color: "#42A0DC", + confetti: winner.name, + }); + resend(winner, "reopen"); + } + } + } + player.cslots[slot] = cache_item(player.slots[slot], true); + resend(player, "u+cid" + ((!player.slots[slot] && "+reopen") || "")); + } + }); + } + } catch (e) { + log_trace("#X decay loop error", e); + } +}, 60000); + +var a_score = {}; // announcement score +setInterval(function () { + try { + for (var id in a_score) { + a_score[id] *= 0.99; + } + } catch (e) { + log_trace("#X ascore loop error", e); + } +}, 10000); + +setInterval(function () { + try { + for (var id in players) { + var player = players[id]; + player.xrange = min(25, player.xrange + 5); + } + } catch (e) { + log_trace("#X xrange loop error", e); + } +}, 1000); + +setInterval(function () { + try { + var c = new Date(); + for (var id in players) { + var player = players[id]; + if (player.auth_id && player.p.first === undefined && !player.first_u_call) { + function first_call(player) { + player.first_u_call = appengine_call( + "is_first", + { auth: player.auth, auth_id: player.auth_id, character: player.real_id, suffix: "/" + player.id }, + function (result) { + if (result.first) { + player.p.first = true; + } else { + player.p.first = false; + } + }, + function () { + delete player.first_u_call; + }, + ); + } + first_call(player); + } + if (player.p.first && !player.p.dt.first) { + realm_broadcast("server_message", { message: player.name + " joined Adventure Land!", color: "#24A2FA" }); + player.p.dt.first = future_h(24 * 7); + } else if (player.p.dt.first && player.p.dt.first > c) { + player.paura = player.paura || {}; + player.paura["newcomersblessing"] = { attr0: 0, attr1: 0 }; + } + } + } catch (e) { + log_trace("#X first_character loop error", e); + } +}, 4000); + +function projectiles_loop() { + var now = new Date(); + for (var id in projectiles) { + try { + if (projectiles[id].eta <= now) { + var projectile = projectiles[id]; + delete projectiles[id]; + complete_attack(projectile.attacker, projectile.target, projectile); + } + } catch (e) { + log_trace("#X projectile loop error", e); + } + } } -setTimeout(game_loop,10); -setTimeout(npc_loop,10); -setTimeout(instance_loop,10); -setTimeout(aura_loop,10); -setInterval(count_unique_users,6000); - -setInterval(function(){ - try{ - for(var id in observers) - { - var observer=observers[id]; - if(observer.player && get_player(observer.player.name)) - { - var player=get_player(observer.player.name); - if(simple_distance(observer,player)>200) - transport_observer_to(observer,player.in,player.map,player.x,player.y+(observer.socket.desktop&&120||0)); - } - else - { - if(simple_distance(observer,{map:observer_map,in:observer_map,x:observer_x,y:observer_y})>200) - transport_observer_to(observer,observer_map,observer_map,observer_x,observer_y+(observer.socket.desktop&&120||0)); - } - } - }catch(e){log_trace("#X observer loop2 error",e);}; -},13200); - -setInterval(function(){ - try{ - if(is_pvp) return; - var names=[],player; - total_merchants=0; - for(var id in players) - { - player=players[id]; - if(!player.npc && player.last.attack && player.map==player.in && ssince(player.last.attack)<3 && !G.maps[player.map].pvp) - { - names.push(player.name); - } - if(player.p && player.p.stand) total_merchants+=1; - } - if(names.length) - { - player=get_player(names[floor(Math.random()*names.length)]); - observer_map=player.map; - observer_x=parseInt(player.x); - observer_y=parseInt(player.y); - server_log("Set the observer location: "+[observer_map,observer_x,observer_y]); - } - else - { - observer_map=merchant_map; - observer_x=merchant_x; - observer_y=merchant_y; - } - - }catch(e){log_trace("#X observer loop error",e);}; -},4000); - -setTimeout(function(){ - setInterval(function(){ - for(var id in players) - { - var player=players[id]; - if(player.type=="merchant" && player.p.stand) - { - var xp=1000; - if(player.level<=40) xp=G.levels[player.level]/100; - else if(player.level<=60) xp=G.levels[player.level]/200; - else if(player.level<=70) xp=G.levels[player.level]/400; - else xp=G.levels[70]/400; - xp=parseInt(Math.round(xp)); - player.xp+=xp; - player.socket.emit("game_log","Gained "+to_pretty_num(xp)+" marketing XP"); - player.socket.emit("disappearing_text",{"message":"+"+xp,"x":player.x,"y":player.y-32,"args":{color:"gray","size":"large"}}); - resend(player,"reopen+u+cid"); - } - } - },3*60*60*1000); //3 -},1*60*60*1000); //1 - -setInterval(function(){ - try{ - var edge=future_s(-120); - for(var id in players) - { - if(0 && players[id].last_ipass50) - pause_instance(instance); - if(Object.keys(instance.players).length>instance.npcs || Object.keys(instance.observers).length) - instance.last_player=new Date(); - } - }catch(e){log_trace("#X pause loop error",e);}; - },2000); -},60000); // delay for a minute, so citizens move around - -setInterval(function(){ - try{ - for(var oname in parties) - { - send_party_update(oname); - } - }catch(e){log_trace("#X party loop error",e);}; -},60000); - -setInterval(function(){ - try{ - for(var id in players) - { - var player=players[id]; - players[id].pdps*=0.8; - player.p.minutes++; - trade_slots.forEach(function(slot){ - if(player.slots[slot] && player.slots[slot].giveaway) - { - player.slots[slot].giveaway--; - if(!player.slots[slot].giveaway) - { - var list=[]; - player.slots[slot].list.forEach(function(p){ - var p=get_player(p); - if(p && p.esize) list.push(p); - else if(!mode.prevent_external) list.push({name:p}); - }); - if(!list.length) player.slots[slot].giveaway=5; - else - { - if(!mode.prevent_external) - { - - var winner=random_one(list); - var item=player.slots[slot]; - player.slots[slot]=null; - delete item.list; delete item.giveaway; delete item.registry; - item.gf=player.name; item.src="gva"; - var mitem=JSON.stringify(item); - add_to_trade_history(player,"giveaway",winner.name,cache_item(item,true)); - xy_emit(player,"game_log",{message:winner.name+" won "+player.name+"'s giveaway of "+G.items[item.name].name+"!",color:"#42A0DC",confetti:winner.name}); - appengine_call("send_mail",{fro:player.name,to:winner.name,subject:"You've won a giveaway!",message:"Congratulations, you won "+player.name+"'s giveaway. Participants were: "+list.join(", "),rid:randomStr(50),retries:5,item:mitem},function(result){ - - },function(){ - console.log("#M unsent giveaway, lost item: "+mitem); - }); - } - else - { - var winner=random_one(list); - var item=player.slots[slot]; - player.slots[slot]=null; - delete item.list; delete item.giveaway; delete item.registry; - item.gf=player.name; item.src="gva"; - add_item(winner,item); - add_to_trade_history(player,"giveaway",winner.name,cache_item(item,true)); - xy_emit(player,"game_log",{message:winner.name+" won "+player.name+"'s giveaway of "+G.items[item.name].name+"!",color:"#42A0DC",confetti:winner.name}); - resend(winner,"reopen"); - } - } - - } - player.cslots[slot]=cache_item(player.slots[slot],true); - resend(player,"u+cid"+(!player.slots[slot]&&"+reopen"||"")); - } - }); - } - }catch(e){log_trace("#X decay loop error",e);}; -},60000); - - -var a_score={}; // announcement score -setInterval(function(){ - try{ - for(var id in a_score) - { - a_score[id]*=0.99; - } - }catch(e){log_trace("#X ascore loop error",e);}; -},10000); - -setInterval(function(){ - try{ - for(var id in players) - { - var player=players[id]; - player.xrange=min(25,player.xrange+5); - } - }catch(e){log_trace("#X xrange loop error",e);}; -},1000); - -setInterval(function(){ - try{ - var c=new Date(); - for(var id in players) - { - var player=players[id]; - if(player.auth_id && player.p.first===undefined && !player.first_u_call) - { - function first_call(player) - { - player.first_u_call=appengine_call("is_first",{auth:player.auth,auth_id:player.auth_id,character:player.real_id,suffix:"/"+player.id},function(result){ - if(result.first) player.p.first=true; - else player.p.first=false; - },function(){ - delete player.first_u_call; - }); - } - first_call(player); - } - if(player.p.first && !player.p.dt.first) - { - realm_broadcast("server_message",{message:player.name+" joined Adventure Land!",color:"#24A2FA"}); - player.p.dt.first=future_h(24*7); - } - else if(player.p.dt.first && player.p.dt.first>c) - { - player.paura=player.paura||{}; - player.paura["newcomersblessing"]={attr0:0,attr1:0}; - } - } - }catch(e){log_trace("#X first_character loop error",e);}; -},4000); - -function projectiles_loop() -{ - var now=new Date(); - for(var id in projectiles) - { - try{ - if(projectiles[id].eta<=now) - { - var projectile=projectiles[id]; - delete projectiles[id]; - complete_attack(projectile.attacker,projectile.target,projectile); - } - }catch(e){log_trace("#X projectile loop error",e);}; - } -} +setInterval(projectiles_loop, 7); -setInterval(projectiles_loop,7); - -var accel=1; -if(mode.fast_mlevels) accel=100; -setInterval(function(){ - try{ - for(var id in instances) - { - for(var mid in (instances[id].monsters||[])) - { - var monster=instances[id].monsters[mid]; - if(G.monsters[monster.type].cute || G.monsters[monster.type].peaceful || G.monsters[monster.type].stationary || monster.target) continue; - var exp=Math.pow(2,(monster.level-1)*0.3),mult=1; - if(monster["1hp"]) mult=200; - else if(G.monsters[monster.type].special) mult=20; - if(mssince(monster.last_level)>max(180000*exp*mult/accel,monster.max_hp*mult*30*exp/accel)) - { - if(monster.temp) remove_monster(monster); - else level_monster(monster); - } - } - } - }catch(e){log_trace("#X mlevel loop error",e);}; -},16000/accel); - -setInterval(function(){ - try{ - for(var id in instances) - { - for(var mid in (instances[id].monsters||[])) - { - var monster=instances[id].monsters[mid]; - if(G.monsters[monster.type].cute || G.monsters[monster.type].stationary || !monster.target) continue; - var target=get_player(monster.target); - if(!target || (target.targets>1 && monster.hpG.items[player.slots[s].name].charge && Math.random()<0.96) continue; - player.slots[s].charges=(player.slots[s].charges||0)+1; - player.cslots[s]=cache_item(player.slots[s]); - } - } - for(var i=0;iG.items[player.items[i].name].charge && Math.random()<0.96) continue; - player.items[i].charges=(player.items[i].charges||0)+1; - player.citems[i]=cache_item(player.items[i]); - } - } - } - } - }catch(e){log_trace("#X random grace loop error",e);}; -},10*60000); // every 10 minutes - -setInterval(function(){ - try{ - server_loot(); - }catch(e){log_trace("#X server loot error",e);}; -},4*60*60*1000); - -setInterval(function(){ - try{ - if(server.live) broadcast_e(); - }catch(e){log_trace("#X broadcast error",e);}; -},24*1000); - -function sync_loop() -{ - function check_for_delays(player) - { - var limit=6; - if(player.mounting && msince(player.mounting)>limit && !player.mount_issue) - { - server_log("#X SEVERE: "+limit+" minutes and still mounting: "+player.name,1); - player.mount_issue=new Date(); - } - if(player.unmounting && msince(player.unmounting)>limit && !player.unmount_issue) - { - server_log("#X SEVERE: "+limit+" minutes and still unmounting: "+player.name,1); - player.unmount_issue=new Date(); - } - if(player.sync_call && msince(player.last_sync)>limit && !player.sync_issue) - { - server_log("#X SEVERE: "+limit+" minutes and still syncing: "+player.name,1); - // player.sync_issue=new Date(); - delete player.sync_call; - } - if(player.stopping && msince(player.stopping)>limit && !player.stop_issue) - { - server_log("#X SEVERE: "+limit+" minutes and still stopping: "+player.name,1); - player.stop_issue=new Date(); - } - } - function mount_call(player) - { - // player.last_sync=new Date(); - sync doesn't happen at mount - maybe it should [16/08/17] - player.mount_call=appengine_call("mount_user",{auth:player.auth,character:player.real_id,suffix:"/"+player.id,to:player.mount_to},function(result){ - server_log("mount_user: "+player.name+" owner: "+player.owner,1); - delete player.mounting; - delete player.mount_call; - if(result.failed) - { - server_log("mount_user[failed]: "+player.name+" in-bank: "+result.name+" result: "+JSON.stringify(result),1); - player.socket.emit("game_response",{response:"bank_opx",name:result.name,reason:result.reason}); - return; - } - player.user=result.user; - init_bank(player); - if(players[player.socket.id]) - { - transport_player_to(player,player.mount_to,player.mount_s); - resend(player); - } - },function(){ - delete player.mounting; - delete player.mount_call; - server_log("#X SEVERE-ish: Mount failed for "+player.name,1); - }); - } - function unmount_call(player) - { - player.last_sync=new Date(); - init_bank_exit(player); - player.unmount_call=appengine_call("sync_character",{auth:player.auth,character:player.real_id,data:player_to_server(player,"sync"),user_data:player.user||"",retries:100,unmount:1,suffix:"/"+player.name+"/unmount" - },function(result){ - server_log("unmount_user[sync]: "+player.name+" owner: "+player.owner+" result: "+JSON.stringify(result),1); - delete player.unmount_call; - if(result.failed) { server_log("unmount_user[failed]: "+player.name+" owner: "+player.owner,1); return; } - delete player.unmounting; - player.user=null; player.cuser=null; - if(players[player.socket.id] && player.unmount_to) - { - transport_player_to(player,player.unmount_to,player.unmount_s); - } - if(players[player.socket.id]) resend(player); - },function(){ - server_log("#X SEVERE: Unmount failed for "+player.name,1); - delete player.unmounting; - delete player.unmount_call; - if(players[player.socket.id]) players[socket.id].socket.disconnect(); - }); - } - function sync_call(player) - { - player.last_sync=new Date(); - player.sync_call=appengine_call("sync_character",{auth:player.auth,character:player.real_id,data:player_to_server(player,"sync"),user_data:player.user||"",retries:0,suffix:"/"+player.name}, - function(result){ - if(result && result.reason=="notingame") - { - server_log("#X SEVERE: sync notingame disconnect for "+player.name,1); - try{player.socket.disconnect();}catch(e){}; - } - delete player.sync_call; - }, - function(){delete player.sync_call}); - } - function stop_call(player) - { - var bank=false; - if(player.user) bank=true; - player.stopping=new Date(); - init_player_exit(player); - if(player.unmount_call) player.unmount_call.retries=0; - player.stop_call=appengine_call("stop_character",{auth:player.auth,character:player.real_id,data:player_to_server(player),user_data:player.user||"",retries:CINF,suffix:"/"+player.name},function(result){ - server_log("stop_character: "+player.name+" owner: "+player.owner+" bank: "+bank+" result: "+JSON.stringify(result),1); - if(result.done) delete dc_players[player.real_id]; - delete player.stop_call; - },function(){ - server_log("#X SEVERE: stop_character failed for "+player.name,1); - delete player.stop_call; - }); - } - - for(var id in players) - { - if(gameplay=="hardcore" || gameplay=="test") return; - var player=players[id]; - try{ - - check_for_delays(player); - - if(player.unmount_call || player.mount_call || player.sync_call || player.stop_call); - else if(!server.live) player.socket.disconnect(); - else if(player.unmounting) unmount_call(player); - else if(player.mounting) mount_call(player); - else if(msince(player.last_sync)>5 && !player.unmounting && !player.mounting) sync_call(player); - - }catch(e){log_trace("#X dc loop error1",e);}; - } - for(var id in dc_players) - { - if(gameplay=="hardcore" || gameplay=="test") return; - var player=dc_players[id]; - try{ - - check_for_delays(player); - - if(player.unmount_call || player.mount_call || player.sync_call || player.stop_call); - else if(!player.stop_call) stop_call(player); - - }catch(e){log_trace("#X dc loop error2",e);}; - } +var accel = 1; +if (mode.fast_mlevels) { + accel = 100; +} +setInterval(function () { + try { + for (var id in instances) { + for (var mid in instances[id].monsters || []) { + var monster = instances[id].monsters[mid]; + if ( + G.monsters[monster.type].cute || + G.monsters[monster.type].peaceful || + G.monsters[monster.type].stationary || + monster.target + ) { + continue; + } + var exp = Math.pow(2, (monster.level - 1) * 0.3); + var mult = 1; + if (monster["1hp"]) { + mult = 200; + } else if (G.monsters[monster.type].special) { + mult = 20; + } + if ( + mssince(monster.last_level) > max((180000 * exp * mult) / accel, (monster.max_hp * mult * 30 * exp) / accel) + ) { + if (monster.temp) { + remove_monster(monster); + } else { + level_monster(monster); + } + } + } + } + } catch (e) { + log_trace("#X mlevel loop error", e); + } +}, 16000 / accel); + +setInterval(function () { + try { + for (var id in instances) { + for (var mid in instances[id].monsters || []) { + var monster = instances[id].monsters[mid]; + if (G.monsters[monster.type].cute || G.monsters[monster.type].stationary || !monster.target) { + continue; + } + var target = get_player(monster.target); + if (!target || (target.targets > 1 && monster.hp < monster.max_hp * 0.23)) { + continue; + } + monster.extra_gold = + (monster.extra_gold || 0) + + ((min(min(2400, target.attack) / 220.0 + monster.attack / 1.2, 55) + 19) * + ((gameplay == "hardcore" && 100) || 1)) / + (max((target.targets - 1) * (target.targets - 1), 1) || 1); + } + } + } catch (e) { + log_trace("#X mgold loop error", e); + } +}, 2000); + +setInterval(function () { + try { + for (var id in players) { + var player = players[id]; + if (Math.random() < 1.0 / (6 * 15)) { + // once every 15 hours + var slots = []; + character_slots.forEach(function (slot) { + if ( + player.slots[slot] && + (G.items[player.slots[slot].name].upgrade || G.items[player.slots[slot].name].compound) + ) { + slots.push(slot); + } + }); + if (!slots.length) { + continue; + } + var slot = random_one(slots); + player.slots[slot].grace = (player.slots[slot].grace || 0) + 0.4; + console.log("random grace " + player.name + " " + slot + " " + player.slots[slot].grace); + } + if (player.computer && Math.random() < 1.0 / (6 * 1)) { + // once every hour + for (var s in player.slots) { + if (player.slots[s] && G.items[player.slots[s].name].charge) { + if (player.slots[s].charges > G.items[player.slots[s].name].charge && Math.random() < 0.96) { + continue; + } + player.slots[s].charges = (player.slots[s].charges || 0) + 1; + player.cslots[s] = cache_item(player.slots[s]); + } + } + for (var i = 0; i < player.items.length; i++) { + if (player.items[i] && G.items[player.items[i].name].charge) { + if (player.items[i].charges > G.items[player.items[i].name].charge && Math.random() < 0.96) { + continue; + } + player.items[i].charges = (player.items[i].charges || 0) + 1; + player.citems[i] = cache_item(player.items[i]); + } + } + } + } + } catch (e) { + log_trace("#X random grace loop error", e); + } +}, 10 * 60000); // every 10 minutes + +setInterval( + function () { + try { + server_loot(); + } catch (e) { + log_trace("#X server loot error", e); + } + }, + 4 * 60 * 60 * 1000, +); + +setInterval(function () { + try { + if (server.live) { + broadcast_e(); + } + } catch (e) { + log_trace("#X broadcast error", e); + } +}, 24 * 1000); + +function sync_loop() { + function check_for_delays(player) { + var limit = 6; + if (player.mounting && msince(player.mounting) > limit && !player.mount_issue) { + server_log("#X SEVERE: " + limit + " minutes and still mounting: " + player.name, 1); + player.mount_issue = new Date(); + } + if (player.unmounting && msince(player.unmounting) > limit && !player.unmount_issue) { + server_log("#X SEVERE: " + limit + " minutes and still unmounting: " + player.name, 1); + player.unmount_issue = new Date(); + } + if (player.sync_call && msince(player.last_sync) > limit && !player.sync_issue) { + server_log("#X SEVERE: " + limit + " minutes and still syncing: " + player.name, 1); + // player.sync_issue=new Date(); + delete player.sync_call; + } + if (player.stopping && msince(player.stopping) > limit && !player.stop_issue) { + server_log("#X SEVERE: " + limit + " minutes and still stopping: " + player.name, 1); + player.stop_issue = new Date(); + } + } + function mount_call(player) { + // player.last_sync=new Date(); - sync doesn't happen at mount - maybe it should [16/08/17] + player.mount_call = appengine_call( + "mount_user", + { auth: player.auth, character: player.real_id, suffix: "/" + player.id, to: player.mount_to }, + function (result) { + server_log("mount_user: " + player.name + " owner: " + player.owner, 1); + delete player.mounting; + delete player.mount_call; + if (result.failed) { + server_log( + "mount_user[failed]: " + player.name + " in-bank: " + result.name + " result: " + JSON.stringify(result), + 1, + ); + player.socket.emit("game_response", { response: "bank_opx", name: result.name, reason: result.reason }); + return; + } + player.user = result.user; + init_bank(player); + if (players[player.socket.id]) { + transport_player_to(player, player.mount_to, player.mount_s); + resend(player); + } + }, + function () { + delete player.mounting; + delete player.mount_call; + server_log("#X SEVERE-ish: Mount failed for " + player.name, 1); + }, + ); + } + function unmount_call(player) { + player.last_sync = new Date(); + init_bank_exit(player); + player.unmount_call = appengine_call( + "sync_character", + { + auth: player.auth, + character: player.real_id, + data: player_to_server(player, "sync"), + user_data: player.user || "", + retries: 100, + unmount: 1, + suffix: "/" + player.name + "/unmount", + }, + function (result) { + server_log( + "unmount_user[sync]: " + player.name + " owner: " + player.owner + " result: " + JSON.stringify(result), + 1, + ); + delete player.unmount_call; + if (result.failed) { + server_log("unmount_user[failed]: " + player.name + " owner: " + player.owner, 1); + return; + } + delete player.unmounting; + player.user = null; + player.cuser = null; + if (players[player.socket.id] && player.unmount_to) { + transport_player_to(player, player.unmount_to, player.unmount_s); + } + if (players[player.socket.id]) { + resend(player); + } + }, + function () { + server_log("#X SEVERE: Unmount failed for " + player.name, 1); + delete player.unmounting; + delete player.unmount_call; + if (players[player.socket.id]) { + players[socket.id].socket.disconnect(); + } + }, + ); + } + function sync_call(player) { + player.last_sync = new Date(); + player.sync_call = appengine_call( + "sync_character", + { + auth: player.auth, + character: player.real_id, + data: player_to_server(player, "sync"), + user_data: player.user || "", + retries: 0, + suffix: "/" + player.name, + }, + function (result) { + if (result && result.reason == "notingame") { + server_log("#X SEVERE: sync notingame disconnect for " + player.name, 1); + try { + player.socket.disconnect(); + } catch (e) {} + } + delete player.sync_call; + }, + function () { + delete player.sync_call; + }, + ); + } + function stop_call(player) { + var bank = false; + if (player.user) { + bank = true; + } + player.stopping = new Date(); + init_player_exit(player); + if (player.unmount_call) { + player.unmount_call.retries = 0; + } + player.stop_call = appengine_call( + "stop_character", + { + auth: player.auth, + character: player.real_id, + data: player_to_server(player), + user_data: player.user || "", + retries: CINF, + suffix: "/" + player.name, + }, + function (result) { + server_log( + "stop_character: " + + player.name + + " owner: " + + player.owner + + " bank: " + + bank + + " result: " + + JSON.stringify(result), + 1, + ); + if (result.done) { + delete dc_players[player.real_id]; + } + delete player.stop_call; + }, + function () { + server_log("#X SEVERE: stop_character failed for " + player.name, 1); + delete player.stop_call; + }, + ); + } + + for (var id in players) { + if (gameplay == "hardcore" || gameplay == "test") { + return; + } + var player = players[id]; + try { + check_for_delays(player); + + if (player.unmount_call || player.mount_call || player.sync_call || player.stop_call) { + } else if (!server.live) { + player.socket.disconnect(); + } else if (player.unmounting) { + unmount_call(player); + } else if (player.mounting) { + mount_call(player); + } else if (msince(player.last_sync) > 5 && !player.unmounting && !player.mounting) { + sync_call(player); + } + } catch (e) { + log_trace("#X dc loop error1", e); + } + } + for (var id in dc_players) { + if (gameplay == "hardcore" || gameplay == "test") { + return; + } + var player = dc_players[id]; + try { + check_for_delays(player); + + if (player.unmount_call || player.mount_call || player.sync_call || player.stop_call) { + } else if (!player.stop_call) { + stop_call(player); + } + } catch (e) { + log_trace("#X dc loop error2", e); + } + } } -function server_loop() -{ - // #IMPORTANT: sometimes none of the success/error callbacks trigger [20/08/19] - if(server.update_call && msince(server.update_call.init)>4) - delete server.update_call; - if(server.update_call || server.stop_call); - else if(server.live && ssince(server.last_update)>75) - { - server.update_call=appengine_call("update_server",{ - keyword:variables.keyword, - id:server_id, - players:Object.keys(players).length, - observers:Object.keys(observers).length, - merchants:total_merchants, - total_players:total_players, - data:S, - retries:3, - },function(result){ - server.last_update=new Date(); - server_log("Server update sent"); - delete server.update_call; - },function(){ - server_log("Server update failed",1); - delete server.update_call; - }); - } - else if(server.started && !server.live && !server.stopped) - { - server.stop_call=appengine_call("stop_server",{keyword:variables.keyword,id:server_id,retries:CINF,data:S},function(result){ - server_log("stop_server success",1); - // these are here as shutting the server first disrupts connections in actual servers - // io.close(); app.close(); - decided to comment these out, they don't do anything now [23/09/16] - server.stopped=true; - delete server.stop_call; - }); - } - else if(server.stopped && (!Object.keys(dc_players).length && !Object.keys(players).length || (gameplay=="hardcore" || gameplay=="test"))) process.exit(); - else if(server.stopped) sync_loop(); +function server_loop() { + // #IMPORTANT: sometimes none of the success/error callbacks trigger [20/08/19] + if (server.update_call && msince(server.update_call.init) > 4) { + delete server.update_call; + } + if (server.update_call || server.stop_call) { + } else if (server.live && ssince(server.last_update) > 75) { + server.update_call = appengine_call( + "update_server", + { + keyword: variables.keyword, + id: server_id, + players: Object.keys(players).length, + observers: Object.keys(observers).length, + merchants: total_merchants, + total_players: total_players, + data: S, + retries: 3, + }, + function (result) { + server.last_update = new Date(); + server_log("Server update sent"); + delete server.update_call; + }, + function () { + server_log("Server update failed", 1); + delete server.update_call; + }, + ); + } else if (server.started && !server.live && !server.stopped) { + server.stop_call = appengine_call( + "stop_server", + { keyword: variables.keyword, id: server_id, retries: CINF, data: S }, + function (result) { + server_log("stop_server success", 1); + // these are here as shutting the server first disrupts connections in actual servers + // io.close(); app.close(); - decided to comment these out, they don't do anything now [23/09/16] + server.stopped = true; + delete server.stop_call; + }, + ); + } else if ( + server.stopped && + ((!Object.keys(dc_players).length && !Object.keys(players).length) || gameplay == "hardcore" || gameplay == "test") + ) { + process.exit(); + } else if (server.stopped) { + sync_loop(); + } } -setInterval(sync_loop,24000); -setInterval(server_loop,1000); +setInterval(sync_loop, 24000); +setInterval(server_loop, 1000); -function shutdown() -{ - server_log("shutdown",1); - server.live=false; - if(!server.exists) server_loot("all"); - sync_loop(); +function shutdown() { + server_log("shutdown", 1); + server.live = false; + if (!server.exists) { + server_loot("all"); + } + sync_loop(); } -function shutdown_routine() -{ - server_log("shutdown_routine",1); - server.shutdown=true; - for(var name in instances) - { - for(var id in instances[name].monsters) - { - var monster=instances[name].monsters[id]; - if(monster.target) - { - monster.u=true; - monster.cid++; - monster.mult=(monster.max_hp-monster.hp)/monster.max_hp; - monster.hp=1; - } - } - } - var seconds=20; - if(is_sdk) seconds=1; - else if(gameplay=="hardcore") seconds=240; - workers.forEach(function(worker){ - try{worker.postMessage({type:"exit"});} - catch(e){console.log(e);} - }); - 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]=[]; + D.drops.monsters[m].push([1.0 / 100, "glitch"]); + D.drops.monsters[m].push([1.0 / 100, "glitch"]); + D.drops.monsters[m].push([1.0 / 1000, "glitch"]); + } + for (var n in D.drops) { + if (!is_array(D.drops[n])) { + continue; + } + var total = 0; + for (var i = 0; i < D.drops[n].length; i++) { + total += D.drops[n][i][0]; + } + for (var i = 0; i < D.drops[n].length; i++) { + if (D.drops[n][i][0] < 0.001) { + D.drops[n][i][0] *= 200; + } - for(var i=0;i total / 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] = []; + } - D.drops.maps[mname].push([1.0/1200,"gem0"]); - } - D.drops.gem0.push([0.5,"candycane"]); - D.drops.gem0.push([0.05,"basketofeggs"]); - D.drops.monsters.iceroamer.push([0.1,"open","statbelt"]); - D.drops.monsters.arcticbee.push([0.00001,"fclaw"]); - D.drops.monsters.minimush.push([0.00001,"throwingstars"]); - D.drops.monsters.squig.push([0.000008,"glitch"]); - D.drops.monsters.bbpompom.push([0.00008,"glitch"]); - D.drops.monsters.croc.push([0.000008,"glitch"]); - D.drops.monsters.mole.push([0.002,"gemfragment"]); - D.drops.monsters.mole.push([0.002,"gemfragment"]); - D.drops.maps.mansion.push([0.001,"lostearring"]); - D.drops.maps.mansion.push([0.001,"lostearring"]); - D.drops.monsters.croc.push([1,"seashell"]); - D.drops.monsters.croc.push([1,"seashell"]); - D.drops.maps.global=[]; - D.monster_gold.bat*=50; - D.monster_gold.bbpompom*=75; - D.monster_gold.ghost*=50; - events.egghunt=0.1; - } - D.craftmap={}; - for(var name in G.craft) - { - var items=[]; - G.craft[name].items.forEach(function(x){ - var name=x[1]; - if(x[2]) name+="+"+x[2]; - items.push(name); - }); - items.sort(); - var key=items.join(","); - D.craftmap[key]=name; - } - process_game_data(); - for(var name in G.monsters) - { - stats.kills[name]=stats.kills[name]||0; - } - D.base_gold={}; - for(var mname in G.maps) - { - var map=G.maps[mname]; - (map.monsters||[]).forEach(function(pack){ - var def=G.monsters[pack.type],drop_value=0,map_value=0,global_value=0,hp_mult=def.hp/1000.0; - // #BETTER IDEA# - // track damage done + dps utilisation / pack fullness - calculate gold based on that - // /1400.0 /40.000 /14000.0 originally - // 0.0624 is the original modifier - pack.gold=def.hp*(0.0782+max(def.charge-40,0)/1900.0+(1-dps_multiplier((def.attack+(def.apiercing||0)/2+(def.rpiercing||0)/2)))*1000/120000.0+min((1-damage_multiplier(def.armor||0))*1000+(def.evasion||0)*10+(def.avoidance||0)*10,(1-damage_multiplier(def.resistance||0))*1000+(def.reflection||0)*25+(def.avoidance||0)*10)/12000.0)-def.xp*0.0172; - if((map.pvp || map.safe_pvp) && !is_pvp) pack.gold*=2; - drop_value=calculate_xvalue(D.drops.monsters[pack.type]||[]); - map_value=calculate_xvalue(D.drops.maps[mname]||[],undefined,undefined,hp_mult); - global_value=calculate_xvalue(D.drops.maps.global||[],undefined,undefined,hp_mult); - pack.gold=pack.gold-map_value/1.002-drop_value*1.002; // Originally 1.2 + for (var i = 0; i < D.drops.maps[mname].length; i++) { + D.drops.maps[mname][i][0] *= 200; + D.drops.maps[mname][i][0] = min(1, D.drops.maps[mname][i][0]); + } + D.drops.maps[mname].push([1.0 / 1200, "gem0"]); + } + D.drops.gem0.push([0.5, "candycane"]); + D.drops.gem0.push([0.05, "basketofeggs"]); + D.drops.monsters.iceroamer.push([0.1, "open", "statbelt"]); + D.drops.monsters.arcticbee.push([0.00001, "fclaw"]); + D.drops.monsters.minimush.push([0.00001, "throwingstars"]); + D.drops.monsters.squig.push([0.000008, "glitch"]); + D.drops.monsters.bbpompom.push([0.00008, "glitch"]); + D.drops.monsters.croc.push([0.000008, "glitch"]); + D.drops.monsters.mole.push([0.002, "gemfragment"]); + D.drops.monsters.mole.push([0.002, "gemfragment"]); + D.drops.maps.mansion.push([0.001, "lostearring"]); + D.drops.maps.mansion.push([0.001, "lostearring"]); + D.drops.monsters.croc.push([1, "seashell"]); + D.drops.monsters.croc.push([1, "seashell"]); + D.drops.maps.global = []; + D.monster_gold.bat *= 50; + D.monster_gold.bbpompom *= 75; + D.monster_gold.ghost *= 50; + events.egghunt = 0.1; + } + D.craftmap = {}; + for (var name in G.craft) { + var items = []; + G.craft[name].items.forEach(function (x) { + var name = x[1]; + if (x[2]) { + name += "+" + x[2]; + } + items.push(name); + }); + items.sort(); + var key = items.join(","); + D.craftmap[key] = name; + } + process_game_data(); + for (var name in G.monsters) { + stats.kills[name] = stats.kills[name] || 0; + } + D.base_gold = {}; + for (var mname in G.maps) { + var map = G.maps[mname]; + (map.monsters || []).forEach(function (pack) { + var def = G.monsters[pack.type]; + var drop_value = 0; + var map_value = 0; + var global_value = 0; + var hp_mult = def.hp / 1000.0; + // #BETTER IDEA# + // track damage done + dps utilisation / pack fullness - calculate gold based on that + // /1400.0 /40.000 /14000.0 originally + // 0.0624 is the original modifier + pack.gold = + def.hp * + (0.0782 + + max(def.charge - 40, 0) / 1900.0 + + ((1 - dps_multiplier(def.attack + (def.apiercing || 0) / 2 + (def.rpiercing || 0) / 2)) * 1000) / 120000.0 + + min( + (1 - damage_multiplier(def.armor || 0)) * 1000 + (def.evasion || 0) * 10 + (def.avoidance || 0) * 10, + (1 - damage_multiplier(def.resistance || 0)) * 1000 + + (def.reflection || 0) * 25 + + (def.avoidance || 0) * 10, + ) / + 12000.0) - + def.xp * 0.0172; + if ((map.pvp || map.safe_pvp) && !is_pvp) { + pack.gold *= 2; + } + drop_value = calculate_xvalue(D.drops.monsters[pack.type] || []); + map_value = calculate_xvalue(D.drops.maps[mname] || [], undefined, undefined, hp_mult); + global_value = calculate_xvalue(D.drops.maps.global || [], undefined, undefined, hp_mult); + pack.gold = pack.gold - map_value / 1.002 - drop_value * 1.002; // Originally 1.2 - if(def.hp<=2000 && pack.gold0.08 && def.hp>900) pack.gold*=0.7; - // if((pack.gold/def.hp)>0.08 && def.hp>900) pack.gold*=0.7; + // if(def.hp>=50000) pack.gold*=0.90; // better dps utilisation - don't punish this - // 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"]); + } - 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.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; - } + 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 calculate_xvalue(arr, rec, divide, mult) { + var value = 0; + var 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]]; + var q = drop[2] || 1; + var g = def.g; + var 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_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 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_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 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; + var characters = 0; + var ips = 0; + var ipx = player.ipx || 1; + var 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 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 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 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_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_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_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_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 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 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 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 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 secondhands_logic(item, quantity) { + var new_item = cache_item(item); + var done = false; + var count = 0; + var 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 < S.sold.length; i++) { + if (done) { + return; + } + if (can_stack(S.sold[i], new_item)) { + done = true; + S.sold[i].q = S.sold[i].q + new_item.q; + csold[i] = cache_item(S.sold[i], true); + } + if (S.sold[i].name == new_item.name && S.sold[i].level == new_item.level) { + if (!S.sold[i].p) { + replace = i; + } + count += 1; + } + } + if (!done && (count < 5 || (new_item.p && replace !== null))) { + var current = S.sold.length % 400; + if (count >= 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 lostandfound_logic(item) { + var done = false; + var count = 0; + var replace = null; + item.rid = randomStr(5); + for (var i = 0; i < S.found.length; i++) { + if (done) { + return; + } + if (can_stack(S.found[i], item)) { + done = true; + S.found[i].q = S.found[i].q + item.q; + cfound[i] = cache_item(S.found[i], true); + } + if (S.found[i].name == item.name && S.found[i].level == item.level) { + if (!S.found[i].p) { + replace = i; + } + count += 1; + } + } + if (!done && (count < 5 || (item.p && replace !== null))) { + var current = S.found.length % 400; + if (count >= 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_loot(type) { + if (type == "all") { + for (var mtype in D.drops.monsters) { + for (var j = 0; j < D.drops.monsters[mtype].length; j++) { + if (D.drops.monsters[mtype][j][0] > 0.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 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; + } + 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 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_mas_receipt(player, receipt) { + if (player.p.mas_hash == quick_hash(receipt) && player.p.dt.mas_expire > new 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 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 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 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) { + delete player.s.hopsickness; + player.p.entries = player.p.entries || []; + var servers = {}; + var main = null; + var mx = 0; + var 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 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 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); + var 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 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 get_nearby_ghash(hash, entity, zone) { + var d = [-1, 0, 1]; + var l = []; + var 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 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 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_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_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_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 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 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_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; +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; +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 = {}; + var 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 ([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 <= 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 <= 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; + 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_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 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 pause_instance(instance) -{ - server_log("Paused: "+instance.name); - instance.paused=true; +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 < map_def.count; j++) { + if (G.monsters[map_def.type].announce) { + setTimeout(new_monster_f(name, map_def), 120 * 1000); + } else { + new_monster(name, map_def); + } + } + } + // for(var i=0;i<(map.specials||[]).length;i++) + // { + // var map_def=map.specials[i]; + // new_monster(name,map_def,1); + // } + // console.log(JSON.stringify(instance.players)); + for (var i = 0; i < (map.npcs || []).length; i++) { + var map_def = map.npcs[i]; + var def = G.npcs[map_def.id]; + if (instance.players[NPC_prefix + def.name]) { + console.log("NPC NAME CLASH: " + def.name); + shutdown_routine(); + } + var npc = (instance.players[NPC_prefix + def.name] = create_npc(def, map_def, instance)); + npcs[map_def.id] = npc; + instance.npcs += 1; + } + map.spawns.forEach(function (spawn) { + var cdist = closest_line(map_name, spawn[0], spawn[1]); + if (cdist < 12) { + console.log( + "Spawn [" + map_name + "," + spawn[0] + "," + spawn[1] + "] is " + cdist + " close to a line! >=12 is a must.", + ); + } + }); + server_log("Created an instance of " + instances[name].map, 1); + return instance; } -function resume_instance(instance) -{ - if(!instance.paused) return; - // server_log("Resumed: "+instance.name); - instance.paused=false; - update_instance(instance); +function pause_instance(instance) { + server_log("Paused: " + instance.name); + instance.paused = true; } -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 resume_instance(instance) { + if (!instance.paused) { + return; + } + // server_log("Resumed: "+instance.name); + instance.paused = false; + update_instance(instance); } -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 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 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); - }); +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 = {}; + var 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); + } } -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; +function collect_signups(event) { + var names = []; + var 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; + } - 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; - } - }); + if (event == "abtesting") { + player.team = (toggle == 1 && "A") || "B"; + toggle = 1 - toggle; + } - 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;i= 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; + var 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]; + var 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; + var 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]; + var 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; + var a = 0; + var b = 0; + if (info.seconds) { + info.seconds--; + } + // console.log(info.A); console.log(info.B); + for (var i = 0; i < info.A.length; i++) { + var p = info.A[i]; + var player = get_player(p.name); + //console.log(player.in+" "+player.duel.id+" "+player.rip); + if (!player || player.in != id || !player.duel || player.rip || !player.team) { + p.active = false; + } + if (p.active) { + info.A[i] = player_to_summary(player); + a++; + info.A[i].active = true; + } + } + for (var i = 0; i < info.B.length; i++) { + var p = info.B[i]; + var player = get_player(p.name); + if (!player || player.in != id || !player.duel || player.rip || !player.team) { + p.active = false; + } + if (p.active) { + info.B[i] = player_to_summary(player); + b++; + info.B[i].active = true; + } + } + if (!info.seconds && !info.active) { + duel.a = []; + duel.b = []; + info.active = true; + info.A.forEach(function (p) { + var player = get_player(p.name); + if (player && p.active) { + player.s = {}; + duel.a.push(p.name); + resend(player, "u+cid"); + } + }); + info.B.forEach(function (p) { + var player = get_player(p.name); + if (player && p.active) { + player.s = {}; + duel.b.push(p.name); + resend(player, "u+cid"); + } + }); + } + if (!a || !b) { + var message = duel.challenger + " wins the duel!"; + var chat = duel.challenger + " defeated " + duel.vs + "!"; + var sent = {}; + if (!a) { + message = duel.vs + " wins the duel!"; + chat = duel.vs + " defeated " + duel.challenger + "!"; + } + for (var pid in instance.players) { + sent[instance.players[pid].name] = true; + instance.players[pid].socket.emit("game_chat", { message: message, color: "#47C1AE" }); + } + info.A.forEach(function (p) { + var player = get_player(p.name); + if (player && !sent[p.name]) { + player.socket.emit("game_chat", { message: message, color: "#47C1AE" }); + } + }); + info.B.forEach(function (p) { + var player = get_player(p.name); + if (player && !sent[p.name]) { + player.socket.emit("game_chat", { message: message, color: "#47C1AE" }); + } + }); + + destroy_instance(id); + + if (!a) { + info.B.forEach(function (p) { + var player = get_player(p.name); + if (player) { + xy_emit(player, "eval", "confetti_shower(get_player('" + player.name + "'),2)"); + } + }); + } else { + info.A.forEach(function (p) { + var player = get_player(p.name); + if (player) { + xy_emit(player, "eval", "confetti_shower(get_player('" + player.name + "'),2)"); + } + }); + } + + if (!a && get_player(duel.vs)) { + xy_emit(get_player(duel.vs), "game_chat", { message: chat, color: "#47C1AE" }); + } + if (!b && get_player(duel.challenger)) { + xy_emit(get_player(duel.challenger), "game_chat", { message: chat, color: "#47C1AE" }); + } + + delete E.duels[id]; + broadcast_e(); + continue; + } + duel.active = instance.active; + duel.seconds = instance.seconds; + instance_emit(id, "map_info", instance.info); + } + + for (var e in E) { + if ((E[e] && E[e].target) || Object.keys(E.duels || {}).length) { + change = true; + break; + } + } + if (change) { + broadcast_e(); + } + } catch (e) { + log_trace("Critical-event_loop: ", e); + } } -function new_worker(num) -{ - var worker=new Worker(variables.worker_path,{workerData:{G:G,amap_data:amap_data,smap_data:smap_data},env:SHARE_ENV,execArgv:[]}); - worker.wnum=num; - worker.on('message',function(data){ - if(data.type=="monster_move") - { - var monster=instances[data.in].monsters[data.id]; - if(!monster) return; - monster.working=false; - if(data.move && (data.move[2]!="bad" || (monster.bmoves||0)<6)) - { - if(data.move[2]=="bad") monster.bmoves=(monster.bmoves||0)+1; - else monster.bmoves=0; - monster.going_x=data.move[0]; - monster.going_y=data.move[1]; - start_moving_element(monster); - } - else - { - var player=monster.target&&get_player(monster.target); - if(player) - if(monster.attack<120 || distance(monster,player,true)>monster.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 broadcast_e(dont_send) { + if (!dont_send) { + broadcast("server_info", E); + } } -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 start_event(name) { + if (name == "goblin") { + events.goblin = true; + } else if (name == "goobrawl") { + signups = {}; + events.goobrawl = 1; + timers.goobrawl = future_s(45); + broadcast("notice", { message: "Goo Brawl is about to start!" }); + } else if (name == "abtesting") { + signups = {}; + events.abtesting = 1; + timers.abtesting = future_s(45); + // instances.main.players[NPC_prefix+"Bean"]=npcs.bean; + // npcs.bean.party="abtesting"; npcs.bean.last.move=new Date(); + broadcast("server_message", { message: "A/B Testing is about to start!", color: "#4BB6E1" }); + } } -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 monster.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 get_monsters(type) -{ - var l=[]; - for(var name in instances) - { - for(var id in instances[name].monsters) - { - var monster=instances[name].monsters[id]; - if(monster.type==type) l.push(monster); - } - } - return l; +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 get_monster(type) -{ - return get_monsters(type)[0] +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 < S.sold.length; i++) { + csold[i] = cache_item(S.sold[i], true); + } + for (var i = 0; i < S.found.length; i++) { + cfound[i] = cache_item(S.found[i], true); + } + if (!S.ugrace) { + S.ugrace = []; + S.cgrace = []; + for (var i = 0; i < 25; i++) { + S.ugrace[i] = 24; + S.cgrace[i] = 24; + } + } } -function add_item_property(item,prop) -{ - if(prop=="legacy") return; - if(!item.ps) item.ps=[]; - if(item.p && !in_arr(item.p,item.ps)) item.ps.push(item.p); // backwards compatibility - if(!in_arr(prop,item.ps)) item.ps.push(prop); - item.p=prop; +function get_monsters(type) { + var l = []; + for (var name in instances) { + for (var id in instances[name].monsters) { + var monster = instances[name].monsters[id]; + if (monster.type == type) { + l.push(monster); + } + } + } + return l; } -function add_condition(target,condition,args) -{ - var def=G.conditions[condition]; if(!def) return; - if(!args) args={}; - var response={response:"condition",name:condition,cevent:true},duration=args.duration||args.ms||def.duration; - if(duration==undefined) duration=1000; - var C={ms:duration}; - if(condition=="poisoned" && target.pnresistance) - { - if(Math.random()<(target.pnresistance||0)/100.0) - return xy_emit(target,"ui",{type:"poison_resist",id:target.id}); - - duration*=(100-target.pnresistance)/100.0; - } - if(condition=="stunned") - { - if(Math.random()<(target.phresistance||0)/100.0) - return xy_emit(target,"ui",{type:"stun_resist",id:target.id}); - - target.abs=true; - target.moving=false; - } - if(condition=="burned") - { - if(Math.random()<(target.firesistance||0)/100.0) - return xy_emit(target,"ui",{type:"fire_resist",id:target.id}); - - duration=5000; - if(!args.attack) args.attack=1000; - if(args.divider==3 && target.s.burned && target.s.burned.ms) - { - duration=min(12000,max(duration+400,min(8000,parseInt(duration/4+50*args.attack/(target.s.burned.intensity+200))))); - } - C.intensity=max(target.s.burned&&target.s.burned.intensity||1,parseInt((target.s.burned&&target.s.burned.intensity||0)/(args.divider||3)+args.attack)); - C.fid=args.fid; - disappearing_text({},target,"BURN!",{xy:1,size:"huge",color:"burn",nv:1}); //target.is_player&&"huge"||undefined - } - if(condition=="deepfreezed") - { - if(Math.random()<(target.fzresistance||0)/100.0) - return xy_emit(target,"ui",{type:"freeze_resist",id:target.id}); - } - if(condition=="woven") - { - C.s=min(target.is_monster&&20||5,target.s.woven&&(target.s.woven.s+1)||1); - C.speed=-3*condition.s; - } - duration=max(target.s[condition]&&target.s[condition].ms||0,duration); - if(target.stresistance && def && def.debuff) duration*=(100-target.stresistance)/100.0; - target.s[condition]=C; - C.ms=response.duration=duration; - server_log(C); - if(args.from) C.f=args.from.name; - if(args.f) C.f=args.f; - if(C.f) response.from=C.f; - target.cid++; - target.u=true; - if(target.socket) - target.hitchhikers.push(["game_response",response]); - return true; +function get_monster(type) { + return get_monsters(type)[0]; } -function consume_mp(player,mp,target) -{ - var mult=1; - if(target && target.humanoid) mult=5; - if(player.a.restore_mp && Math.random()=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 instance_emit(name, event, data) { + var instance = instances[name] || name; + if (gameplay == "hardcore" && event != "tavern" && event != "dice") { + return broadcast(event, data); + } + for (var id in instance.players) { + var player = instance.players[id]; + if (!player.npc) { + player.socket.emit(event, data); + } + } } -function delete_observer(socket) -{ - var observer=observers[socket.id]; - delete observers[socket.id]; - delete instances[observer.in].observers[observer.id]; +function party_emit(name, event, data, args) { + if (!parties[name]) { + return; + } + // console.log(parties[name]); + var owners = []; + var owner = get_player(data.owner); + parties[name].forEach(function (player_name) { + var player = players[name_to_id[player_name]]; + if (args && args.instance && player.in != args.instance) { + return; + } + if (0 && in_arr(event, ["disappearing_text"])) { + player.socket.emit(event, data); + } //volatile. + else { + player.socket.emit(event, data); + } + if (event == "partym") { + owners[player.owner] = owners[player.owner] || []; + owners[player.owner].push(player.name); + } + }); + if (event == "partym" && owner) { + appengine_call("log_chat", { + to: Object.entries(owners), + type: "party", + message: data.message, + fro: owner.name, + author: owner.owner, + }); + } } -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 leave_party(name, leaver) { + // [10/07/18]: For a long time chased oddities around party routines that didn't make sense, yesterday I realised that .emit causes a "disconnect" to be handled right inside this function, rather than afterwards, causing the oddities and irregularities - so - after a .emit, there's no guarantee that the player will still be there + var newparty = []; + var oldparty = parties[name]; + if (!oldparty) { + leaver.party = null; + console.log("#X NO PARTY " + name); + return; + } // Don't know the cause, maybe a disconnect triggering on the accept routines? [12/07/18] + parties[name].forEach(function (player_name) { + var player = players[name_to_id[player_name]]; + if (!player) { + console.log("#X SHOULD'VE FOUND " + player_name); + return; + } + player.party = null; + if (leaver.name != player.name) { + newparty.push(player_name); + } + }); + delete parties[name]; + if (newparty.length >= 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 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 send_all_xy(observer, args) { + var data = { players: [], monsters: [], type: "all", in: observer.in, map: observer.map }; + var 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_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 xy_emit(entity, event, data, must) { + var x = entity.x; + var y = entity.y; + var 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] < x && + x < player.x + player.vision[0] && + player.y - player.vision[1] < y && + y < player.y + player.vision[1]) || + player.id == must + ) { + if (event == "light" && (player.type == "rogue" || player.s.invis) && distance(player, entity) < 300) { + player.last.invis = new Date(); + delete player.s.invis; + player.socket.emit("light", { name: data.name, affected: 1 }); + resend(player, "u"); + } else if ((event == "upgrade" || event == "ui") && entity.name != player.name) { + player.socket.emit(event, data); + } //volatile. [02/02/18] + else { + player.socket.emit(event, data); + if (event == "chat_log" && data.p) { + owners[player.owner] = owners[player.owner] || []; + owners[player.owner].push(player.name); + } + } + // else if(!data.nv && in_arr(event,["disappearing_text","upgrade"])) player.socket.emit(event,data); //volatile. + } + } + for (var id in instances[entity.in].observers) { + var player = instances[entity.in].observers[id]; + if ( + player.x - player.vision[0] < x && + x < player.x + player.vision[0] && + player.y - player.vision[1] < y && + y < player.y + player.vision[1] + ) { + if (event == "upgrade") { + player.socket.emit(event, data); + } //volatile. [02/02/18] + else if (!data.nv && in_arr(event, ["disappearing_text", "upgrade"])) { + player.socket.emit(event, data); + } //volatile. + else { + player.socket.emit(event, data); + } + } + } + if (event == "chat_log" && data.p) { + appengine_call("log_chat", { + to: Object.entries(owners), + type: "ambient", + message: data.message, + fro: entity.name, + author: entity.owner, + }); + } } -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 xy_u_logic(element) { + // sets .u so updates are sent - this element might enter someone's vision + var u = false; + + if (!element.last_u) { + u = true; + } else if (abs(element.last_u[0] - element.x) > 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 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 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 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_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 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 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 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 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 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 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 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 disappearing_text(socket, entity, text, args) { + var x = entity.x; + var y = entity.y - 30; + var 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 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])); - } - } - }); +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; } -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}; +function exchange(player, name, args) { + if (!args) { + args = {}; + } + var socket = player.socket; + var done = false; + var total = 0; + var 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]); + var 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; + var total = 0; + var 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, +}; +var item_trade_p_ignore = { grace: true, o: true, oo: true, src: true }; // DOC // .name -> key from G.items @@ -2652,1226 +3557,1776 @@ var item_p_ignore={"grace":true,"giveaway":true,"gf":true,"price":true,"b":true, // .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 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 get_trade_slots(player) { + if (player.p.stand) { + var num = 16; + var 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 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;i= 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; i < player.items.length; i++) { + player.citems[i] = cache_item(player.items[i]); + } + reslot_player(player); } -function init_bank(player) -{ - player.cuser={}; - for(var pack in bank_packs) - { - if(!player.user[pack]) continue; - player.cuser[pack]=[]; - for(var i=0;i42) player.user[pack].length=42; - } +function init_bank_exit(player) { + for (var pack in bank_packs) { + if (!player.user[pack]) { + continue; + } + if (player.user[pack].length > 42) { + 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; +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; i < player.items.length; i++) { + if (!player.items[i]) { + continue; + } + delete player.items[i].m; + if (player.items[i].v && msince(new Date(player.items[i].v)) > 60) { + 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; + //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])}; - } + 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 + 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 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 push_xyh(player, x, y) { + if (!player.xyh) { + return; + } + var last = player.xyh[player.xyh.length - 1]; + var 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 item_name(item) { + var def = G.items[item.name]; + var 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 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 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 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_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 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 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];j 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]; j < current[2]; j += smap_step) { + push(current[0], j, 0); + [ + [0, smap_step], + [0, -smap_step], + [smap_step, 0], + [-smap_step, 0], + ].forEach(function (m) { + push(current[0] + m[0], j + m[1], 1); + }); + } + } + for (var i = 0; i < (G.maps[map].data.y_lines || []).length; i++) { + var current = G.maps[map].data.y_lines[i]; + push(current[1], current[0], 0); + push(current[2], current[0], 0); + for (var j = current[1]; j < current[2]; j += smap_step) { + push(j, current[0], 0); + [ + [0, smap_step], + [0, -smap_step], + [smap_step, 0], + [-smap_step, 0], + ].forEach(function (m) { + push(j + m[0], current[0] + m[1], 1); + }); + } + } + } + start = 0; + queue = []; + } + if (chiding_places.length) { + Array.prototype.push.apply(hiding_places, chiding_places); + } } -function amap_round(x) // for blink, port, magiport -{ - var x1=parseInt(x)-(parseInt(x)%amap_step); - var x2=x1+amap_step; - if(x<0) x2=x1-amap_step; - if(abs(x-x1)120000) - { - 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; - } +var amap_data = {}; +var amap_step = 8; +function server_bfs2(map) { + var base = { h: 9, v: 9, vn: 2 }; + var xmult = 1; + var vhmult = 1; + amap_data[map] = {}; //new Map(); - Map can't be sent to the Worker! :) [19/08/20] + var queue = []; + var visited = {}; + var start = 0; + var level = 0; + var chiding_places = []; + function push(x, y, c) { + x = parseInt(x) - (parseInt(x) % amap_step); + y = parseInt(y) - (parseInt(y) % amap_step); + var hash = phash2(x, y); + if (visited[hash]) { + return; + } + queue.push([x, y]); + visited[hash] = 1; + } + G.maps[map].spawns.forEach(function (s) { + var x = s[0]; + var y = s[1]; + var done = false; + x = parseInt(x) - (parseInt(x) % amap_step); + y = parseInt(y) - (parseInt(y) % amap_step); + var current = [x, y]; + [ + [0, 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 ( + !done && + can_move({ + map: map, + x: x, + y: y, + 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], level); + done = true; + } + }); + }); + while (start < queue.length) { + if (queue.length > 120000) { + 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-rnd 1200 || total > 4000) { + server_log(["heap", "fast_astar"]); + if (theone) { + var result = finalise(theone); + result.push("bad"); + return result; + } + return null; + } + var current = heap.removeTop(); + var dist = point_distance(current.x, current.y, tx, ty); + var 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 - rnd < best) { + best = dist + current.bad - rnd; + theone = current; + } + [ + [amap_step, amap_step, 0, amap_step * 1.41], + [amap_step, -amap_step, 1, amap_step * 1.41], + [-amap_step, -amap_step, 2, amap_step * 1.41], + [-amap_step, amap_step, 3, amap_step * 1.41], + [0, amap_step, 4, amap_step], + [0, -amap_step, 5, amap_step], + [amap_step, 0, 6, amap_step], + [-amap_step, 0, 7, amap_step], + ].forEach(function (m) { + var cx = current.x + m[0]; + var cy = current.y + m[1]; + if (amap_data[map][cx + "|" + cy] && !visited[cx + "|" + cy]) { + hpush(cx, cy, current.x + "|" + current.y, m[2], current.bad + m[3] + ((current.dir != m[2] && 0.5) || 0)); + } + }); + } + server_log(["no start", "fast_astar"]); } -function fast_abfs(monster,tx,ty) -{ - var map=monster.map,sx=monster.x,sy=monster.y,start=0,last=0; - var queue=[],visited={},start_t=new Date(),best=999999999999,theone=null,good=false; - function hpush(cx,cy,fr,dir,bad) - { - var value=point_distance(cx,cy,tx,ty),hash=cx+"|"+cy; - queue[last++]={x:cx,y:cy,bad:bad,dir:dir}; - visited[hash]=fr; - } - function finalise(current) - { - var dx=current.x,dy=current.y; - path=[[dx,dy]]; - while(visited[dx+"|"+dy] && visited[dx+"|"+dy]!="start") - { - dx=visited[dx+"|"+dy].split("|"); dy=parseInt(dx[1]); dx=parseInt(dx[0]); - path.push([dx,dy]); - } - path.reverse(); - // path.push([tx,ty]); // Looks bad - dx=path[0][0],dy=path[0][1]; - for(var i=1;i1200 || 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-rnd 1200 || 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++]; + var dist = point_distance(current.x, current.y, tx, ty); + var rnd = Math.random() * 100; + if (dist < 28) { + monster.bpath = 0; + good = true; + return finalise(current); + } + if (dist + current.bad - rnd < best) { + best = dist + current.bad - rnd; + theone = current; + } + [ + [amap_step, amap_step, 0, amap_step * 1.41], + [amap_step, -amap_step, 1, amap_step * 1.41], + [-amap_step, -amap_step, 2, amap_step * 1.41], + [-amap_step, amap_step, 3, amap_step * 1.41], + [0, amap_step, 4, amap_step], + [0, -amap_step, 5, amap_step], + [amap_step, 0, 6, amap_step], + [-amap_step, 0, 7, amap_step], + ].forEach(function (m) { + var cx = current.x + m[0]; + var cy = current.y + m[1]; + if (amap_data[map][cx + "|" + cy] && !visited[cx + "|" + cy]) { + hpush(cx, cy, current.x + "|" + current.y, m[2], current.bad + m[3] + ((current.dir != m[2] && 0.5) || 0)); + } + }); + } + server_log(["no start", "fast_abfs"]); } -function add_call_cost(socket,num,method) -{ - 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(!method) method=ls_method; +function add_call_cost(socket, num, method) { + 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 (!method) { + method = ls_method; + } + while (socket.calls.length && mssince(socket.calls[0][0]) > 4000) { + socket.calls.shift(); + } // #TODO: make one operation [27/02/23] - while(socket.calls.length && mssince(socket.calls[0][0])>4000) 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]); + 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; +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(); - } + 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 +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] + 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; + var cost = 0; + socket.calls.forEach(function (c) { + cost += c[2]; + }); + return cost; } - -function set_direction(){} // compatibility +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 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(); + 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."); - } - } + 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; -}; + return plaintext; +} function parseAppTicket(ticket) { - // https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl + // 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); - } + // console.log(ticket); + if (!ByteBuffer.isByteBuffer(ticket)) { + ticket = ByteBuffer.wrap(ticket, ByteBuffer.LITTLE_ENDIAN); + } - let details = {}; + 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 + 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); + 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; - } + 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 + 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); - } + 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; - } + // 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; + let i; + let j; + let 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; + 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()); - } + let licenseCount = ticket.readUint16(); + for (i = 0; i < licenseCount; i++) { + details.licenses.push(ticket.readUint32()); + } - details.dlc = []; + details.dlc = []; - let dlcCount = ticket.readUint16(); - for (i = 0; i < dlcCount; i++) { - dlc = {}; - dlc.appID = ticket.readUint32(); - dlc.licenses = []; + let dlcCount = ticket.readUint16(); + for (i = 0; i < dlcCount; i++) { + dlc = {}; + dlc.appID = ticket.readUint32(); + dlc.licenses = []; - licenseCount = ticket.readUint16(); + licenseCount = ticket.readUint16(); - for (j = 0; j < licenseCount; j++) { - dlc.licenses.push(ticket.readUint32()); - } + for (j = 0; j < licenseCount; j++) { + dlc.licenses.push(ticket.readUint32()); + } - details.dlc.push(dlc); - } + 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(); - } + 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 - } + 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; + 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"); -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] -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; - // 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"]; - // 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; + // 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"]; - // x-client-ip - if (clientIp) { - ipAddress = clientIp; - } + // 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-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-client-ip + if (clientIp) { + ipAddress = clientIp; + } - // x-real-ip - // (default nginx proxy/fcgi) - else if (realIp) { - // alternative to x-forwarded-for, used by some proxies - ipAddress = realIp; - } + // 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-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-real-ip + // (default nginx proxy/fcgi) + else if (realIp) { + // alternative to x-forwarded-for, used by some proxies + ipAddress = realIp; + } - // x-forwarded - else if (forwardedAlt) { - ipAddress = forwardedAlt; - } + // 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; + } - // forwarded-for - else if (forwardedFor) { - ipAddress = forwardedFor; - } + // x-forwarded + else if (forwardedAlt) { + ipAddress = forwardedAlt; + } - // forwarded - else if (forwarded) { - ipAddress = forwarded; - } + // forwarded-for + else if (forwardedFor) { + ipAddress = forwardedFor; + } - // remote address checks - else if (reqConnectionRemoteAddress) { - ipAddress = reqConnectionRemoteAddress; - } - else if (reqSocketRemoteAddress) { - ipAddress = reqSocketRemoteAddress - } - else if (reqConnectionSocketRemoteAddress) { - ipAddress = reqConnectionSocketRemoteAddress - } - else if (reqInfoRemoteAddress) { - ipAddress = reqInfoRemoteAddress - } + // forwarded + else if (forwarded) { + ipAddress = forwarded; + } - // return null if we cannot find an address - else { - ipAddress = null; - } - - return ipAddress; + // 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 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 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 +/* eslint-disable no-misleading-character-class,no-control-regex */ +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 } +/* eslint-enable no-misleading-character-class,no-control-regex */ // #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}); +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 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 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_level(player) -{try{ - if(gameplay=="hardcore") - { - var announce=false; - ["warrior","paladin","priest","mage","ranger","rogue","wabbit"].forEach(function(c){ - if(player.type==c && player.level>=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 - { +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); + } +} - } -}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_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 - { +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); + } +} - } -}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; + } -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 - { + if (monster.type == "goo" || monster.type == "cgoo") { + item_achievement_increment(player, player.slots.pants, "gooped", attack); + } + } catch (e) { + console.log("#A: " + e); + } +} - } -}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);}} \ No newline at end of file +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); + } +} diff --git a/node/server_worker.js b/node/server_worker.js index 0e98fff..302dc8b 100644 --- a/node/server_worker.js +++ b/node/server_worker.js @@ -1,23 +1,28 @@ -var is_game=0,is_server=1,is_code=0; -var variables=require('./variables'); -var is_sdk=variables.is_sdk; -var fs=require('fs'); -eval(""+fs.readFileSync(variables.cfunctions_path)); -eval(""+fs.readFileSync(variables.functions_path)); -var {workerData,parentPort}=require('worker_threads'); -var G=workerData.G,smap_data=workerData.smap_data,amap_data=workerData.amap_data; +var is_game = 0; +var is_server = 1; +var is_code = 0; +var variables = require("./variables"); +var is_sdk = variables.is_sdk; +var fs = require("fs"); +eval("" + fs.readFileSync(variables.cfunctions_path)); +eval("" + fs.readFileSync(variables.functions_path)); +var { workerData, parentPort } = require("worker_threads"); +var G = workerData.G; +var smap_data = workerData.smap_data; +var amap_data = workerData.amap_data; -parentPort.on("message",function(data){ - try{ - //console.log(data); - if(data.type=="fast_astar") - parentPort.postMessage({type:"monster_move",move:fast_astar(data),id:data.id,in:data.in}); - if(data.type=="exit") process.exit(); - }catch(e){ - console.log(e); - } +parentPort.on("message", function (data) { + try { + //console.log(data); + if (data.type == "fast_astar") { + parentPort.postMessage({ type: "monster_move", move: fast_astar(data), id: data.id, in: data.in }); + } + if (data.type == "exit") { + process.exit(); + } + } catch (e) { + console.log(e); + } }); -setInterval(function(){ - -},10); \ No newline at end of file +setInterval(function () {}, 10); diff --git a/node/test.js b/node/test.js index d2f5910..4bdad91 100644 --- a/node/test.js +++ b/node/test.js @@ -1,150 +1,176 @@ -var crypto=require('crypto'); -var protobuf=require('protobufjs'),ByteBuffer=require('bytebuffer'); +var crypto = require("crypto"); +var protobuf = require("protobufjs"); +var ByteBuffer = require("bytebuffer"); 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 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(); + 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."); - } - } + 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; -}; + return plaintext; +} function parseAppTicket(ticket) { - // https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl + // 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); - } + console.log(ticket); + if (!ByteBuffer.isByteBuffer(ticket)) { + ticket = ByteBuffer.wrap(ticket, ByteBuffer.LITTLE_ENDIAN); + } - let details = {}; + 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 + 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); + 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; - } + 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 + 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); - } + 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; - } + // 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; + let i; + let j; + let 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; + 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()); - } + let licenseCount = ticket.readUint16(); + for (i = 0; i < licenseCount; i++) { + details.licenses.push(ticket.readUint32()); + } - details.dlc = []; + details.dlc = []; - let dlcCount = ticket.readUint16(); - for (i = 0; i < dlcCount; i++) { - dlc = {}; - dlc.appID = ticket.readUint32(); - dlc.licenses = []; + let dlcCount = ticket.readUint16(); + for (i = 0; i < dlcCount; i++) { + dlc = {}; + dlc.appID = ticket.readUint32(); + dlc.licenses = []; - licenseCount = ticket.readUint16(); + licenseCount = ticket.readUint16(); - for (j = 0; j < licenseCount; j++) { - dlc.licenses.push(ticket.readUint32()); - } + for (j = 0; j < licenseCount; j++) { + dlc.licenses.push(ticket.readUint32()); + } - details.dlc.push(dlc); - } + 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(); - } + 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 - } + 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; + 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"); -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"); - - -var outer=EncryptedAppTicket.decode(new Buffer("080110a7e59890031815204f2a80014527ad8cccd8cd9b7d3260529556004ddf0454a1c2e8543495b1eb72b3dff44d9d67555798ef44b7e3f97cf86d3bf1bc3f84a91de415c382b0d96033c8e5398f2b775bcb004f5487c651d81ef410d7d2917cbb407713397d2c29a455b6852eac7db1a0e52248585bd5440ef07de54b7519f67eefbad5420d97b77ef7e35450af","hex")); -var decrypted=symmetricDecrypt(outer.encryptedTicket,new Buffer("8af5c487af6c7709ef1a4e6e2275fcc0a90f57ae4319de793fca3d7ed8655520","hex")); +var outer = EncryptedAppTicket.decode( + new Buffer( + "080110a7e59890031815204f2a80014527ad8cccd8cd9b7d3260529556004ddf0454a1c2e8543495b1eb72b3dff44d9d67555798ef44b7e3f97cf86d3bf1bc3f84a91de415c382b0d96033c8e5398f2b775bcb004f5487c651d81ef410d7d2917cbb407713397d2c29a455b6852eac7db1a0e52248585bd5440ef07de54b7519f67eefbad5420d97b77ef7e35450af", + "hex", + ), +); +var decrypted = symmetricDecrypt( + outer.encryptedTicket, + new Buffer("8af5c487af6c7709ef1a4e6e2275fcc0a90f57ae4319de793fca3d7ed8655520", "hex"), +); let userData = decrypted.slice(0, outer.cbEncrypteduserdata); let ownershipTicketLength = decrypted.readUInt32LE(outer.cbEncrypteduserdata); -let ownershipTicket = parseAppTicket(decrypted.slice(outer.cbEncrypteduserdata, outer.cbEncrypteduserdata + ownershipTicketLength)); +let ownershipTicket = parseAppTicket( + decrypted.slice(outer.cbEncrypteduserdata, outer.cbEncrypteduserdata + ownershipTicketLength), +); if (ownershipTicket) { - ownershipTicket.userData = userData.toString(); + ownershipTicket.userData = userData.toString(); } console.log(ownershipTicket); - diff --git a/useful/template.live_variables.js b/useful/template.live_variables.js index cc4e57d..c622edd 100644 --- a/useful/template.live_variables.js +++ b/useful/template.live_variables.js @@ -1,18 +1,18 @@ -module.exports={ - "cfunctions_path":"./server/common_functions.js", - "functions_path":"./server/server_functions.js", - "worker_path":"./server/server_worker.js", - "data_path":"./server/data.js", - "base_url":"https://yourappspoturldontincludedashes.appspot.com", - "keyword":"123", - "access_master":"123", - "bot_key":"123", - "discord_token":"NDXXXXXXXXXXX...", - "apple_token":"acXXXXXXXX...", - "steam_key":"8aXXXXXXXXX...", - "steam_web_key":"B4XXXXXXX...", - "steam_partner_key":"F9XXXXXXX...", - "close_timeout":24000, - "ip_limit":3, - "character_limit":3, -} +module.exports = { + cfunctions_path: "./server/common_functions.js", + functions_path: "./server/server_functions.js", + worker_path: "./server/server_worker.js", + data_path: "./server/data.js", + base_url: "https://yourappspoturldontincludedashes.appspot.com", + keyword: "123", + access_master: "123", + bot_key: "123", + discord_token: "NDXXXXXXXXXXX...", + apple_token: "acXXXXXXXX...", + steam_key: "8aXXXXXXXXX...", + steam_web_key: "B4XXXXXXX...", + steam_partner_key: "F9XXXXXXX...", + close_timeout: 24000, + ip_limit: 3, + character_limit: 3, +}; diff --git a/useful/template.variables.js b/useful/template.variables.js index bf90732..c44d461 100644 --- a/useful/template.variables.js +++ b/useful/template.variables.js @@ -1,22 +1,22 @@ -const path = require("node:path") +const path = require("node:path"); -module.exports={ - "cfunctions_path":path.resolve(__dirname, "../js/common_functions.js"), - "functions_path":path.resolve(__dirname, "server_functions.js"), - "worker_path":path.resolve(__dirname, "server_worker.js"), - "data_path":path.resolve(__dirname, "data.js"), - "base_url":"http://thegame.com", - "keyword":"123", - "access_master":"123", - "bot_key":"123", - "discord_token":"NDXXXXXXXXXXX...", - "apple_token":"acXXXXXXXX...", - "steam_key":"8aXXXXXXXXX...", - "steam_web_key":"B4XXXXXXX...", - "steam_partner_key":"F9XXXXXXX...", - "is_sdk":1, - "close_timeout":4000, - "ip_limit":3, - "character_limit":3, - "fast_sdk":0, -} +module.exports = { + cfunctions_path: path.resolve(__dirname, "../js/common_functions.js"), + functions_path: path.resolve(__dirname, "server_functions.js"), + worker_path: path.resolve(__dirname, "server_worker.js"), + data_path: path.resolve(__dirname, "data.js"), + base_url: "http://thegame.com", + keyword: "123", + access_master: "123", + bot_key: "123", + discord_token: "NDXXXXXXXXXXX...", + apple_token: "acXXXXXXXX...", + steam_key: "8aXXXXXXXXX...", + steam_web_key: "B4XXXXXXX...", + steam_partner_key: "F9XXXXXXX...", + is_sdk: 1, + close_timeout: 4000, + ip_limit: 3, + character_limit: 3, + fast_sdk: 0, +};