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 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"); 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 monster_respawns = []; // [target,ms] array so they can be hastened [20/12/25] 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 = []; 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: 12000, 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, drop_table_multiplier: 1, // 2 means drop tables extended by their original selves, not %'s multiplied }; 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 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 fear_affects_heal: 0, // when feared heal output is lowered implicit_targets: 0, // Do skills that don't have an explicit target, such as self-buffing skills, trigger mana restoring effects with increased chances? }; var events = { // SEASONS holidayseason: false, lunarnewyear: true, valentines: true, pinkgoo: 0, // every N minutes - 60 snowman: 20 * 60, // 1200 normally - 60 - at sprocess_game_data egghunt: 0, // every N minutes - 60 halloween: false, // RANDOM goblin: false, goldenbat: 160000, cutebee: 960000, goldenbot: 200000, 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 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; 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]; 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; } 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.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("gateway"); create_instance("ucliffs"); create_instance("uhills"); create_instance("mforest"); server_bfs("crypt"); server_bfs("winter_instance"); server_bfs("spider_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 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); 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); 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", "user", ]) ) { 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 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 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 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 = {}; 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 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", }; 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 = max(1, player.max_mp); 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; player.fear = Math.max( 0, player.targets_p - player.courage, player.targets_m - player.mcourage, player.targets_u - player.pcourage, ); const sredux = [0, 20, 40, 70, 80, 90, 100]; player.speed -= sredux[Math.min(sredux.length - 1, player.fear)]; if (player.fear > 2) { player.attack = Math.round(player.attack * 0.2); if (mode.fear_affects_heal) { player.heal = Math.round(player.heal * 0.2); } } else if (player.fear > 1) { player.attack = Math.round(player.attack * 0.4); if (mode.fear_affects_heal) { player.heal = Math.round(player.heal * 0.4); } } else if (player.fear) { player.attack = Math.round(player.attack * 0.6); if (mode.fear_affects_heal) { player.heal = Math.round(player.heal * 0.6); } } 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; } 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 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 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_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 create_new_item(name, quantity) { var new_item = { name: name }; if (G.items[name].s) { new_item.q = quantity || 1; } if (G.items[name].upgrade || G.items[name].compound) { new_item.level = 0; } return new_item; } function create_new_sitem(item, quantity) { let s_item = create_new_item(item.name, quantity); if (item.v) { s_item.v = item.v; } if (item.data) { s_item.data = item.data; } if (item.p) { s_item.p = item.p; } return s_item; } function aadd_item(player, new_item) { // admin [24/06/23] add_item(player, new_item, { announce: false }); reopen(player, "u+cid+reopen"); } function add_item(player, new_item, args) { // # IMPORTANT: ALWAYS KEEP CAN_ADD_ITEM IN SYNC! var done = false; var num = 0; var a = false; if (!args) { args = {}; } if (!new_item.name) { new_item = create_new_item(new_item, args.q || 1); } if (args.p) { new_item.p = args.p; } if ( (args.m || args.r) && ((G.items[new_item.name].upgrade && Math.random() < 1.0 / 500) || (G.items[new_item.name].compound && Math.random() < 1.0 / 20000)) ) { new_item.p = "shiny"; a = true; } if (G.items[new_item.name].s) { for (var i = 0; i < player.items.length; i++) { var item = player.items[i]; if (can_stack(item, new_item)) { if ((args.v && is_in_pvp(player, 1)) || new_item.v) { item.v = new Date(); } if (new_item.m) { item.m = new_item.m; } item.q = parseInt((new_item.q || 1) + (item.q || 1)); // to fix the side-effects of not including parseInt on 'sell'/'buy' [22/02/18] player.citems[i] = cache_item(player.items[i]); done = true; num = i; break; } } } if (!new_item.oo) { new_item.oo = player.name; } if (!done) { for (var i = 0; i < player.items.length; i++) { var item = player.items[i]; if (!item) { if (args.v && is_in_pvp(player, 1)) { new_item.v = new Date(); } player.items[i] = new_item; player.citems[i] = cache_item(player.items[i]); player.esize--; // esize-- needed for concurrent party chest operations [18/10/18] done = true; num = i; break; } } } if (!done) { player.items.push(new_item); player.citems[player.items.length - 1] = cache_item(player.items[player.items.length - 1]); num = player.items.length - 1; player.esize--; } if ( args.m && player.s.mluck && get_player(player.s.mluck.f) && Math.random() <= 0.02 && can_add_item(get_player(player.s.mluck.f), new_item.name) ) { var mr = get_player(player.s.mluck.f); var item = create_new_item(new_item.name); item.m = player.name; if (new_item.data) { item.data = new_item.data; } // "cxjar" var num = add_item(mr, item, { m: 1 }); mr.socket.emit("game_log", { message: "Found " + item_to_phrase(item), color: "#64B867" }); xy_emit(mr, "ui", { type: "+M", name: mr.name, item: cache_item(item), num: num, cevent: "mluck", event: "mluck" }); resend(mr, "reopen+nc+inv"); } if (args.announce !== false && (a || (a_score[new_item.name] || 0) < G.items[new_item.name].a) && !player.stealth) { var event = G.items[new_item.name].event; var e_type = "server_found"; var e_phrase = " found "; if (!args.found) { e_type = "server_received"; e_phrase = " received "; } if (args.phrase) { e_type = "server_received"; e_phrase = " " + args.phrase.toLowerCase() + " "; } broadcast("server_message", { message: player.name + e_phrase + item_to_phrase(new_item), color: "#85C76B", type: e_type, item: cache_item(new_item), name: player.name, event: event, }); a_score[new_item.name] = (a_score[new_item.name] || 0) + 1.05; } if (args.log) { player.socket.emit("game_response", { response: "add_item", item: new_item }); } return num; } function list_to_pseudo_items(list, type, add) { var items = []; var ex = {}; if (!type || type == "multi") { list.forEach(function (el) { if (G.items[el[1]].s) { if (ex[el[1]]) { ex[el[1]].q += max(1, el[0]); } else { ex[el[1]] = { name: el[1], q: max(1, el[0]) }; items.push(ex[el[1]]); } } else { items.push({ name: el[1], q: max(1, el[0]) }); } }); } else if (type == "chest") { list.forEach(function (name) { if (G.items[name].s) { if (ex[name]) { ex[name].q += 1; } else { ex[name] = create_new_item(name); items.push(ex[name]); } } else { items.push(create_new_item(name)); } }); (add || []).forEach(function (item) { items.push(item); }); } return items; } function bank_add_item(player, slot, new_item) { if (!new_item.name) { new_item = create_new_item(new_item); } if (new_item.q) { for (var i = 0; i < player.user[slot].length; i++) { var item = player.user[slot][i]; if (can_stack(item, new_item)) { item.q = (item.q || 1) + (new_item.q || 1); player.cuser[slot][i] = cache_item(player.user[slot][i]); return i; } } } for (var i = 0; i < player.user[slot].length; i++) { var item = player.user[slot][i]; if (!item) { player.user[slot][i] = new_item; player.cuser[slot][i] = cache_item(player.user[slot][i]); return i; } } player.user[slot].push(new_item); const num = player.user[slot].length - 1; player.cuser[slot][num] = cache_item(player.user[slot][num]); return num; } function drop_item_logic(drop, def, pvp) { var added = false; if (def[1] == "shells") { drop.cash += def[2]; } else if (def[1] == "cxjar" || def[1] == "emotionjar") { var item = { name: def[1], q: def[2], data: def[3] }; if (pvp) { item.v = new Date(); } for (var i = 0; i < drop.items.length; i++) { if (can_stack(drop.items[i], item)) { drop.items[i].q += def[2]; added = true; break; } } if (!added) { drop.items.push(item); } } else if (def[1] == "open") { chest_exchange(drop, def[2]); } else { var item = create_new_item(def[1], def[2]); if (pvp) { item.v = new Date(); } for (var i = 0; i < drop.items.length; i++) { if (can_stack(drop.items[i], item)) { drop.items[i].q += def[2] ?? 1; added = true; break; } } if (!added) { drop.items.push(item); } } } function drop_one_thing(player, items, args) { if (!args) { args = {}; } var drop_id = randomStr(30); var chest = args.chest || "chest1"; if (!is_array(items)) { items = [items]; } drop = chests[drop_id] = { items: [], cash: 0 }; for (var i = 0; i < items.length; i++) { if (is_string(items[i])) { drop_item_logic(drop, [1, items[i]], is_in_pvp(player, 1)); } } drop.gold = args.gold || 0; drop.x = (args.x !== undefined && args.x) || player.x; drop.y = (args.y !== undefined && args.y) || player.y; drop.map = args.map || player.map; drop.date = new Date(); 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); const is_pvp = is_in_pvp(player, 1); var drop_id = randomStr(30); var drop; var chest = "chest3"; var hp_mult = 1; var drop_norm = 1000; var global_mult = monster.mult; var 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_mult < item[0] || mode.drop_all) { drop_item_logic(drop, item, is_pvp); } }); } if (D.drops.maps.global && player.tskin != "konami" && B.global_drops) { D.drops.maps.global.forEach(function (item) { if (Math.random() / share / player.luckm / hp_mult / monster.luckx / global_mult < item[0] || mode.drop_all) { drop_item_logic(drop, item, is_pvp); } }); } if (D.drops.maps[monster.map] && player.tskin != "konami") { D.drops.maps[monster.map].forEach(function (item) { if (Math.random() / share / player.luckm / hp_mult / monster.luckx < item[0] || mode.drop_all) { drop_item_logic(drop, item, is_pvp); } }); } /** * Determines whether an item should drop for a player based * on the drop rate and luck modifiers. * @param {Array} item - An array representing the item to be * dropped, where item[0] is the drop rate. * @returns {boolean} - true if the item should drop, * false if the item should NOT drop. * Change Note: * - [7/17/24] - ATLUS - 'temp' monsters should also roll for drops */ const shouldItemDrop = (item) => { // 1) calculate the drop rate // 2) calculate & use roll modifier on a random roll (0 <= N < 1) // 3) item drops if the calculated falls below the drop rate threshold let dropRate = item[0]; let rollModifier = share * player.luckm * monster.level * monster_mult; let playerRoll = Math.random() / rollModifier; return playerRoll < dropRate; }; // if(player.level<50 && monster.type=="goo" && mode.low49_200xgoo) monster_mult=200; if (D.drops.monsters[monster.type] && player.tskin != "konami") { D.drops.monsters[monster.type].forEach(function (item) { let itemShouldDrop = shouldItemDrop(item); if (itemShouldDrop || mode.drop_all) { // /hp_mult - removed [13/07/18] for (var d = 0; d < B.drop_table_multiplier; d++) { drop_item_logic(drop, item, is_pvp); } } }); } if (monster.drops) { monster.drops.forEach(function (item) { let itemShouldDrop = shouldItemDrop(item); if (itemShouldDrop || mode.drop_all) { // /hp_mult - removed [13/07/18] drop_item_logic(drop, item, is_pvp); } }); } // Home-server monster-specific drops if ( player.p.home && player.p.home === region + server_name && D.drops.monsters_home_server[monster.type] && player.tskin != "konami" ) { D.drops.monsters_home_server[monster.type].forEach(function (item) { let itemShouldDrop = shouldItemDrop(item); if (itemShouldDrop || mode.drop_all) { drop_item_logic(drop, item, is_pvp); } }); } 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_pvp); } }); } 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_hardcore(player, target) { var drop_id = randomStr(30); var drop; drop = chests[drop_id] = { items: [], pvp_items: [], cash: 0 }; drop.gold = 99; drop.x = target.x; drop.y = target.y + 10; drop.map = target.map; target.slots.elixir = target.cslots.elixir = null; for (var name in target.slots) { if (target.slots[name] && !target.slots[name].b) { var prob = 0.1; if (name == "mainhand" || name == "offhand") { prob = 0.05; } if (Math.random() < prob) { drop.pvp_items.push(target.slots[name]); target.slots[name] = target.cslots[name] = null; } } } for (var i = 0; i < 42; i++) { if (target.items[i]) { if (Math.random() < 0.2) { drop.pvp_items.push(target.items[i]); target.items[i] = target.citems[i] = null; } else if (Math.random() < 0.05) { target.items[i] = target.citems[i] = null; } } } drop.date = new Date(); if (!drop.pvp_items.length) { delete chests[drop_id]; return; } 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.pvp_items.length, chest: "chest8", id: drop_id, party: player.party, map: target.map, owners: owners, }, { instance: player.in }, ); } else { player.socket.emit("drop", { x: drop.x, y: drop.y, items: drop.pvp_items.length, chest: "chest8", id: drop_id, map: target.map, owners: [player.owner], }); } } function drop_something_pvp(player, target) { var drop_id = randomStr(30); var drop; drop = chests[drop_id] = { items: [], pvp_items: [], cash: 0, pvp: true }; drop.gold = 0; drop.x = target.x; drop.y = target.y + 10; drop.map = target.map; for (var name in target.slots) { if (target.slots[name] && target.slots[name].v && !target.slots[name].b) { var prob = 0.5; if (name == "mainhand" || name == "offhand") { prob = 0.3; } if (Math.random() < prob) { drop.pvp_items.push(target.slots[name]); target.slots[name] = target.cslots[name] = null; } } } for (var i = 0; i < 42; i++) { if (target.items[i] && target.items[i].v) { var prob = (target.items[i].q && 0.5) || 0.8; if (Math.random() < prob) { drop.pvp_items.push(target.items[i]); target.items[i] = target.citems[i] = null; } // else if(Math.random()<0.05) target.items[i]=target.citems[i]=null; } } drop.date = new Date(); if (!drop.pvp_items.length) { delete chests[drop_id]; return; } 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.pvp_items.length, chest: "chest8", id: drop_id, party: player.party, map: target.map, owners: owners, }, { instance: player.in }, ); } else { player.socket.emit("drop", { x: drop.x, y: drop.y, items: drop.pvp_items.length, chest: "chest8", id: drop_id, map: target.map, owners: [player.owner], }); } } 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 issue_monster_awards(monster) { var total = 0.1; var total_characters = 0; for (var name in monster.points) { var current = players[name_to_id[name]]; if (current) { // && current.map==monster.map total += Math.pow(max(0, monster.points[name]), 0.65); // An attempt to soften the gap between strong and weak players [12/02/26] } } for (var name in monster.points) { var share = Math.pow(max(0, monster.points[name]), 0.65) / total; if (share > 0.0008) { total_characters += 1; } } B.drop_table_multiplier = parseInt(1 + Math.floor(total_characters / 10)); for (var name in monster.points) { var current = players[name_to_id[name]]; var share = Math.pow(max(0, monster.points[name]), 0.65) / total; if (current && share > 0.0025) { current.socket.emit("kill_credit", { mtype: monster.type, }); // && 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"); } } B.drop_table_multiplier = 1; } function issue_monster_award(monster) { if (monster.cooperative) { return issue_monster_awards(monster); } var player = players[name_to_id[monster.target]]; if (!player) { return; } // if(gameplay=="test" && player.level<80) player.level+=1; stats.kills[monster.type]++; drop_something(player, monster); if (!player.party) { player.socket.emit("kill_credit", { mtype: monster.type, }); if (monster.rbuff && G.conditions[monster.rbuff]) { player.s[monster.rbuff] = { ms: G.conditions[monster.rbuff].duration }; } if (monster.cbuff) { for (var i = 0; i < monster.cbuff.length; i++) { if (player.level <= monster.cbuff[i][0] && G.conditions[monster.cbuff[i][1]]) { player.s[monster.cbuff[i][1]] = { ms: G.conditions[monster.cbuff[i][1]].duration }; break; } } } var score = calculate_monster_score(player, monster); player.p.stats.monsters[monster.type] = (player.p.stats.monsters[monster.type] || 0) + 1; player.p.stats.monsters_diff[monster.type] = (player.p.stats.monsters_diff[monster.type] || 0) + (score - 1); monster_hunt_logic(player, monster); if (player.type == "merchant") { return; } player.xp += monster.xp * player.xpm * monster.mult; if (player.t) { player.t.xp += monster.xp * player.xpm * monster.mult; } player.cid++; player.u = true; calculate_player_stats(player); } else { var xp = monster.xp; // xp*=[1,1,0.70,0.5,0.4,0.32,0.26,0.24,0.24,0.24,0.24,0.24][parties[player.party].length]; // original: [1,1,0.8,0.7,0.65,0.5,0.4,0.3,0.3,0.3,0.3,0.3] // xp=round(xp); parties[player.party].forEach(function (name) { var current = players[name_to_id[name]]; if (!current) { return; } current.socket.emit("kill_credit", { mtype: monster.type, }); var cxp = round(xp * current.xpm * current.share); if (monster.rbuff && G.conditions[monster.rbuff]) { current.s[monster.rbuff] = { ms: G.conditions[monster.rbuff].duration }; } var score = calculate_monster_score(current, monster); 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); if (current.type == "merchant") { return; } current.xp += cxp * monster.mult; if (current.t) { current.t.xp += cxp * monster.mult; } current.cid++; current.u = true; calculate_player_stats(current); if (current != player) { current.socket.emit("player", player_to_client(current)); } // current.socket.emit("game_log",{message:xp+" XP",color:"#416F3A"}); disappearing_text(current.socket, current, "+" + cxp, { map: current.map, color: "#753D8C", size: "large", nv: 1, }); //party:player.party, - better send it to individuals and multiply on client with a setting [19/08/16] }); } } function kill_monster(attacker, target) { if (target.dead) { return; } // [04/02/23] var no_decrease = false; if (attacker) { if (!attacker.party) { attacker.socket.emit("game_log", "You " + killed_message(target.type)); } // tut("killagoo") on game.js else { party_emit(attacker.party, "game_log", attacker.name + " " + killed_message(target.type)); } if (B.free_last_hits && target.target && target.target != attacker.name) { stop_pursuit(target, { force: true, cause: "kill_monster call" }); } if (!target.target) { target_player(target, attacker, 1); no_decrease = true; } } issue_monster_award(target); remove_monster(target, { no_decrease: no_decrease }); } function player_rip_logic(player) { if (!player.rip && player.hp <= 0) { defeat_player(player); rip(player); } } function pwn_routine(victor, target) { issue_player_award(victor, target); rip(target); instance_emit(target.in, "server_message", { message: victor.name + " pwned " + target.name, color: (victor.team && colors[victor.team]) || "gray", }); if (target.map == "arena") { xy_emit(npcs.pvp, "chat_log", { owner: npcs.pvp.name, message: victor.name + " pwned " + target.name, id: "pvp" }); } } 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; var 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; 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 commence_attack(attacker, target, atype) { var attack = attacker.attack; var mp_cost = 0; var info = { apiercing: 0, rpiercing: 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); 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 }; } // 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 (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") { 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 (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") { 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") { 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] = (info[p] || 0) + 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.poison && Math.random() < (attacker.a.poison.attr0 * (G.maps[attacker.map].poison_multiplier || 1)) / 100.0 ) { info.conditions.push("poisoned"); } 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); 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: 0, 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)) { action.eta = Math.floor((1000 * dist) / G.projectiles[def.projectile].speed); } else { action.instant = true; } info.eta = future_ms(action.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 (!action.eta) { projectiles_loop(); } action.response = "data"; action.place = atype; return action; } function complete_attack(attacker, target, info) { var defense = "armor"; var pierce = "apiercing"; var combo = 1; var combo_m = 1; var atype = info.atype; var evade = false; var first = true; var attack = info.attack; var o_attack; var i_attack = info.attack; var 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"]]; var 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; info.rpiercing = 0; } else if (info.damage_type == "magical") { defense = "resistance"; pierce = "rpiercing"; info.apiercing = 0; } else if (info.damage_type == "physical") { defense = "armor"; pierce = "apiercing"; info.rpiercing = 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 < 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, source: def.source }, attacker.id, ); } else if ( target.dc || target.dead || (info.miss && Math.random() * 100 < info.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, source: def.source }, 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, source: def.source, 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.id != otarget.id && distance(target, otarget) < radius) { 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, source: def.source }, attacker.id, ); continue; } 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[pierce]), ) || 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) { add_condition(target, "frozen"); } 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 }); } 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() < attacker.a.sugarrush.attr0 / 100) { 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 target_player(monster, player, no_increase) { // if(is_sdk) console.log("target_player: "+player.name+" "+(!no_increase)); if (!no_increase && (monster.s.charmed || monster.peaceful)) { return; } if (monster.dead || monster.pet || monster.trap) { return; } monster.target = player.name; if (!no_increase) { increase_targets(player, monster); } delete monster.s.sleeping; monster.last.attacked = new Date(); monster.last_level = future_s(Math.random() * 100 - 50); monster.ex = monster.x; monster.ey = monster.y; monster.moving = false; monster.abs = true; monster.u = true; monster.cid++; calculate_monster_stats(monster); } function defeat_player(player) { player.violations = (player.violations || 0) + 1; if (player.s.block && player.s.block.f && !player.rip) { var attacker = players[name_to_id[player.s.block.f]]; if (attacker && attacker.name != player.name) { issue_player_award(attacker, player); instance_emit(attacker.in, "server_message", { message: attacker.name + " defeated " + player.name, color: "gray", }); if (player.map == "arena") { xy_emit(npcs.pvp, "chat_log", { owner: npcs.pvp.name, message: attacker.name + " defeated " + player.name, id: "pvp", }); } rip(player); } } } function duel_defeat(player) { var info = instances[player.duel.id].info; info.A.forEach(function (p) { if (p.name == player.name) { p.active = false; } }); info.B.forEach(function (p) { if (p.name == player.name) { p.active = false; } }); delete player.duel; delete player.team; } function resend(player, events) { if (player.halt || player.is_npc) { return; } if (!player.gold && player.gold !== 0) { player.gold = 0; server_log("#X - GOLD BUG resend", 1); } events = (events && events.split && events.split("+")) || []; delete player.to_resend; if (in_arr("u", events)) { add_call_cost(call_modifier); player.u = true; } if (in_arr("cid", events)) { player.cid++; } // if(in_arr("inv",events)) no longer needed as both add_item and consume has .esize updates [18/10/18] // { // player.esize=0; // for(var i=0;i 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 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 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 == "equip_batch") { var cost = 0.5; if (Array.isArray(data)) { cost += data.length / 2; } add_call_cost(CC.equip * cost); } else if (method == "cm") { var add = 1; var len = JSON.stringify(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; } if (!Array.isArray(data.to)) { data.to = [data.to]; } // 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) || {}, version: G.version, }; 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, monsters_home_server: D.drops.monsters_home_server || {}, //add this }; // ,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; data.drops_home = D.drops.monsters_home_server || {}; //mirror monsters_home } else { data.drops = {}; data.drops_home = {}; for (var name in data.monsters) { if (data.monsters[name] >= 100 && D.drops.monsters[name]) { data.drops[name] = D.drops.monsters[name]; } if (data.monsters[name] >= 100 && D.drops.monsters_home_server[name]) { data.drops_home[name] = D.drops.monsters_home_server[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.drops_home) { //include home tables register_table(data.drops_home[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) && distance( { map: player.map, x: G.maps[player.map].spawns[door[6]][0], y: G.maps[player.map].spawns[door[6]][1], width: door[2], height: door[3], }, 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" || data.place == "spider_instance" || data.place == "tomb" ) { 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 (data.place == "spider_instance") { f = "gateway"; ref = G.maps.gateway.spawns[3]; item = "spiderkey"; } if (data.place == "tomb") { f = "mansion"; ref = G.maps.mansion.spawns[1]; item = "tombkey"; } if (simple_distance(player, { in: f, map: f, x: ref[0], y: ref[1] }) > 120) { return fail_response("transport_cant_reach"); } if (data.name) { // Player requested to enter an existing instance if (instances[data.name] && instances[data.name].map == data.place) { // The instance exists transport_player_to(player, data.name); } else { // The instance doesn't exist return fail_response("transport_cant_invalid"); } } 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) { const player = players[socket.id]; if (!player || !player.rip) { return fail_response("invalid"); } if (player.rip_time) { const msRemaining = B.rip_time - mssince(player.rip_time); if (msRemaining > 0) { return fail_response("cant_respawn", { ms: msRemaining }); } } 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) { const player = players[socket.id]; if (!player) { return fail_response("invalid"); } if (player.q.exchange) { return fail_response("exchange_existing"); } if (player.user) { return fail_response("cant_in_bank"); } const item = player.items[data.item_num]; if (!item) { return fail_response("invalid"); } if (item.l) { return fail_response("item_locked"); } if (player.esize <= 0 && !((item.q || 1) == 1)) { // TODO: If there are no items in the drop table, we could add support to exchange it return fail_response("inventory_full"); } const def = G.items[item.name]; const suffix = def && (def.compound || def.upgrade) ? item.level || 0 : ""; const dropId = item.name + suffix; if (!def || !def.e || !D.drops[dropId]) { return fail_response("invalid"); } if (!player.computer) { const dist = distance(player, def.quest ? G.quests[def.quest] : G.maps.main.exchange); if (dist > B.sell_dist) { return fail_response("distance"); } } if (def.e > 1 && item.q < def.e) { return fail_response("exchange_notenough"); } player.p.stats.exchanges[dropId] = (player.p.stats.exchanges[dropId] || 0) + 1; consume(player, data.item_num, def.e); const num = add_item(player, "placeholder"); let ms = gameplay === "hardcore" ? 400 : 3000 + Math.floor(Math.random() * 3000); if (player.s.massexchange) { ms /= 2; delete player.s.massexchange; ex = "+u+cid"; } if (player.s.massexchangepp) { ms /= 10; delete player.s.massexchangepp; ex = "+u+cid"; } player.q.exchange = { ms: ms, len: ms, name: item.name, id: dropId, 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("destat", function (data) { var player = players[socket.id]; if (!player || player.user) { return fail_response("cant_in_bank"); } if (!player.computer && simple_distance(G.maps.desertland.ref.scrollsmith, 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 (item.stat_type == null) { return fail_response("scrollsmith_cant"); } var scrolltype = item.stat_type + "scroll"; if (scrolltype == "mp_costscroll") { scrolltype = "mpcostscroll"; } var scrolldef = G.items[scrolltype]; if (!scrolldef) { return fail_response("scrollsmith_cant"); // Just in case there isn't actually an item associated with the scroll... should never happen, though. } var needed = [1, 10, 100, 1000, 9999, 9999, 9999]; var ograde = calculate_item_grade(def, { name: item.name, level: 0 }); var cost = needed[ograde] * scrolldef.g * 10; if (player.gold < cost) { return fail_response("gold_not_enough"); } if (!can_add_items(player, list_to_pseudo_items([[needed[ograde], scrolltype]]))) { return fail_response("inv_size"); } player.gold -= cost; add_item(player, scrolltype, { q: needed[ograde] }); delete item.stat_type; socket.emit("game_response", { response: "scrollsmith_success", gold: cost }); player.citems[data.num] = cache_item(player.items[data.num]); resend(player, "reopen+nc"); success_response(); }); 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]; if (!Array.isArray(data.items) || data.items.length != 3) { return fail_response("no_item"); } if (!Number.isInteger(data.items[0]) || !Number.isInteger(data.items[1]) || !Number.isInteger(data.items[2])) { return fail_response("no_item"); } 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 (!scroll) { return socket.emit("game_response", "compound_no_scroll"); } if (!item0 || !item1 || !item2) { 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 || 0, 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 ((item0.level || 0) != data.clevel) { return fail_response("no_item"); } 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 (item0 == item1 || item1 == item2 || item0 == item2) { 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 || 0) >= 3) { igrade = calculate_item_grade(def, { name: item0.name, level: (item0.level || 0) - 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 || 0) - 2, 1); increase = 0.2; } else { probability = probability * 1.08 + min(15 * 0.015, grace) / max((item0.level || 0) - 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 || 0) - 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 || 0, 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 || 0) == 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_batch", function (data) { let player = players[socket.id]; if (!player) { return; } player.c = {}; if (Array.isArray(data)) { data.length = min(data.length, 15); let resolve = []; let penalty = 0; for (let i = 0; i < data.length; i++) { let equip_def = data[i]; equip_def.num = to_number(equip_def.num); if (equip_def.num >= player.items.length) { resolve.push("invalid"); break; } var item = player.items[equip_def.num]; if (!item) { resolve.push("no_item"); break; } if (item.b) { resolve.push("item_blocked"); break; } if (item.name == "placeholder") { resolve.push("item_placeholder"); break; } var def = G.items[item.name]; def.iname = item.name; //just for orb name checks [08/10/16] if (!def) { resolve.push("no_item"); break; } var slot = equip_def.slot || def.type; var comp = can_equip_item(player, def, slot); if (comp == "no") { resolve.push("cant_equip"); break; } slot = comp; var existing = player.slots[slot]; player.slots[slot] = player.items[equip_def.num]; player.cslots[slot] = cache_item(player.slots[slot]); if (existing && existing.b) { existing = null; } player.items[equip_def.num] = existing; player.citems[equip_def.num] = cache_item(existing); penalty += 120; resolve.push({ num: equip_def.num, slot }); } if ("penalty_cd" in player.s) { player.s.penalty_cd.ms = min(player.s.penalty_cd.ms + penalty, 120000); } else { player.s.penalty_cd = { ms: penalty }; } resend(player, "reopen+u+cid"); success_response("data", { slots: resolve }); } else { return fail_response("invalid"); } }); 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) { const ms = -mssince(player.last.potion); if (ms > 0) { return fail_response("not_ready", { ms: ms }); } } 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) { const player = players[socket.id]; const item = player.items[data.num]; if (!item) { return fail_response("no_item"); } if (!G.items[item.name].s) { return fail_response("invalid"); } if (!player.esize) { return fail_response("cant_space"); } 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"); } let quantity = min(max(parseInt(data.quantity) || 0, 1), (item && item.q) || 1); quantity = min(quantity, G.items[item.name].s); if (quantity >= item.q) { // They requested to split the whole stack... return success_response({ from: data.num, to: data.num, q: quantity }); } consume(player, data.num, quantity); const new_item = cache_item(item); if (item.q) { new_item.q = quantity; } let num; for (let i = 0; i < player.isize; i++) { if (player.items[i]) { continue; } player.items[i] = new_item; player.citems[i] = cache_item(new_item); num = i; break; } resend(player, "reopen"); success_response({ from: data.num, to: num, q: quantity }); }); 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("bless_server", function (data) { var player = players[socket.id]; if (!player || player.user || gameplay == "hardcore" || gameplay == "test") { return fail_response("cant_in_bank"); } var cost = 1200; if (player.blessing) { return fail_response("in_progress"); } player.blessing = true; appengine_call( "bill_user", { auth: player.auth, amount: cost, reason: "blessing", name: player.name }, function (result) { player.blessing = false; server_log("buy_with_cash: " + JSON.stringify(result)); if (result.failed || !result.done) { socket.emit("game_log", "Purchase failed"); socket.emit("game_response", "blessed_fail"); return; } player.cash = result.cash; S.blessed_minutes = 60 * 24 * 3; S.blessed_by = player.name; socket.emit("game_log", "Spent " + to_pretty_num(cost) + " shells"); socket.emit("game_response", "blessed"); resend(player, "reopen+nc+inv"); bless_loop(); discord_call(player.name + " blessed " + region + " " + server_name); realm_broadcast("server_message", { message: player.name + " blessed " + region + " " + server_name, color: "#8F70D8", }); }, ); 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_sitem(item, data.q); 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", { num: data.num }); } 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", { num: data.num }); } if (item.l) { return fail_response("item_locked", { num: data.num }); } if (item.b) { return fail_response("item_blocked", { num: data.num }); } if (item.level != 13) { consume(player, data.num, data.q); //player.items[data.num]=player.citems[data.num]=null; } if (data.statue && distance(player, G.maps.spookytown.ref.poof) < B.sell_dist) { var grade = (calculate_item_grade(G.items[name]) || 0) + 1; 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) || 3000013 * grade * grade) ) { add = "+u+cid"; item.level = 13; player.items[data.num] = item; player.citems[data.num] = cache_item(player.items[data.num]); const announce = !item.silent; if (announce && !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(G.maps.spookytown.ref.poof, "upgrade", { type: "poof", success: 1 }); } resend(player, "reopen" + add); success_response("destroyed", { name: name, num: data.num, 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_sitem(item, 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]); success = { operation: "swap", pack: data.pack, inv: data.inv, str: data.str }; } 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; const num = bank_add_item(player, data.pack, inv_item); success = { operation: "swap", pack: data.pack, inv: data.inv, str: num }; } 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; const num = add_item(player, bank_item, { announce: false }); success = { operation: "swap", pack: data.pack, inv: num, str: data.str }; } } 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 && is_same(player, get_player(monster.target), 1) && 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 || !booster_items.includes(item.name)) { 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 (!booster_items.includes(data.to)) { 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 || !["stoneofxp", "stoneofgold", "stoneofluck"].includes(item.name)) { 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) { const player = players[socket.id]; if (!player) { return; } server_log("skill " + JSON.stringify(data)); 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; if (is_disabled(player)) { return fail_response("disabled", data.name); } if ((player.tskin == "konami" || is_silenced(player)) && data.name != "attack") { return fail_response("skill_cant_incapacitated", data.name); } const gSkill = G.skills[data.name]; if (!gSkill) { return fail_response("no_skill", data.name); } if ( gSkill.mp && player.mp < (gSkill.mp * (100 - (player.mp_reduction || 0))) / 100.0 && !(player.role == "gm" && data.name == "blink") ) { return fail_response("no_mp", data.name); } if (gSkill.level && player.level < gSkill.level) { return fail_response("no_level", data.name); } let cooldown; let lastUse; if (gSkill.share) { // This skill shares a cooldown with another skill, use that skill's cooldown cooldown = G.skills[gSkill.share].cooldown * (gSkill.cooldown_multiplier || 1); lastUse = player.last[gSkill.share]; } else { cooldown = gSkill.cooldown || gSkill.reuse_cooldown; lastUse = player.last[data.name]; } if (cooldown && lastUse && mssince(lastUse) < cooldown) { return fail_response("cooldown", data.name, { skill: data.name, id: data.id, ms: cooldown - mssince(lastUse), }); } if (gSkill.class && !in_arr(player.type, gSkill.class) && player.role != "gm") { return fail_response("skill_cant_use", data.name); } if (gSkill.wtype && player.role != "gm") { let canUse; if (!player.slots.mainhand) { canUse = false; } else if (is_array(gSkill.wtype)) { canUse = in_arr(G.items[player.slots.mainhand.name].wtype, gSkill.wtype); } else { canUse = G.items[player.slots.mainhand.name].wtype == gSkill.wtype; } if (!canUse) { return fail_response("skill_cant_wtype", data.name); } } const gMap = G.maps[player.map]; if (gSkill.hostile && gMap.safe) { return fail_response("skill_cant_safe", data.name); } var target; if (gSkill.target) { const isMonster = "" + parseInt(data.id) === "" + data.id; const isPlayer = !isMonster; const canTargetMonster = gSkill.target == "monster" || gSkill.target === true; const canTargetPlayer = gSkill.target == "player" || gSkill.target === true; if ((isMonster && !canTargetMonster) || (isPlayer && !canTargetPlayer)) { return fail_response("invalid_target", data.name, { id: data.id }); } if (isPlayer && players[id_to_id[data.id]]) { target = players[id_to_id[data.id]]; if (gSkill.hostile && target.name == player.name) { // Prevent casting the skill on oneself return fail_response("no_target", data.name); } } else if (isMonster && instances[player.in].monsters[data.id]) { target = instances[player.in].monsters[data.id]; } else { // TODO: Consider adding game_response, too return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" }); } if (is_invis(target) || (!gSkill.global && distance(target, player) > B.max_vision)) { // TODO: Consider adding game_response, too return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" }); } } if (gSkill.requirements) { for (const requirement in gSkill.requirements) { if (!player[requirement] || player[requirement] < gSkill.requirements[requirement]) { return fail_response("skill_cant_requirements", data.name); } } } if (gSkill.slot) { let found = false; let noCharge = false; for (const [slotName, itemName] of gSkill.slot) { const slotItem = player.slots[slotName]; if (!slotItem || slotItem.name !== itemName) { continue; } found = true; const gItem = G.items[itemName]; if (!gItem.charge) { continue; } // This skill requires a charge, check the charge level if ((slotItem.charges || 0) < gItem.charge) { noCharge = true; continue; } // Charge is sufficient, use this item for the skill slotItem.charges -= gItem.charge; player.cslots[slotName] = cache_item(slotItem); noCharge = false; break; } if (!found) { return fail_response("skill_cant_slot", data.name); } if (noCharge) { return fail_response("skill_cant_charges", data.name); } } if (player.s.invincible) { delete player.s.invincible; player.to_resend = "u+cid"; } // Range check const isTargetTooFar = (target) => { // Use the range of the skill, falling back to the player range if it isn't set let range = gSkill.range || player.range; if (gSkill.global) { return false; } if (gSkill.range_multiplier) { range *= gSkill.range_multiplier; } if (gSkill.range_bonus) { range += gSkill.range_bonus; } if (data.name == "throw") { range += player.level; } const dist = distance(player, target); if (dist > range + player.xrange) { return true; } if (dist > range) { // xrange was used, reduce it by however much we used player.xrange -= dist - range; } return false; }; if (target && isTargetTooFar(target)) { return fail_response("too_far", data.name, { dist: dist, id: target.id }); } // Consume item check if (gSkill.consume) { let item; if (data.num === undefined || data.num === null) { // No item was provided, search for one for (let i = player.isize - 1; i >= 0; i--) { const potentialItem = player.items[i]; if (!potentialItem) { continue; } if (potentialItem.name !== gSkill.consume) { continue; } // We found the item data.num = i; item = potentialItem; break; } } else { item = player.items[data.num]; } if (!item || item.name != gSkill.consume) { return fail_response("skill_cant_item", data.name); } // Consume the item consume_one(player, data.num); player.to_resend = "u+cid+reopen"; } if ( [ "attack", "burst", "heal", "mentalburst", "piercingshot", "poisonarrow", "purify", "quickpunch", "quickstab", "smash", "snowball", "supershot", "taunt", "zapperzap", ].includes(data.name) ) { const attack = commence_attack(player, target, data.name); if (!attack.failed) { resolve = attack; } else { reject = attack; cool = false; } } else if (data.name == "selfheal") { const attack = commence_attack(player, player, data.name); if (!attack.failed) { resolve = attack; } else { reject = attack; cool = false; } } else if (data.name == "invis" && !player.s.invis) { if (player.s.marked) { return fail_response("skill_cant_use", data.name); } player.s.invis = { ms: 999999999999999 }; xy_emit(player, "disappear", { id: player.id, invis: true, reason: "invis" }); player.to_resend = " "; } else if (data.name == "pickpocket") { consume_mp(player, gSkill.mp, target); player.c[data.name] = { ms: gSkill.duration_min + Math.random() * (gSkill.duration_max - gSkill.duration_min), target: target.name, }; player.to_resend = "u+cid"; resolve = { response: "data", place: data.name, success: false, in_progress: true }; } else if (data.name == "fishing" || data.name == "mining") { var direction = 0; var the_zone = null; consume_mp(player, gSkill.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: gSkill.duration_min + Math.random() * (gSkill.duration_max - gSkill.duration_min), drop: the_zone.drop, }; } resolve = { response: "data", place: data.name, success: false, in_progress: true }; player.to_resend = "u+cid"; } else if (data.name == "light") { consume_mp(player, gSkill.mp); xy_emit(player, "light", { name: player.name }); player.to_resend = "u+cid"; } else if (data.name == "charge") { player.s.charging = { ms: gSkill.duration }; player.to_resend = "u+cid"; } else if (data.name == "dash") { consume_mp(player, gSkill.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 + ")" }); } } } else if (data.name == "hardshell" || data.name == "power" || data.name == "xpower") { consume_mp(player, gSkill.mp, mode.implicit_targets && player); player.s[gSkill.condition] = { ms: gSkill.duration || G.conditions[gSkill.condition].duration, }; player.to_resend = "u+cid"; } else if (data.name == "mshield") { // consume_mp(player, gSkill.mp); if (player.s[gSkill.condition]) { delete player.s[gSkill.condition]; } else { player.s[gSkill.condition] = { ms: 999999999 }; } player.to_resend = "u+cid"; } else if ( data.name == "mcourage" || data.name == "mfrenzy" || data.name == "massproduction" || data.name == "massproductionpp" || data.name == "massexchange" || data.name == "massexchangepp" ) { consume_mp(player, gSkill.mp, mode.implicit_targets && player); player.s[gSkill.condition] = { ms: G.conditions[gSkill.condition].duration }; xy_emit(player, "ui", { type: data.name, name: player.name }); player.to_resend = "u+cid"; } else if (data.name == "throw") { if (target.s.invincible) { return fail_response("target_invincible", data.name); } var item = player.items[data.num]; if (!item) { return fail_response("skill_no_item", data.name); } else if (item.name == "placeholder") { return fail_response("item_placeholder", data.name); } else if (item.l) { return fail_response("item_locked", data.name); } else if (item.b) { return fail_response("item_blocked", data.name); } var prop = calculate_item_properties(item); var negative = in_arr(item.name, G.skills.throw.negative); for (const p of G.skills.throw.nprop) { if (prop[p]) { negative = true; break; } } if (negative && target.is_player && !is_in_pvp(player)) { return fail_response("not_in_pvp", data.name); } consume_one(player, data.num); var damage = 0; 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, gSkill.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, "u+cid"); } else { target.u = true; target.cid++; } } else if (data.name == "phaseout") { consume_mp(player, gSkill.mp, mode.implicit_targets && player); player.s.phasedout = { ms: G.conditions.phasedout.duration }; } else if (data.name == "pcoat") { consume_mp(player, gSkill.mp, mode.implicit_targets && player); player.s.poisonous = { ms: G.conditions.poisonous.duration }; } else 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; } } else if (data.name == "entangle" || data.name == "tangle") { if (target.s.invincible) { return fail_response("target_invincible", data.name); } 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, gSkill.mp, target); add_pdps(player, target, 4000); if (target.is_monster) { target.u = true; target.cid++; ccms(target); } else { resend(target, "u+cid"); } } else if (data.name == "4fingers") { if (target.s.invincible) { return fail_response("target_invincible", data.name); } if (!is_in_pvp(player) && !is_same(player, target, 1)) { return fail_response("non_friendly_target", data.name); } add_condition(target, "fingered", { ms: gSkill.duration }); add_condition(target, "stunned", { duration: gSkill.duration - 2000 }); xy_emit(player, "ui", { type: "4fingers", from: player.name, to: target.name }); consume_mp(player, gSkill.mp, target); add_pdps(player, target, 1000); resend(target, "u+cid"); player.to_resend = "u+cid"; } else if (data.name == "revive") { if (!target.rip) { return fail_response("target_alive", data.name); } consume_mp(player, gSkill.mp, target); 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 { if (target.party == player.party) { add_pdps(player, target, target.pdps / 5); } target.c.revival = { ms: 8000, f: player.name }; resend(target, "u+cid"); } } else if (data.name == "cburst") { var targeted = {}; var attack = null; var c_resolve = null; consume_mp(player, gSkill.mp); player.first_burst = true; player.halt = true; if (is_array(data.targets)) { // Only look at the first 16 targets in the array if more are provided for (const t of data.targets.slice(0, 16)) { const id = t[0]; // Prevent attacking the same entity twice if (targeted[id]) { continue; } targeted[id] = true; const mp = max(0, parseInt(t[1]) || 0); if (mp <= 0) { continue; } let target = instances[player.in].monsters[id]; if (!target) { target = instances[player.in].players[id]; } if (!target || is_invinc(target) || target.name == player.name) { continue; } if (isTargetTooFar(target)) { continue; } player.next_mp = mp; attack = commence_attack(player, target, "cburst"); if (!attack || !attack.projectile) { continue; } 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) { reject = { failed: true, place: data.name, reason: "no_target" }; disappearing_text(player.socket, player, "NO HITS"); } else { resolve = c_resolve; } } else 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; } } else if (data.name == "darkblessing" || data.name == "warcry") { consume_mp(player, gSkill.mp); for (var id in instances[player.in].players) { var target = instances[player.in].players[id]; if ( !target.npc && distance(player, target, true) < gSkill.range && (!(G.maps[player.map].pvp || is_pvp) || is_same(player, target, 1)) ) { target.s[gSkill.condition] = { ms: gSkill.duration, f: player.name }; resend(target, "u+cid"); add_pdps(player, target, 500); } } xy_emit(player, "ui", { type: data.name }); } else if (data.name == "3shot" || data.name == "5shot") { player.halt = true; const targeted = {}; let c_resolve = null; //console.log(data.ids); if (is_array(data.ids)) { // Only look at the first Xshot targets in the array if more are provided for (const id of data.ids.slice(0, data.name === "5shot" ? 5 : 3)) { // Prevent attacking the same entity twice if (targeted[id]) { continue; } targeted[id] = true; target = instances[player.in].monsters[id]; if (!target) { target = instances[player.in].players[id]; } if (!target || is_invinc(target) || target.name == player.name) { continue; } if (isTargetTooFar(target)) { continue; } const attack = commence_attack(player, target, data.name); if (!attack || !attack.projectile) { continue; } 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"; consume_mp(player, gSkill.mp, target); if (!c_resolve) { reject = { failed: true, place: data.name, reason: "no_target" }; disappearing_text(player.socket, player, "NO HITS"); } else { resolve = c_resolve; } } else 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, gSkill.mp); xy_emit(player, "ui", { type: "track", name: player.name }); socket.emit("track", list); player.to_resend = "u+cid"; } else 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, gSkill.mp, target); xy_emit(player, "ui", { type: "agitate", name: player.name, ids: ids }); player.to_resend = "u+cid"; add_pdps(player, null, 1000); } else if (data.name == "absorb") { var ids = []; if (is_same(player, target, 1)) { consume_mp(player, gSkill.mp, target); } else { if (player.mp < gSkill.mp * 6 || player.level < 75) { return socket.emit("game_response", { response: "non_friendly_target", place: data.name, failed: true }); } consume_mp(player, gSkill.mp * 6, target); 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); } else 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, gSkill.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 }; } else if (data.name == "scare") { var ids = []; consume_mp(player, gSkill.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"; } else if (data.name == "huntersmark") { if (target.is_player && !is_in_pvp(player) && !is_same(player, target, 1)) { return fail_response("skill_cant_pve", data.name); } consume_mp(player, gSkill.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"; } else if (data.name == "charm") { consume_mp(player, gSkill.mp, target); if (Math.random() > player.a.charm.attr0 / 100) { 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"; } else if (data.name == "cleave" || data.name == "shadowstrike") { player.to_resend = "u+cid"; 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 < gSkill.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 < gSkill.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, gSkill.mp, reftarget); xy_emit(player, "ui", { type: data.name, name: player.name, ids: ids }); player.halt = false; if (c_resolve) { resolve = c_resolve; } } else if (data.name == "magiport") { consume_mp(player, gSkill.mp, target); 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"; } else 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 fail_response("blink_failed", data.name); } 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, gSkill.mp, mode.implicit_targets && player); } // xy_emit(player,"ui",{type:"blinking",name:player.name}); resend(player, "u+cid"); // #TODO: Appear animation for non-self's [21/05/18] } else 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 fail_response("blink_failed", data.name); } if (instance.mount != instances[player.in].mount) { return fail_response("cant_in_bank", data.name); } var spot = safe_xy_nearby(instance.name, x, y); if (!spot) { return fail_response("blink_failed", data.name); } 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, gSkill.mp, mode.implicit_targets && player); } // xy_emit(player,"ui",{type:"blinking",name:player.name}); player.to_resend = "u+cid"; // #TODO: Appear animation for non-self's [21/05/18] } else if (data.name == "mluck") { consume_mp(player, gSkill.mp, target); if ( !target.s[gSkill.condition] || !target.s[gSkill.condition].strong || target.s[gSkill.condition].f == player.name ) { target.s[gSkill.condition] = { ms: G.conditions.mluck.duration, f: player.name }; } if (target.owner == player.owner) { target.s[gSkill.condition].strong = true; } xy_emit(player, "ui", { type: "mluck", from: player.name, to: target.name }); resend(target, "u+cid"); resend(player, "u+cid"); } else if (data.name == "rspeed") { consume_mp(player, gSkill.mp, target); target.s[gSkill.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); } } else if (data.name == "reflection") { consume_mp(player, gSkill.mp, target); target.s[gSkill.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); } } else if (data.name == "energize") { 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[gSkill.condition] = { ms: G.conditions[gSkill.condition].duration }; xy_emit(player, "ui", { type: "energize", from: player.name, to: target.name }); resend(target, "u+cid"); resend(player, "u+cid"); } else if (data.name == "alchemy") { 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, gSkill.mp); xy_emit(player, "ui", { type: "alchemy", name: player.name }); var gold = 0; 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 }); } else if (data.name == "temporalsurge") { consume_mp(player, gSkill.mp); var count = 0; var times = []; for (var i = 0; i < monster_respawns.length; i++) { if (distance(monster_respawns[i][0], player) < 160) { monster_respawns[i][1] *= 0.85; monster_respawns[i][1] -= 1000; times.push(monster_respawns[i][1]); xy_emit( { map: player.map, in: player.in, x: monster_respawns[i][0].x, y: monster_respawns[i][0].y }, "eval", "assassin_smoke(" + monster_respawns[i][0].x + "," + monster_respawns[i][0].y + ",'icecrack');", ); count++; } } if (!count) { socket.emit("game_response", "temporalsurge_none"); } else { socket.emit("game_response", { response: "temporalsurge", count: count, times: times }); } } 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 && 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"); } 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, }); } 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 = {}; 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, }); } 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 (S.blessed_minutes) { cdata.blessed_by = S.blessed_by; cdata.blessed_minutes = S.blessed_minutes; player.s.patronsgrace = { ms: G.conditions.patronsgrace.duration, f: S.blessed_by }; } 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) { const player = players[socket.id]; if (!player) { return; } if (data.item == "hp" || data.item == "mp") { if (player.last.potion) { const ms = -mssince(player.last.potion); if (ms > 0) { return fail_response("not_ready", { ms: ms }); } } player.last.potion = future_ms(4000); if (data.item == "hp") { player.hp = Math.min(player.hp + 50, player.max_hp); disappearing_text(socket, player, "+50", { color: "green", xy: 1, s: "hp", nohp: 1 }); } if (data.item == "mp") { player.mp = Math.min(player.mp + 100, player.max_mp); disappearing_text(socket, player, "+100", { color: "#006AA9", xy: 1, s: "mp", nomp: 1 }); } // 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("skin", function (data) { var player = players[socket.id]; if (player.role != "gm") { return; } player.tskin = data.name; 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 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; } if (attacker.p && attacker.p.home == region + server_name) { // Favoring home server characters [12/02/26] mnet *= 5; } 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 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) { monster_respawns.push([target, round(G.monsters[target.type].respawn * (720 + Math.random() * 480))]); //setTimeout( // new_monster_f(target.oin, target.map_def, { before_respawn: target }), // round(G.monsters[target.type].respawn * (720 + Math.random() * 480)), //); } else { monster_respawns.push([target, round(G.monsters[target.type].respawn * 1000 + Math.random() * 900)]); //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]; } else { monster.width = monster.height = 24; } if (G.monsters[monster.type].size) { monster.width = Math.round(monster.width * G.monsters[monster.type].size); monster.height = Math.round(monster.height * G.monsters[monster.type].size); } 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; } 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_f(instance, map_def, args) { return function () { try { new_monster(instance, map_def, args); } catch (e) { log_trace("#X Critical-new_monster", e); } }; } function start_moving_element(monster) { // var last.move=monster.moving&&monster.last.move; if (!monster.moving) { monster.last.move = new Date(); } // VERY VERY VERY VERY IMPORTANT - This was the cause of the server/client position discrepancies [08/01/16] monster.moving = true; monster.abs = false; monster.u = true; monster.from_x = monster.x; monster.from_y = monster.y; monster.move_num = total_moves++; calculate_vxy(monster); } function send_xy_updates(player, list) { // third version, very refined - deleted older versions after the "instances" commit // #NOTE: this routine is the bottlenck, unclear whether anything can be done about it, test with mode.nopush [31/07/18] if (mode.noxy) { return; } // list includes to_push - everything in that instance that are updated var data = { players: [], monsters: [], type: "xy", in: player.in, map: player.map }; var m_mark = {}; var p_count = 0; list.forEach(function (def) { if (!is_invis(def.entity) && within_xy_range(player, def.entity) && player.id != def.entity.id) { if (def.entity.is_monster) { if (player.push) { m_mark[def.entity.id] = 1; } data.monsters.push(def.data); } else { if (player.push) { m_mark[def.entity.id] = 1; } data.players.push(def.data); } } }); perfc.sxyu += list.length; if (!mode.nopush && player.push) { // player is moving and receiving/seeing new entities //#GTODO: Maybe also calculate and factor in the monster/player dx/dy [27/08/16] var log_push = mode.upush_test; var dx = player.x - player.push[0]; var dy = player.y - player.push[1]; var avoid = { x: player.push[0] - dx * 0.4, y: player.push[1] - dy * 0.4, vision: player.vision, in: player.in }; // on main1, when you put a merchant on the town's bottom limit, and move upwards from the first island to the town, the merchant is lost with the avoid logic var grab = { x: player.push[0] + dx * 1.4, y: player.push[1] + dy * 1.4, vision: player.vision, in: player.in }; if (log_push) { server_log(JSON.stringify(grab) + " dx/dy " + dx + " " + dy); } for (var id in instances[player.in].monsters) { if (m_mark[instances[player.in].monsters[id].id]) { continue; } var monster = instances[player.in].monsters[id]; if ((mode.novi || !within_xy_range(avoid, monster)) && within_xy_range(grab, monster)) { data.monsters.push(monster_to_client(monster)); p_count++; } } for (var id in instances[player.in].players) { if ( m_mark[instances[player.in].players[id].id] || instances[player.in].players[id].id == player.id || is_invis(instances[player.in].players[id]) ) { continue; } var monster = instances[player.in].players[id]; if ((mode.novi || !within_xy_range(avoid, monster)) && within_xy_range(grab, monster)) { data.players.push(player_to_client(monster, 1)); p_count++; } } perfc.sxyu += Object.keys(instances[player.in].players).length + Object.keys(instances[player.in].monsters).length; if (log_push) { server_log("push done: " + p_count); } player.push = false; } if (data.players.length || data.monsters.length || (mode.xyinf && player.moving)) { if (mode.xyinf && player.moving) { data.xy = { x: player.x, y: player.y }; } // to synchronise the client speed to match the server displacement [07/01/16] player.socket.emit("entities", data); } } function step_out_of_invis(player) { if (player.s.invincible) { player.s.invincible = { ms: 0 }; } if (!player.s.invis) { return; } delete player.s.invis; player.u = true; consume_skill(player, "invis", true); resend(player); } function reduce_targets(player, monster) { if (is_string(player)) { player = players[name_to_id[player]]; } if (!player) { return; } player.targets--; if (player.targets < 0) { player.targets = 0; } var p = "targets_p"; if (monster.damage_type == "magical") { p = "targets_m"; } else if (monster.damage_type == "pure") { p = "targets_u"; } player[p]--; if (player[p] < 0) { player[p] = 0; } resend(player); } function increase_targets(player, monster) { if (!player) { return; } player.targets++; player.to_resend = " "; var p = "targets_p"; if (monster.damage_type == "magical") { p = "targets_m"; } else if (monster.damage_type == "pure") { p = "targets_u"; } player[p]++; resend(player); // this needs to be added, or all usages need to be re-visited } function port_monster(monster, target, extras) { if (is_disabled(monster)) { return false; } var spot = safe_xy_nearby(target.map, target.x - 8, target.y - 6); if (!spot || instances[target.in].solo) { return false; } pulled = monster; pulled.s.magiport = { ms: (monster == target && 80) || 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; for (var id in extras) { pulled.s.magiport[id] = extras[id]; } monster.abs = true; monster.moving = false; monster.m++; monster.cid++; monster.u = true; return true; } function stop_pursuit(monster, args) { // if(is_sdk && monster.target && !(args && args.cause)) console.log("stop_pursuit: "+monster.target); if (!args) { args = {}; } if (monster.target) { var target = players[name_to_id[monster.target]]; if ( !args.redirect && !args.force && !args.stop && target && !is_invis(target) && !target.rip && monster.a.portal && port_monster(monster, target) ) { return; } monster.target = null; if (monster.master && target && !args.redirect) { if ( instances[monster.in].monsters[monster.master] && instances[monster.in].monsters[monster.master].cooperative ) { add_coop_points(instances[monster.in].monsters[monster.master], target, max(monster.hp, 300)); } } reduce_targets(target, monster); } if (monster.spawn && !args.redirect) { return remove_monster(monster, { method: "disappear" }); } if (is_sdk && args && args.cause) { console.log("stop_pursuit: " + args.cause); } monster.last_level = future_s(Math.random() * 100 - 50); monster.cid++; monster.u = true; monster.irregular = 2; calculate_monster_stats(monster); // Forward args.cause to the UI so it can give different messages xy_emit(monster, "ui", { id: monster.id, type: "disengage", event: true, cause: args.cause }); } 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; i < player.isize; i++) { if (player.items[i] && player.items[i].name == "xptome") { lost_xp = floor(lost_xp / 50); consume_one(player, i); player.socket.emit("game_log", { message: "A tome fades away", color: "#B5C09C" }); break; } } player.xp -= lost_xp; if (gameplay == "hardcore") { player.socket.emit("game_log", "Lost 1 level"); } player.socket.emit("game_response", { response: "defeated_by_a_monster", xp: lost_xp, monster: attacker.type }); if (attacker.target) { stop_pursuit(attacker, { force: true, cause: "defeat" }); } rip(player); if (gameplay == "hardcore") { player.level = max(1, player.level - 1); player.xp = 0; } lost_xp /= 12; if (player.type == "merchant") { lost_xp = 0; } var mult = 1; if (attacker["1hp"]) { mult = 500; } else if (G.monsters[attacker.type] && G.monsters[attacker.type].special) { mult = 20; } while (lost_xp > 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 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 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 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 = []; var monster_map = {}; var now_date = instance.last_update; var aggressives = {}; var targets = {}; for (var id in instance.monsters) { var monster = instance.monsters[id]; var events = []; var change = false; var 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]; 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, { cause: "anger" }); } 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, { cause: "warpstomp" }); } 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((spi) => { const condition = spi[0]; // interval or "hp:0.75" const name = spi[1]; // monster type const count = spi[2] || 1; // default to 1 // --- Timed spawns (existing) --- if (typeof condition === "number") { if (!monster.last[name] || mssince(monster.last[name]) > condition) { for (let i = 0; i < count; i++) { const pname = random_one(Object.keys(monster.points)); const 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(); const 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, }); } } } // --- HP threshold spawns (new) --- if (typeof condition === "string" && condition.startsWith("hp:")) { const threshold = parseFloat(condition.split(":")[1]); // e.g. 0.75 const currentHpRatio = monster.hp / monster.max_hp; const key = name + "_hp_" + threshold; if (currentHpRatio <= threshold && (monster.last[key] == undefined || monster.last[key] < count)) { const pname = random_one(Object.keys(monster.points)); const player = get_player(pname); if (player && !player.npc && distance(monster, player) < 400) { if (is_same(player, get_player(monster.target), true)) { const spot = safe_xy_nearby( player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10, ); if (spot) { new_monster(instance.name, { type: name, stype: "spawn", x: spot.x, y: spot.y, target: player.name, master: monster.id, }); monster.last[key] = 1 + (monster.last[key] || 0); } } } } } }); } 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 if (!G.monsters[monster.type].stationary) { 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 && !G.monsters[monster.type].stationary ) { // 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: ref.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: ref.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) { 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 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); } } } 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 && Math.random() < 0.0005) { delete npc.s.block; npc.hp = npc.max_hp; npc.mp = npc.max_mp; npc.rip = false; transport_player_to(npc, npc.map, 0, 1); resend(npc, "u+cid"); continue; } 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 bless_loop() { if (S.blessed_minutes) { S.blessed_minutes--; } if (!S.blessed_minutes) { delete E.blessed_minutes; delete E.blessed_by; return; } E.blessed_minutes = S.blessed_minutes; E.blessed_by = S.blessed_by; for (var id in players) { var player = players[id]; var rs = false; if (!player.s.patronsgrace) { rs = true; } player.s.patronsgrace = { ms: G.conditions.patronsgrace.duration, f: S.blessed_by }; if (rs) { resend(player, "u+cid"); } } } setInterval(bless_loop, 60 * 1000); 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); } 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 (name) { var player = get_player(name); if (player && player.esize) { list.push(player); } else if (!mode.prevent_external) { list.push({ name }); } }); 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.map((e) => e.name).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); 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 i = monster_respawns.length - 1; i >= 0; i--) { monster_respawns[i][1] -= 10; if (monster_respawns[i][1] < 0) { var target = monster_respawns[i][0]; new_monster(target.oin, target.map_def, { before_respawn: target }); monster_respawns.splice(i, 1); } } } catch (e) { log_trace("#X monster respawn loop error", e); } }, 10); 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(); } } 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_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; i < seconds; i++) { function m(x) { return function () { broadcast("server_message", { message: "Server shutdown in: " + x, color: "#DD1B50", nod: true, log: true }); }; } setTimeout(m(seconds - i), i * 1000); } broadcast("eval", { code: "call_code_function('trigger_event','shutdown',{seconds:" + seconds + "})" }); setTimeout(shutdown, seconds * 1000); if (!is_sdk && region == "EU" && server_name == "I") { discord_call("Game update sequence initiated. Servers are shutting down in 20 seconds!"); } } function exit_handler(options, err) { if (options.exit) { server_log("exit_handler", 1); shutdown_routine(); } } process.on("exit", exit_handler.bind(null, { cleanup: true })); process.on("SIGHUP", exit_handler.bind(null, { exit: true })); process.on("SIGQUIT", exit_handler.bind(null, { exit: true })); process.on("SIGINT", exit_handler.bind(null, { exit: true })); process.on("SIGTERM", exit_handler.bind(null, { exit: true })); // process.on('uncaughtException', function(err) { console.log('Caught exception: ' + err); });