Files
adventureland/node/server.js
T

13850 lines
406 KiB
JavaScript

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 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,
};
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
};
var events = {
// SEASONS
holidayseason: false,
lunarnewyear: false,
valentines: true,
pinkgoo: 60, // 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,
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("d_e");
create_instance("d_g");
create_instance("d_b1");
create_instance("d_a1");
create_instance("d_a2");
server_bfs("crypt");
server_bfs("winter_instance");
server_bfs("tomb");
server_bfs("dungeon0");
server_bfs("cgallery");
} else if (gameplay == "dungeon") {
for (var name in G.maps) {
if (G.maps[name].world == "dungeon") {
create_instance(name);
}
}
}
create_instance("jail");
console.log("Calculations took: " + mssince(start) + "ms");
shuffle(hiding_places);
init_tavern();
init_server();
server.started = true;
server.live = true;
server.last_update = new Date();
} catch (e) {
log_trace("init", e);
}
},
function () {
console.log("#X init_game's create_server call failed");
},
);
}
init_game();
function reload_server(to_broadcast, change) {
appengine_call(
"reload_server",
{ keyword: variables.keyword, port: port, region: region, pvp: is_pvp || "", gameplay: gameplay },
function (result) {
try {
G = result.game;
D = result.dynamics;
sprocess_game_data();
prop_cache = {};
if (to_broadcast) {
broadcast("reloaded", { change: change || "" });
}
} catch (e) {
broadcast("notice", { message: "Server Live Reload Failed" });
log_trace("#X live_reload", e);
}
},
function () {
// setTimeout(init_game,1000);
server_log("#X live_reload failed", 1);
broadcast("notice", { message: "Live Reload Failed" });
},
);
}
function 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",
])
) {
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;
var excess = max(
0,
max(player.targets_p - player.courage, max(player.targets_m - player.mcourage, player.targets_u - player.pcourage)),
);
var sredux = [0, 20, 40, 70, 80, 90, 100];
player.speed -= sredux[min(sredux.length - 1, excess)];
if (excess > 2) {
player.attack = round(player.attack * 0.2);
} else if (excess > 1) {
player.attack = round(player.attack * 0.4);
} else if (excess) {
player.attack = round(player.attack * 0.6);
}
player.fear = excess;
if (player.map == "winterland") {
player.speed *= 0.95;
}
if (player.p.stand || player.s.hardshell) {
player.speed = 10;
}
player.evasion = min(50, player.evasion);
player.reflection = min((player.s.reflection && 50) || 30, player.reflection);
player.speed = min(player.speed, player.cruise || 200000);
player.speed = round(max(5, player.speed));
if (!player.gold && player.gold !== 0) {
player.gold = 0;
server_log("#X - GOLD BUG calculate", 1);
}
recalculate_vxy(player);
perfc.cps += 1;
}
function calculate_common_stats(entity) {
if (entity.s.poisoned) {
entity.frequency *= 0.9;
}
if (entity.s.frozen) {
entity.frequency *= 0.3;
entity.speed -= 40;
}
if (entity.speed < 1) {
entity.speed = 1;
}
if (entity.s.tangled) {
entity.speed = min(entity.speed, 24);
}
}
function calculate_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]);
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 += 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);
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_in_pvp(player, 1));
}
});
}
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_in_pvp(player, 1));
}
});
}
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_in_pvp(player, 1));
}
});
}
/**
* 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]
drop_item_logic(drop, item, is_in_pvp(player, 1));
}
});
}
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_in_pvp(player, 1));
}
});
}
if (player.tskin == "konami") {
D.drops.konami.forEach(function (item) {
if (Math.random() / share / player.luckm / monster.level < item[0] || mode.drop_all) {
drop_item_logic(drop, item, is_in_pvp(player, 1));
}
});
}
if (player.p.first && !player.p.first_drop) {
player.p.first_drop = true;
drop.gold += 100000;
drop.items.push(create_new_item("ringsj"));
drop.items.push(create_new_item("ringsj"));
drop.items.push(create_new_item("ringsj"));
drop.items.push(create_new_item("hpbelt"));
drop.items.push(create_new_item("gem0"));
}
if (Math.random() < D.drops.gold.x10) {
drop.gold *= 10;
chest = "chest4";
} // previously 12
if (Math.random() < D.drops.gold.x50) {
drop.gold *= 50;
chest = "chest5";
} // previously 200
if (drop.items.length || drop.cash) {
chest = "chest6";
}
drop.date = new Date();
if (player.party) {
var owners = [];
parties[player.party].forEach(function (name) {
var current = players[name_to_id[name]];
if (current && !owners.includes(current.owner)) {
owners.push(current.owner);
}
});
party_emit(
player.party,
"drop",
{
x: drop.x,
y: drop.y,
items: drop.items.length,
chest: chest,
id: drop_id,
party: player.party,
map: drop.map,
owners: owners,
},
{ instance: player.in },
);
} else {
player.socket.emit("drop", {
x: drop.x,
y: drop.y,
items: drop.items.length,
chest: chest,
id: drop_id,
map: drop.map,
owners: [player.owner],
});
}
}
function drop_something_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;
for (var name in monster.points) {
var current = players[name_to_id[name]];
if (current) {
// && current.map==monster.map
total += max(0, monster.points[name]);
}
}
for (var name in monster.points) {
var current = players[name_to_id[name]];
var share = max(0, monster.points[name]) / total;
if (current && share > 0.0025) {
// && current.map==monster.map
if (monster.rbuff && G.conditions[monster.rbuff]) {
current.s[monster.rbuff] = { ms: G.conditions[monster.rbuff].duration };
}
if (monster.cbuff) {
for (var i = 0; i < monster.cbuff.length; i++) {
if (current.level <= monster.cbuff[i][0] && G.conditions[monster.cbuff[i][1]]) {
current.s[monster.cbuff[i][1]] = { ms: G.conditions[monster.cbuff[i][1]].duration };
break;
}
}
}
if (G.monsters[monster.type]["1hp"]) {
drop_something(current, monster);
} else {
drop_something(current, monster, share);
}
var score = calculate_monster_score(current, monster, share);
current.p.stats.monsters[monster.type] = (current.p.stats.monsters[monster.type] || 0) + 1;
current.p.stats.monsters_diff[monster.type] = (current.p.stats.monsters_diff[monster.type] || 0) + (score - 1);
monster_hunt_logic(current, monster, share);
if (current.type == "merchant") {
continue;
}
current.xp += round(monster.xp * share * current.xpm);
if (current.t) {
current.t.xp += round(monster.xp * share * current.xpm);
}
delete current.s.coop;
resend(current, "u+cid");
}
}
}
function issue_monster_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) {
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]];
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,
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.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;
} else if (info.damage_type == "magical") {
defense = "resistance";
pierce = "rpiercing";
info.apiercing = 0;
} else {
info.damage_type = "physical";
}
if (target.is_player && attack > 0 && !info.heal) {
add_pdps(target, attacker, attack * B.dps_tank_mult); // "tank"
if (attacker.cooperative) {
add_coop_points(attacker, target, attack * B.dps_tank_mult);
}
}
if (attacker.is_monster && attack > 0 && !info.heal) {
attacker.outgoing += min(target.hp, attack);
}
if (
target.reflection &&
defense == "resistance" &&
Math.random() * 100 < 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 ||
(attacker.miss && Math.random() * 100 < attacker.miss) ||
(target.avoidance && Math.random() * 100 < target.avoidance)
) {
return xy_emit(
info.action,
"hit",
{ pid: def.pid, hid: attacker.id, id: target.id, damage: 0, miss: true, 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.apiercing),
) || 0;
if (target.incdmgamp) {
attack = round((attack * (100 + target.incdmgamp)) / 100.0);
}
if (target["1hp"]) {
attack = (def.crit && 2) || 1;
}
if (attack > 0 && target.s.invincible) {
attack = 0;
}
if (attacker.tskin == "konami" && target.type != attacker.p.target_lock) {
attack = 0;
}
o_attack = attack = ceil(attack);
if (info.conditions.includes("frozen") && !target.immune && target.hp > attack) {
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<player.items.length;i++) if(!player.items[i]) player.esize++;
// player.esize+=player.isize-player.items.length;
// }
if (!in_arr("nc", events)) {
add_call_cost(call_modifier);
calculate_player_stats(player);
}
var data = player_to_client(player);
if (player.hitchhikers && player.hitchhikers.length) {
data.hitchhikers = player.hitchhikers;
player.hitchhikers = [];
}
if (in_arr("reopen", events) || player.to_reopen) {
if (current_socket != player.socket) {
add_call_cost(call_modifier * 4);
}
data.reopen = true;
player.socket.emit("player", data);
delete player.to_reopen;
} else {
player.socket.emit("player", data);
}
delete player.to_resend;
}
function transport_monster_to(monster, to_in, to_map, x, y) {
xy_emit(monster, "disappear", { id: monster.id, effect: "magiport", to: to_map, s: 0, reason: "magiport" });
delete instances[monster.in].monsters[monster.id];
instances[to_in].monsters[monster.id] = monster;
monster.x = x;
monster.y = y;
monster.in = to_in;
monster.map = to_map;
monster.abs = true;
monster.moving = false;
}
function transport_observer_to(observer, to_in, map, x, y) {
delete instances[observer.in].observers[observer.id];
var instance = instances[to_in];
instance.observers[observer.id] = observer;
resume_instance(instance);
observer.in = to_in;
observer.map = map;
observer.x = x;
observer.y = y;
// observer.socket.latest_calls+=8;
observer.socket.emit("new_map", {
name: instance.map,
in: to_in,
x: observer.x,
y: observer.y,
direction: 0,
effect: 0,
info: instance.info,
m: 0,
entities: send_all_xy(observer, { raw: true }),
});
}
function transport_player_to(player, name, point, effect) {
// if((player.duel || player.team) && player.in!=name) restore_state(player); // PROBLEMATIC [29/07/22]
if (!instances[name]) {
name = "main";
}
var instance = instances[name];
var new_map = G.maps[instance.map];
var direction = 0;
var scatter = 0;
var data = { id: player.id, reason: "transport" };
if (!is_invis(player) && !player.stealth && G.maps[name]) {
data.to = name;
data.s = point;
}
if (effect) {
data.effect = effect;
}
xy_emit(player, "disappear", data);
player.map = instance.map;
if (player.in != name) {
delete instances[player.in].players[player.id];
if (instances[player.in].solo == player.id) {
destroy_instance(player.in);
}
}
pmap_remove(player);
player.in = name;
resume_instance(instances[player.in]);
instances[player.in].players[player.id] = player;
if (!instances[player.in].mount && player.user && !player.mounting && !player.unmounting) {
player.unmounting = new Date();
sync_loop();
} // patches the exit by jail loophole [20/08/18]
if (Object.keys(player.bets).length && name != "tavern") {
for (var bid in player.bets) {
player.gold += player.bets[bid].gold;
}
player.bets = {};
resend(player, "reopen+nc");
}
player.m++;
if (is_array(point)) {
player.x = point[0];
player.y = point[1];
direction = point[2] || 0;
scatter = point[3] || 0;
} else {
if (!new_map.spawns[point || 0]) {
point = 0;
}
player.x = new_map.spawns[point || 0][0];
player.y = new_map.spawns[point || 0][1];
direction = new_map.spawns[point || 0][2];
scatter = new_map.spawns[point || 0][3] || 0;
}
if (scatter) {
player.x += Math.random() * scatter - scatter / 2;
player.y += Math.random() * scatter - scatter / 2;
}
// server_log(effect);
if (effect) {
// server_log("here");
player.tp = effect; //appear
setTimeout(function () {
try {
if (!check_player(player)) {
return;
}
player.tp = false;
} catch (e) {
log_trace("#X Critical-tp", e);
}
}, 500);
}
player.moving = false;
player.vx = player.vy = 0;
player.u = true;
player.cid++;
calculate_player_stats(player);
pmap_add(player);
add_call_cost(player, 8, "transport"); // New - to offset send_all_xy [26/01/20]
if (0 && !effect && mssince(player.last.transport) > 6000) {
var ms = max(0, -mssince(player.last.attack)) + 3200;
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,
}; // ,computer:false
function register_table(table) {
if (table) {
table.forEach(function (drop) {
if (drop[1] == "open" && !data.tables[drop[2]]) {
data.tables[drop[2]] = D.drops[drop[2]];
register_table(D.drops[drop[2]]);
}
});
}
}
if (player.computer || 1) {
// data.computer=true;
data.drops = D.drops.monsters;
} else {
data.drops = {};
for (var name in data.monsters) {
if (data.monsters[name] >= 100 && D.drops.monsters[name]) {
data.drops[name] = D.drops.monsters[name];
}
}
}
if (D.drops.maps.global) {
data.global = D.drops.maps.global;
register_table(data.global);
}
if (D.drops.maps.global_static) {
data.global_static = D.drops.maps.global_static;
register_table(data.global_static);
}
for (var name in data.drops) {
register_table(data.drops[name]);
}
for (var name in data.maps) {
register_table(data.maps[name]);
}
for (var name in G.items) {
if (
G.items[name].e &&
(player.computer ||
1 ||
(player.p.stats.exchanges && player.p.stats.exchanges[name] && player.p.stats.exchanges[name] >= 100))
) {
if (G.items[name].upgrade || G.items[name].compound) {
for (var i = 0; i < 13; i++) {
if (D.drops[name + i]) {
data.tables[name + i] = D.drops[name + i];
register_table(data.tables[name + i]);
}
}
} else if (D.drops[name]) {
data.tables[name] = D.drops[name];
register_table(data.tables[name]);
}
}
}
socket.emit("tracker", data);
});
socket.on("set_home", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (player.p.dt.last_homeset && hsince(player.p.dt.last_homeset) < 36) {
return fail_response("sh_time", { hours: 36 - hsince(player.p.dt.last_homeset) });
}
player.p.dt.last_homeset = new Date();
player.p.home = region + server_name;
delete player.s.hopsickness;
success_response("home_set", { home: player.p.home });
});
socket.on("code", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (data.run) {
players[socket.id].code = true;
} else {
players[socket.id].code = false;
}
resend(players[socket.id], "u+cid");
});
socket.on("property", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (data.typing) {
if (!player.s.typing || player.s.typing.ms < 3000) {
reduce_call_cost();
}
player.s.typing = { ms: 4000 };
resend(player, "u+cid+nc");
} else {
var original = player.afk;
if (player.afk == "bot" || player.afk == "code") {
} else if (data.afk === true) {
player.afk = true;
} else if (data.afk === false) {
if (player.afk !== undefined) {
player.afk = false;
}
}
if (original !== player.afk) {
reduce_call_cost();
resend(player, "u+cid+nc");
}
}
});
socket.on("cruise", function (speed) {
var player = players[socket.id];
if (!player) {
return;
}
player.cruise = parseInt(speed);
resend(player, "u+cid");
success_response("cruise", { speed: player.cruise });
});
socket.on("test", function (data) {
if (is_sdk) {
console.log(data.test);
}
socket.emit("test", { date: new Date() });
});
socket.on("blocker", function (data) {
if (data.type == "pvp") {
if (instances["arena"].allow) {
socket.emit("blocker", { type: "pvp", allow: 1 });
} else {
socket.emit("blocker", { type: "pvp" });
}
}
});
socket.on("mail_take_item", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (mode.prevent_external) {
return socket.emit("game_response", { response: "not_in_this_server" });
}
if (!player.esize) {
return socket.emit("game_response", "inv_size");
}
appengine_call(
"take_item_from_mail",
{ owner: player.owner, mid: data.id },
function (result) {
var player = players[socket.id];
if (result.failed) {
return socket.emit("game_response", { response: "mail_item_already_taken" });
}
var item = JSON.parse(result.item);
add_item(player, item, { announce: false });
resend(player, "reopen");
socket.emit("game_response", { response: "mail_item_taken" });
},
function () {
socket.emit("game_response", { response: "mail_take_item_failed" });
},
);
});
socket.on("mail", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (mode.prevent_external) {
return fail_response("not_in_this_server");
}
var item = null;
var retries = 1;
if (player.gold < 48000) {
return fail_response("gold_not_enough");
}
player.gold -= 48000;
if (data.item && player.items[0] && player.items[0].name != "placeholder") {
if (player.items[0].l) {
return fail_response("item_locked");
}
if (player.items[0].b || player.items[0].v) {
return fail_response("item_blocked");
}
if (player.gold < 312000) {
return fail_response("gold_not_enough");
}
player.gold -= 312000;
item = JSON.stringify(player.items[0]);
player.items[0] = player.citems[0] = null;
retries = 3;
}
appengine_call(
"send_mail",
{
fro: player.name,
to: data.to,
subject: data.subject || "",
message: data.message || "",
rid: randomStr(50),
retries: retries,
item: item,
},
function (result) {
var player = players[socket.id];
if (result.failed) {
if (player) {
socket.emit("game_response", {
response: "mail_failed",
to: data.to,
reason: result.reason,
cevent: "mail_failed",
});
}
if (player && item && result.return && player.esize) {
var r = JSON.parse(item);
add_item(player, r);
resend(player, "reopen");
} else {
console.log("#M unsent mail, lost item: " + item);
}
return;
}
if (player) {
socket.emit("game_response", { response: "mail_sent", to: data.to, cevent: "mail_sent" });
}
},
function () {
var player = players[socket.id];
if (player) {
socket.emit("game_response", { response: "mail_failed", reason: "coms_failure" });
}
if (item) {
console.log("#M unsent mail, lost item: " + item);
}
},
);
resend(player, "reopen");
success_response("mail_sending", { sucess: false, in_progress: true, received: "unknown" });
});
socket.on("leave", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (!can_walk(player)) {
return fail_response("transport_failed");
}
if (
(0 && player.s.block) ||
player.targets > 5 ||
!(player.map == "jail" || player.map == "cyberland" || instances[player.in].solo)
) {
return fail_response("cant_escape");
}
transport_player_to(player, B.start_map);
success_response();
});
socket.on("transport", function (data) {
var player = players[socket.id];
var can_reach = false;
if (!player) {
return;
} // this was missing, probably caused a production exception - should be added everywhere [04/09/16]
if (!can_walk(player) || player.map == "jail") {
return fail_response("transport_failed");
}
var new_map = G.maps[data.to];
var s = data.s || 0;
var the_door = null;
if (!new_map || !instances[data.to] || !instances[data.to].allow) {
return fail_response("cant_enter");
}
if ((0 && player.s.block) || player.targets > 5) {
return fail_response("cant_escape");
}
(G.maps[player.map].doors || []).forEach(function (door) {
if (
!can_reach &&
door[4] == data.to &&
s == (door[5] || 0) &&
simple_distance(
{ map: player.map, x: G.maps[player.map].spawns[door[6]][0], y: G.maps[player.map].spawns[door[6]][1] },
player,
) < B.door_dist
) {
can_reach = "door";
the_door = door;
}
});
if ((player.map == "woffice" && gameplay == "hardcore") || player.role == "gm") {
can_reach = "transport";
}
if (
!can_reach &&
G.maps[player.map].ref.transporter &&
simple_distance(G.maps[player.map].ref.transporter, player) < B.transporter_dist &&
G.npcs.transporter.places[data.to] === s
) {
can_reach = "transport";
}
if (can_reach == "transport" && player.s.dampened) {
return fail_response("transport_cant_dampened");
}
if (!can_reach) {
return fail_response("transport_cant_reach");
}
if (the_door && the_door[7] == "protected") {
var protected = false;
for (var x in instances[player.in].monsters || {}) {
if (instances[player.in].monsters[x].map_def.gatekeeper) {
protected = true;
}
}
if (protected) {
return fail_response("transport_cant_protection");
}
}
if (
the_door &&
the_door[7] == "ulocked" &&
G.maps[data.to].mount &&
player.user &&
!player.user.unlocked[data.to]
) {
return fail_response("transport_cant_locked");
}
if (instances[data.to].mount && !player.user) {
if (player.mounting || player.unmounting) {
return fail_response("bank_opi");
}
add_call_cost(32, undefined, "bank");
player.mounting = new Date();
player.mount_to = data.to;
player.mount_s = s;
sync_loop();
return success_response({ success: false, in_progress: true });
} else if (!instances[data.to].mount && player.user) {
// previously handled at transport_player_to
if (player.mounting || player.unmounting) {
return fail_response("bank_opi");
}
add_call_cost(16, undefined, "bank");
player.unmounting = new Date();
player.unmount_to = data.to;
player.unmount_s = s;
sync_loop();
return success_response({ success: false, in_progress: true });
} else if (0 && instances[data.to].mount && player.user && data.to != player.map) {
// an easy prevention for the bank re-entry nuisance, bank can be re-entered physically but not digitally [03/11/16]
// commented out for bank_u / bank_b [06/05/20]
return fail_response("transport_failed");
} else {
decay_s(player, 5200);
transport_player_to(player, data.to, s);
return success_response();
}
});
socket.on("enter", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (gameplay != "normal") {
return fail_response("transport_failed");
}
if (!can_walk(player) || player.map == "jail") {
return fail_response("transport_failed");
}
if (player.s.block || player.targets > 5) {
return fail_response("cant_escape");
}
// if(player.role!="gm" && !(data.place==player.map && player.map=="cgallery"))
// {
// if(data.place!="resort" && !G.maps[player.map].ref.transporter || simple_distance(G.maps[player.map].ref.transporter,player)>80) return socket.emit("game_response","transport_cant_reach");
// if(data.place=="resort" && player.map!="resort") return socket.emit("game_response","transport_cant_reach");
// }
server_log(data);
var name = randomStr(24);
if (data.place == "resort" && 0) {
var name = "resort_" + data.name;
instance = instances[name] || create_instance(name, "resort_map");
transport_player_to(player, name);
} else if (data.place == "duelland") {
if (instances[data.name] && instances[data.name].map == "duelland") {
transport_player_to(player, data.name);
instance_emit(data.name, "game_log", { message: player.name + " is spectating the duel", color: "gray" });
} else {
return fail_response("cant_enter");
}
} else if (data.place == "crypt" || data.place == "winter_instance") {
var f = "cave";
var ref = G.maps.cave.spawns[2];
var item = "cryptkey";
if (data.place == "winter_instance") {
f = "winterland";
ref = G.maps.winterland.spawns[5];
item = "frozenkey";
}
if (simple_distance(player, { in: f, map: f, x: ref[0], y: ref[1] }) > 120) {
return fail_response("transport_cant_reach");
}
if (data.name) {
// 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");
const ms = gameplay === "hardcore" ? 400 : 3000 + Math.floor(Math.random() * 3000);
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,
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 >= 3) {
igrade = calculate_item_grade(def, { name: item0.name, level: item0.level - 2 });
}
delete player.p.c_item;
delete player.p.c_itemx;
delete player.p.c_roll;
oprobability = probability = D.compounds[igrade][new_level];
result = Math.random();
server_log(result + " < " + probability);
if (scroll_def.grade > grade) {
probability = probability * 1.1 + 0.001;
server_log("higher grade scroll " + result + " < " + probability);
if (!data.calculate) {
grace_bonus += 0.4;
}
high = scroll_def.grade - grade;
}
if (offering) {
var increase = 0.5;
if (!data.calculate) {
consume_one(player, data.offering_num);
}
grace = 0.027 * ((item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0) + 0.5 + player.p.ograce);
if (offering_def.grade > grade + 1) {
probability = probability * 1.64 + grace * 2;
high = true;
increase = 3;
} else if (offering_def.grade > grade) {
probability = probability * 1.48 + grace;
high = true;
increase = 1;
} else if (offering_def.grade == grade) {
probability = probability * 1.36 + min(30 * 0.027, grace);
} else if (offering_def.grade == grade - 1) {
probability = probability * 1.15 + min(25 * 0.019, grace) / max(item0.level - 2, 1);
increase = 0.2;
} else {
probability = probability * 1.08 + min(15 * 0.015, grace) / max(item0.level - 1, 1);
increase = 0.1;
}
if (!data.calculate) {
item0.grace = (item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0);
grace_bonus += increase;
}
server_log("offering " + result + " < " + probability);
} else {
grace = 0.007 * ((item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0) + player.p.ograce);
probability = probability + min(25 * 0.007, grace) / max(item0.level - 1, 1);
if (!data.calculate) {
item0.grace = max(max(item0.grace || 0, item1.grace || 0), item2.grace || 0);
}
}
if (!data.calculate) {
item0.grace = item0.grace / 6.4 + grace_bonus;
}
if (def.type == "booster") {
probability = 0.9999999999999;
proc = offering && 0.12;
} else {
probability = min(
probability,
min(oprobability * (3 + ((high && high * 0.6) || 0)), oprobability + 0.2 + ((high && high * 0.05) || 0)),
);
}
if (gameplay == "test") {
result = 0;
}
server_log(result + " < " + probability + " grace: " + grace);
if (data.calculate) {
return success_response("compound_chance", {
calculate: true,
chance: probability,
item: cache_item(item0),
scroll: scroll.name,
offering: (offering && offering.name) || undefined,
grace: (item0.grace || 0) + (item1.grace || 0) + (item2.grace || 0),
});
}
player.p.c_roll = result;
var len = 10000;
if (gameplay == "hardcore") {
len = 1200;
}
if (player.s.massproduction) {
len /= 2;
delete player.s.massproduction;
ex = "+u+cid";
}
if (player.s.massproductionpp) {
len /= 10;
delete player.s.massproductionpp;
ex = "+u+cid";
}
player.q.compound = { ms: len, len: len, num: data.items[0], nums: [] };
player.items[data.items[0]] = {
name: "placeholder",
p: {
chance: probability,
name: item0.name,
level: item0.level,
scroll: scroll.name,
offering: offering && offering.name,
nums: [],
},
};
player.items[data.items[1]] = null;
player.items[data.items[2]] = null;
if (result <= probability) {
if (offering) {
player.p.ograce = 0;
} else {
player.p.ograce *= 1 - new_level * 0.02;
}
item0.level = new_level;
if ((item0.p || item1.p || item2.p) != "legacy") {
item0.p = item0.p || item1.p || item2.p;
} else {
delete item0.p;
}
player.p.c_item = item0;
if (item0.oo != player.name) {
item0.o = player.name;
}
player.esize += 2;
if (parseInt(result * 10000) == parseInt(probability * 10000)) {
// && grade>=1
add_achievement(player, "lucky");
add_item_property(item0, "lucky");
}
if (item1.v || item2.v) {
item0.v = item1.v || item2.v;
}
if (item0.name == "ctristone" && item0.level == 1 && Math.random() < 0.012) {
item0.name = "cdarktristone";
}
if (def.type == "booster") {
var activate = false;
item0.extra = 0;
while (offering && proc && Math.random() < proc) {
item0.extra++;
item0.level += 1;
proc /= 2.0;
}
var seconds = 6 * 24 * 60 * 60;
[item0, item1, item2].forEach(function (i) {
if (i.expires) {
seconds -= ssince(i.expires);
activate = true;
} else {
seconds += def.days * 24 * 60 * 60;
}
});
if (activate) {
item0.expires = future_s(seconds / 3);
}
}
} else {
if (offering) {
player.p.ograce += 0.4;
}
player.esize += 2;
player.p.c_item = null;
player.p.c_itemx = item0;
}
player.citems[data.items[0]] = cache_item(player.items[data.items[0]]);
player.citems[data.items[1]] = cache_item(player.items[data.items[1]]);
player.citems[data.items[2]] = cache_item(player.items[data.items[2]]);
resend(player, "reopen+nc+inv" + ex);
} catch (e) {
server_log("compound_e " + e);
return socket.emit("game_response", { response: "exception", place: "compound", failed: true });
}
});
socket.on("upgrade", function (data) {
try {
var player = players[socket.id];
var item = player.items[data.item_num];
var scroll = player.items[data.scroll_num];
var offering = player.items[data.offering_num];
var result;
var ex = "";
if (!player || player.user) {
return fail_response("cant_in_bank");
}
if (player.q.upgrade) {
return socket.emit("game_response", "upgrade_in_progress");
}
G.maps.main.upgrade.name = player.name;
if (!player.computer && simple_distance(G.maps.main.upgrade, player) > B.sell_dist) {
return socket.emit("game_response", { response: "distance", place: "upgrade", failed: true });
}
if (!item) {
return socket.emit("game_response", "upgrade_no_item");
}
if (
offering &&
!(
(G.items[offering.name] && G.items[offering.name].type == "offering") ||
(G.items[offering.name] && G.items[offering.name].offering !== undefined && !(item.level > 0))
)
) {
return socket.emit("game_response", "upgrade_invalid_offering");
}
if (!scroll && !offering) {
return socket.emit("game_response", "upgrade_no_scroll");
}
if ((item.level || 0) != data.clevel) {
return socket.emit("game_response", "upgrade_mismatch");
}
var item_def = G.items[item.name];
var scroll_def = scroll && G.items[scroll.name];
var offering_def = offering && G.items[offering.name];
var grade = calculate_item_grade(item_def, item);
if (!item_def.upgrade) {
return socket.emit("game_response", "upgrade_cant");
}
if (
scroll &&
(!in_arr(scroll_def.type, ["uscroll", "pscroll"]) ||
(scroll_def.type == "uscroll" && !item_def.upgrade) ||
(scroll_def.type == "pscroll" && !item_def.stat) ||
grade > scroll_def.grade)
) {
if (grade == 4 && scroll_def.type == "uscroll") {
return socket.emit("game_response", {
response: "max_level",
level: item.level,
place: "upgrade",
failed: true,
});
}
return socket.emit("game_response", "upgrade_incompatible_scroll");
}
var new_level = (item.level || 0) + 1;
var probability = 1;
var oprobability = 1;
var grace = 0;
var high = false;
var ograde = calculate_item_grade(item_def, { name: item.name, level: 0 });
var tmult = 1;
if (ograde == 1) {
tmult = 1.5;
} else if (ograde == 2) {
tmult = 2;
}
delete player.p.u_item;
delete player.p.u_type;
delete player.p.u_itemx;
delete player.p.u_roll;
delete player.p.u_fail;
delete player.p.u_level;
player.p.u_level = item.level || 0;
if (!scroll) {
if (G.items[offering.name] && G.items[offering.name].offering !== undefined) {
var chance = 0.16;
var ms = 2000;
if (
G.items[offering.name].offering > ograde ||
(G.items[offering.name].offering == 2 && calculate_item_value(item) <= 20000000)
) {
chance = 0.32;
}
chance *= [2.8, 1.6, 1][ograde];
if (G.items[offering.name].offering < ograde) {
return socket.emit("game_response", "upgrade_invalid_offering");
}
if (data.calculate) {
return success_response("upgrade_chance", {
calculate: true,
chance: chance,
offering: offering.name,
item: cache_item(item),
grace: item.grace || 0,
});
}
var result = Math.random();
consume_one(player, data.offering_num);
player.p.u_type = "normal";
player.p.u_roll = result;
if (player.s.massproduction) {
ms /= 2;
delete player.s.massproduction;
ex = "+u+cid";
}
if (player.s.massproductionpp) {
ms /= 10;
delete player.s.massproductionpp;
ex = "+u+cid";
}
player.q.upgrade = { ms: ms, len: ms, num: data.item_num, silent: true };
player.items[data.item_num] = {
name: "placeholder",
p: {
chance: chance,
name: item.name,
level: item.level,
scroll: null,
offering: offering.name,
nums: [],
},
};
if (result <= chance) {
item.p = "shiny";
player.p.u_item = item;
} else {
player.p.u_item = item;
player.p.u_fail = true;
}
} else {
var ms = 1000;
if (data.calculate) {
return success_response("upgrade_chance", {
calculate: true,
chance: 1,
offering: offering.name,
item: cache_item(item),
grace: item.grace || 0,
});
}
consume_one(player, data.offering_num);
item.grace = (item.grace || 0) + 0.5;
server_log("item.grace: " + item.grace);
player.p.u_type = "offering";
player.p.u_item = item;
player.p.u_roll = 0.999999999999999;
if (player.s.massproduction) {
ms /= 2;
delete player.s.massproduction;
ex = "+u+cid";
}
if (player.s.massproductionpp) {
ms /= 10;
delete player.s.massproductionpp;
ex = "+u+cid";
}
player.q.upgrade = { ms: ms, len: ms, num: data.item_num };
player.items[data.item_num] = {
name: "placeholder",
p: { chance: 1, name: item.name, level: item.level, scroll: null, offering: offering.name, nums: [] },
};
}
} else if (scroll_def.type == "uscroll") {
if (item.l) {
return socket.emit("game_response", { response: "item_locked", place: "upgrade", failed: true });
}
if (grade == 4) {
return socket.emit("game_response", {
response: "max_level",
level: item.level,
place: "upgrade",
failed: true,
});
}
if (!data.calculate) {
consume_one(player, data.scroll_num);
}
oprobability = probability = D.upgrades[item_def.igrade][new_level];
// grace=max(0,min(new_level+1, (item.grace||0) + min(3,player.p.ugrace[new_level]/3.0) +min(2,ugrace[new_level]/4.0) +item_def.igrace + player.p.ograce/2.0 )); - original [16/07/18]
grace = max(
0,
min(new_level + 1, (item.grace || 0) + min(3, player.p.ugrace[new_level] / 4.5) + item_def.igrace) +
min(6, S.ugrace[new_level] / 3.0) +
player.p.ograce / 3.2,
);
server_log(
"Grace num: " +
grace +
"\nItem: " +
(item.grace || 0) +
"\nPlayer: " +
min(3, player.p.ugrace[new_level] / 4.5) +
"\nDef: " +
item_def.igrace +
"\nOgrace:" +
player.p.ograce / 3.2 +
"\nS.ugrace: " +
min(6, S.ugrace[new_level] / 3.0),
);
grace = (probability * grace) / new_level + grace / 1000.0;
server_log("Grace-prob: " + grace);
result = Math.random();
server_log(result + " < " + probability);
// if(!data.calculate && item.name=="throwingstars" && scroll_def.grade==2 && item.level==4) item.p="superfast";
if (scroll_def.grade > grade && new_level <= 10) {
probability = probability * 1.2 + 0.01;
high = true;
if (!data.calculate) {
item.grace = (item.grace || 0) + 0.4;
}
}
if (offering) {
var increase = 0.4;
if (!data.calculate) {
consume_one(player, data.offering_num);
}
if (offering_def.grade > grade + 1) {
probability = probability * 1.7 + grace * 4;
high = true;
increase = 3;
} else if (offering_def.grade > grade) {
probability = probability * 1.5 + grace * 1.2;
high = true;
increase = 1;
} else if (offering_def.grade == grade) {
probability = probability * 1.4 + grace;
} else if (offering_def.grade == grade - 1) {
probability = probability * 1.15 + grace / 3.2;
increase = 0.2;
} else {
probability = probability * 1.08 + grace / 4;
increase = 0.1;
}
if (!data.calculate) {
item.grace = (item.grace || 0) + increase;
} // previously +1 [16/07/18]
} else {
grace = max(0, grace / 4.8 - 0.4 / ((new_level - 0.999) * (new_level - 0.999)));
probability += grace; // previously 12.0 // previously 9.0 [16/07/18]
}
if (!data.calculate && Math.random() < 0.025) {
// Bonus grace
item.grace = (item.grace || 0) + 1;
}
if (data.item_num == player.p.item_num && Math.random() < 0.6) {
// Added [29/10/17]
server_log("16 cheat");
result = max(Math.random() / 10000.0, result * 0.975 - 0.012);
}
if (high) {
probability = min(probability, min(oprobability + 0.36, oprobability * 3));
} else {
probability = min(probability, min(oprobability + 0.24, oprobability * 2));
}
server_log(result + " < " + probability + " grace: " + grace);
if (data.calculate) {
return success_response("upgrade_chance", {
calculate: true,
chance: probability,
offering: (offering && offering.name) || undefined,
item: cache_item(item),
grace: item.grace || 0,
scroll: scroll.name,
});
}
if (gameplay == "test") {
result = 0;
}
// result=probability;
player.p.u_type = "normal";
player.p.u_roll = result;
var ms = 500 * new_level * Math.sqrt(new_level) * tmult;
if (player.s.massproduction) {
ms /= 2;
delete player.s.massproduction;
ex = "+u+cid";
}
if (player.s.massproductionpp) {
ms /= 10;
delete player.s.massproductionpp;
ex = "+u+cid";
}
player.q.upgrade = { ms: ms, len: ms, num: data.item_num };
if (gameplay == "hardcore") {
player.q.upgrade.ms = player.q.upgrade.len = 500;
}
player.items[data.item_num] = {
name: "placeholder",
p: {
chance: probability,
name: item.name,
level: item.level,
scroll: scroll.name,
offering: offering && offering.name,
nums: [],
},
};
//result=probability+EPS;
if (result <= probability) {
// console.log("here");
player.p.ugrace[new_level] = S.ugrace[new_level] = 0;
if (offering) {
player.p.ograce *= 0.25;
} else {
player.p.ograce *= 1 - new_level * 0.005;
}
item.level = new_level;
if (item.oo != player.name) {
item.o = player.name;
}
player.p.u_item = item;
if (parseInt(result * 10000) == parseInt(probability * 10000) && grade >= 1) {
add_item_property(item, "lucky");
add_achievement(player, "lucky");
}
} else {
player.p.ugrace[new_level - 1] += 1;
S.ugrace[new_level - 1] += 1;
player.p.ugrace[new_level] += 1;
S.ugrace[new_level] += 1;
if (new_level >= 8 && new_level <= 15) {
player.p.ugrace[new_level - 1] += 1;
S.ugrace[new_level - 1] += 1;
player.p.ugrace[new_level - 2] += 2 + ((offering && 1) || 0);
S.ugrace[new_level - 2] += 2;
player.p.ugrace[new_level - 3] += 2 + ((offering && 2) || 0);
S.ugrace[new_level - 3] += 3 + ((offering && 1) || 0);
}
if (offering) {
player.p.ograce += 0.6;
} // previously 1 [16/07/18]
if (scroll_def.grade != 3.6) {
player.p.u_itemx = item;
} else {
player.p.u_item = item;
player.p.u_fail = true;
}
if (parseInt(result * 10000) == parseInt(probability * 10000)) {
if (player.esize && grade >= 1) {
add_item(player, "essenceofgreed");
}
add_achievement(player, "unlucky");
}
}
} else if (scroll_def.type == "pscroll") {
var needed = [1, 10, 100, 1000, 9999, 9999, 9999];
if (scroll.q < needed[grade]) {
return socket.emit("game_response", { response: "upgrade_scroll_q", q: needed[grade], h: scroll.q });
}
if (!data.calculate) {
consume(player, data.scroll_num, needed[grade]);
}
if (data.calculate) {
return success_response("upgrade_chance", {
calculate: true,
chance: 0.99999,
scroll: scroll.name,
item: cache_item(item),
grace: item.grace || 0,
});
}
if (offering) {
consume_one(player, data.offering_num);
item.grace = (item.grace || 0) + 1;
server_log("Graced up to " + item.grace);
}
item.stat_type = scroll_def.stat;
player.p.u_roll = Math.random();
player.p.u_type = "stat";
if (player.p.u_roll <= 0.99999 || offering) {
player.p.u_item = item;
} else {
player.p.u_itemx = item;
}
var ms = 2000 * tmult * tmult;
if (player.s.massproduction) {
ms /= 2;
delete player.s.massproduction;
ex = "+u+cid";
}
if (player.s.massproductionpp) {
ms /= 10;
delete player.s.massproductionpp;
ex = "+u+cid";
}
player.q.upgrade = { ms: ms, len: ms, num: data.item_num };
player.items[data.item_num] = {
name: "placeholder",
p: {
chance: 0.99999,
name: item.name,
level: item.level,
scroll: scroll.name,
offering: offering && offering.name,
nums: [],
},
};
}
player.citems[data.item_num] = cache_item(player.items[data.item_num]);
resend(player, "reopen+nc+inv" + ex);
} catch (e) {
server_log("upgrade_e " + e);
return socket.emit("game_response", { response: "exception", place: "upgrade", failed: true });
}
});
socket.on("equip_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 && mssince(player.last.potion) < 0) {
return fail_response("not_ready");
}
if (item.l) {
return fail_response("item_locked");
}
var timeout = 2000;
var timeout_ui = null;
var xp = false;
consume_one(player, data.num);
(def.gives || []).forEach(function (p) {
var amount = p[1];
if (player.s.poisoned) {
amount = round(amount / 2);
}
player[p[0]] = (player[p[0]] || 0) + amount;
player[p[0]] = max(1, player[p[0]]);
if (p[0] == "hp") {
if (amount > 0) {
disappearing_text(socket, player, "+" + amount, { color: "hp", xy: 1, s: "hp" });
} else {
disappearing_text(socket, player, amount, { color: "hp", xy: 1, s: "hp" });
}
}
if (p[0] == "mp") {
disappearing_text(socket, player, "+" + amount, { color: "mp", xy: 1, s: "mp" });
}
if (p[0] == "xp") {
disappearing_text(socket, player, "+1M", { color: "1mxp", xy: 1, s: "xp", size: "large" });
xp = true;
timeout_ui = 120;
timeout = 0.1;
}
});
if (def.debuff) {
for (var c in player.s) {
if (G.conditions[c] && G.conditions[c].debuff && (!G.conditions[c].persistent || c == "hopsickness")) {
delete player.s[c];
}
}
}
player.hp = min(player.hp, player.max_hp);
player.mp = min(player.mp, player.max_mp);
if (def.cooldown) {
timeout = def.cooldown;
}
player.last.potion = future_ms(timeout);
if (!xp) {
to_update = "u+cid+reopen+nc";
}
socket.emit("eval", { code: "pot_timeout(" + (timeout_ui || timeout) + ")" });
} else if (!data.consume) {
// console.log(data);
var slot = data.slot || def.type;
var existing;
var comp = can_equip_item(player, def, slot);
if (comp == "no") {
resend(player, "u+cid+reopen"); // if you drop something on an invalid slot, it stays there for a bit otherwise [29/08/22]
return fail_response("cant_equip");
}
slot = comp;
existing = player.slots[slot];
player.slots[slot] = player.items[data.num];
player.cslots[slot] = cache_item(player.slots[slot]);
if (existing && existing.b) {
existing = null;
}
player.items[data.num] = existing;
player.citems[data.num] = cache_item(existing);
player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 120, 120000) };
resolve.slot = slot;
} else {
return fail_response("cant_consume");
}
if (to_update) {
resend(player, to_update);
}
success_response(resolve_type, resolve);
});
socket.on("misc_npc", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
var npc = players[data.npc];
if (!npc || !npc.npc) {
return;
}
if (simple_distance(player, npc) > 1000) {
return socket.emit("game_response", "distance");
}
if (data.npc == NPC_prefix + "Marven" && npc.misc == true) {
server_log("Here!");
}
});
socket.on("unequip", function (data) {
var player = players[socket.id];
if (!player || !data.slot || !player.slots[data.slot]) {
return fail_response("invalid");
}
if (data.slot == "elixir") {
return fail_response("cant");
}
var item = player.slots[data.slot];
var done = false;
if (in_arr(data.slot, trade_slots) && item.giveaway !== undefined) {
return fail_response("giveaway");
}
// an oversight allowed .giveaway items to be equipped, so now they can be unequipped from regular slots [04/11/21]
if (player.esize <= 0 && !item.b) {
return fail_response("no_space");
}
player.slots[data.slot] = null;
player.cslots[data.slot] = null;
player.c = {};
if (!item.b) {
add_item(player, item, { announce: false });
}
resend(player, "reopen+u+cid");
success_response("data");
});
socket.on("secondhands", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (simple_distance(G.maps.main.ref.secondhands, player) > 500) {
return socket.emit("game_response", "distance");
}
socket.emit("secondhands", csold);
});
socket.on("lostandfound", function (data) {
var player = players[socket.id];
if (!player) {
return;
}
if (data == "info") {
return socket.emit("game_response", { response: "lostandfound_info", gold: S.gold });
}
if (!player.donation) {
return socket.emit("game_response", "lostandfound_donate");
}
if (simple_distance(G.maps.woffice.ref.lostandfound, player) > 500) {
return socket.emit("game_response", "distance");
}
socket.emit("lostandfound", cfound);
});
socket.on("split", function (data) {
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("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) {
if (item.name == "shadowstone") {
add = "+u+cid";
player.s.invis = { ms: 99999 };
}
if (G.items[item.name].upgrade && Math.random() < 1.0 / ((gameplay == "hardcore" && 10000) || 1000000)) {
add = "+u+cid";
item.level = 13;
player.items[data.num] = item;
player.citems[data.num] = cache_item(player.items[data.num]);
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);
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);
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"
) {
consume_mp(player, gSkill.mp);
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);
player.s.phasedout = { ms: G.conditions.phasedout.duration };
} else if (data.name == "pcoat") {
consume_mp(player, gSkill.mp);
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);
}
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);
} 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);
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);
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);
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);
}
// 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);
}
// 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);
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.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.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 });
}
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 (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("<pre>"+data.output+"</pre>")',
});
}
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;
}
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) {
setTimeout(
new_monster_f(target.oin, target.map_def, { before_respawn: target }),
round(G.monsters[target.type].respawn * (720 + Math.random() * 480)),
);
} else {
setTimeout(
new_monster_f(target.oin, target.map_def, { before_respawn: target }),
round(G.monsters[target.type].respawn * 1000 + Math.random() * 900),
);
} // previously Math.random()*2000
}
var luckm = undefined;
if (target.target) {
var player = players[name_to_id[target.target]];
if (player) {
luckm = player.luckm;
if (!args.no_decrease) {
reduce_targets(player, target);
}
}
}
if (!args.silent) {
xy_emit(target, args.method, {
id: target.id,
luckm: luckm || 1,
points: (target.cooperative && target.points) || undefined,
});
}
delete instances[target.in].monsters[target.id];
if (!args.nospawn) {
target.map_def.live--;
monster_c[target.type]--;
}
}
function new_monster(instance, map_def, args) {
if (!instances[instance]) {
return;
} //otherwise late dungeon spawns bring down the server
if (!args) {
args = {};
}
var monster = {};
var id = ++total_monsters + "";
var name = map_def.type;
monster.id = id;
monster.cid = 1;
monster.mult = 1; // gets reduced during a server restart
monster.m = 0;
monster.outgoing = 0;
monster.u = true;
monster.type = name;
if (args.before_respawn && G.monsters[args.before_respawn.type] && G.monsters[args.before_respawn.type].respawn_as) {
name = monster.type = G.monsters[args.before_respawn.type].respawn_as;
(G.maps[instances[instance].map].monsters || []).forEach(function (pack) {
if (pack.type == name) {
map_def.gold = pack.gold;
}
});
}
var monster_def = G.monsters[monster.type];
monster.s = {};
monster.a = {};
monster.last = { attacked: new Date() };
monster.level = 1;
monster.luckx = 1;
monster.xrange = 0;
if (map_def.stype == "randomrespawn") {
var boundary = map_def.boundaries[parseInt(floor(Math.random() * map_def.boundaries.length))];
if (!server.live) {
boundary = map_def.boundaries[0];
} // to prevent referencing un-created instances
instance = boundary[0];
map_def.boundary = [boundary[1], boundary[2], boundary[3], boundary[4]];
}
monster.gold = map_def.gold;
if (G.dimensions[name]) {
monster.width = G.dimensions[name][0];
monster.height = G.dimensions[name][1];
}
set_base(monster);
if (map_def.random) {
var spot = random_place(instances[instance].map);
monster.x = spot.x;
monster.y = spot.y;
} else if (map_def.stype == "pet") {
monster.pet = true;
monster.x = map_def.x;
monster.y = map_def.y;
monster.owner = map_def.owner;
monster.name = map_def.name;
} else if (map_def.stype == "trap") {
monster.trap = true;
monster.x = map_def.x;
monster.y = map_def.y;
monster.owner = map_def.owner;
} else if (map_def.stype == "spawn") {
monster.spawn = true;
monster.x = map_def.x;
monster.y = map_def.y;
monster.master = map_def.master;
} else if (map_def.polygon) {
var p = random_point(map_def.polygon, monster.base);
var times = 0;
while (!is_xy_safe(instances[instance].map, p[0], p[1])) {
p = random_point(map_def.polygon, monster.base);
times++;
if (times > 40) {
break;
}
}
monster.x = p[0];
monster.y = p[1];
} else if (map_def.position) {
monster.x = map_def.position[0] + Math.random() * (2 * map_def.radius) - map_def.radius;
monster.y = map_def.position[1] + Math.random() * (2 * map_def.radius) - map_def.radius;
} else if (map_def.boundary) {
monster.x = map_def.boundary[0] + Math.random() * (map_def.boundary[2] - map_def.boundary[0]);
monster.y = map_def.boundary[1] + Math.random() * (map_def.boundary[3] - map_def.boundary[1]);
}
[
"speed",
"xp",
"hp",
"attack",
"range",
"frequency",
"damage_type",
"aggro",
"evasion",
"avoidance",
"reflection",
"armor",
"resistance",
"dreturn",
"rage",
"apiercing",
"rpiercing",
"immune",
"cooperative",
"peaceful",
"drop_on_hit",
"global",
"1hp",
"heal",
"spawns",
"lifesteal",
"manasteal",
"rbuff",
"cbuff",
"projectile",
"slots",
"crit",
"humanoid",
"explosion",
"for",
"difficulty",
"phresistance",
].forEach(function (prop) {
if (prop in monster_def) {
monster[prop] = monster_def[prop];
}
});
monster.mp = ceil((monster.hp * 2) / 100);
if (monster_def.s) {
monster.s = clone(monster_def.s);
}
if (monster_def.abilities) {
monster.a = monster_def.abilities;
}
if (monster_def.poisonous) {
monster.s.poisonous = { ms: 99999999999999 };
}
if (mode.range_test) {
monster.range = 2000;
}
monster.width = monster.height = 24;
if (args.temp) {
monster.temp = 1;
}
if (map_def.rage) {
monster.rid = map_def.id;
}
monster.points = {};
for (var a in monster.a) {
if (monster.a[a].cooldown) {
monster.s[a] = { ms: parseInt(monster.a[a].cooldown * Math.random()), ability: true };
}
}
monster.s.young = { ms: 500 };
monster.is_monster = true;
monster.max_hp = monster_def.hp;
monster.oin = instance;
monster.in = instance;
monster.map = instances[instance].map;
monster.map_def = map_def;
monster.last_level = future_s(Math.random() * 100 - 50);
if (mode.fast_mlevels) {
monster.last_level = new Date();
}
monster.last.attack = new Date();
monster.last.attacked = really_old;
monster.hits = 0;
if (args.last_state) {
while (monster.level < args.last_state.level) {
level_monster(monster, { silent: true });
}
["hp", "level", "s", "temp", "points", "m", "extra_gold", "outgoing", "id"].forEach(function (p) {
if (args.last_state[p]) {
monster[p] = args.last_state[p];
}
});
calculate_monster_stats(monster);
}
if (args.before_respawn) {
while (monster.level < args.before_respawn.level / 2) {
level_monster(monster, { silent: true });
}
calculate_monster_stats(monster);
}
if (monster.aggro) {
monster.last_aggro = new Date();
}
instances[instance].monsters[monster.id] = monster;
if (!args.last_state) {
map_def.live = (map_def.live || 0) + 1;
monster_def.c = (monster_def.c || 0) + 1;
monster_c[monster.type] = (monster_c[monster.type] || 0) + 1;
}
if (map_def.grow && server.live && map_def.live <= map_def.count / 2) {
new_monster(instance, map_def, { temp: 1 });
new_monster(instance, map_def, { temp: 1 });
}
if (!args.last_state && monster_def.announce && server.live) {
broadcast("server_message", {
color: monster_def.announce,
message: monster_def.name + " spawned in " + G.maps[monster.map].name + "!",
discord: "orange",
});
if (monster.cooperative) {
broadcast("server_message", {
color: monster_def.announce,
message: "Join the fight against " + monster_def.name + "!",
event: true,
});
}
}
if (Object.keys(monster.s).length) {
calculate_monster_stats(monster);
}
if (map_def.stype == "spawn") {
target_player(monster, get_player(map_def.target));
}
return monster;
}
function new_monster_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(function (spi) {
var interval = spi[0];
var name = spi[1];
if (!monster.last[name] || mssince(monster.last[name]) > interval) {
var pname = random_one(Object.keys(monster.points));
var player = get_player(pname);
if (!player || player.npc || distance(monster, player) > 400) {
return;
}
if (!is_same(player, get_player(monster.target), true)) {
return;
}
monster.last[name] = new Date();
var spot = safe_xy_nearby(player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10);
if (!spot) {
return;
}
new_monster(instance.name, {
type: name,
stype: "spawn",
x: spot.x,
y: spot.y,
target: player.name,
master: monster.id,
});
}
});
}
function attack_target_or_move() {
var player = players[name_to_id[monster.target]];
if (player && ssince(monster.last.attacked) > 20 && Math.random() > monster.rage * 0.99) {
stop_pursuit(monster, { force: true, cause: "bored" });
return;
}
if (focus && distance(focus, monster) > 40 && !monster.moving) {
if (mode.all_smart) {
if (!monster.worker) {
monster.working = true;
workers[wlast++ % workers.length].postMessage({
type: "fast_astar",
in: monster.in,
id: monster.id,
map: monster.map,
sx: monster.x,
sy: monster.y,
tx: focus.x,
ty: focus.y,
});
}
} else {
monster.ogoing_x = monster.going_x;
monster.ogoing_y = monster.going_y;
monster.going_x = monster.x + (focus.x - monster.x) / 2;
monster.going_y = monster.y + (focus.y - monster.y) / 2;
if (mode.path_checks && !can_move(monster)) {
monster.going_x = monster.ogoing_x;
monster.going_y = monster.ogoing_y;
} else {
start_moving_element(monster);
}
}
}
if (player && player.in == monster.in && !player.rip && !is_invis(player)) {
if (
distance(player, monster, true) >
min(monster.range / 1.6, 240) + min(100, monster.attack / 5.0) + 160 + ((mode.all_smart && 320) || 1) &&
!monster.walk_once
) {
stop_pursuit(monster, { cause: "exceeds_range" });
} else if (can_attack(monster, player)) {
var attack = commence_attack(monster, player, "attack");
if (attack && attack.events && attack.events.length) {
events.push(...attack.events);
}
} else if (
distance(monster, player, true) > 12 &&
!mode.range_test &&
!(mode.all_smart && monster.moving) &&
!focus
) {
// console.log(monster.height);
if (mode.all_smart) {
if (!monster.worker) {
monster.working = true;
workers[wlast++ % workers.length].postMessage({
type: "fast_astar",
in: monster.in,
id: monster.id,
map: monster.map,
sx: monster.x,
sy: monster.y,
tx: player.x,
ty: player.y,
});
}
} else {
monster.ogoing_x = monster.going_x;
monster.ogoing_y = monster.going_y;
monster.going_x = monster.x + (player.x - monster.x) / 2;
monster.going_y = monster.y + (player.y - monster.y) / 2;
if (mode.path_checks && !can_move(monster)) {
monster.going_x = monster.ogoing_x;
monster.going_y = monster.ogoing_y;
if (monster.attack < 120 || distance(monster, player, true) > monster.range) {
stop_pursuit(monster, { cause: "cant_move" });
}
} else {
// console.log("Moving to "+monster.going_x+" "+monster.going_y);
start_moving_element(monster);
}
}
}
} else if (monster.target) {
stop_pursuit(monster, { cause: "player_gone" });
}
if (monster.walk_once) {
monster.walk_once = false;
}
}
if (monster.moving) {
var ms = mssince(monster.last.move);
ms = min(ms, 2000); // to prevent monsters from jumping off the map when the machine sleeps
monster.x += (monster.vx * ms) / 1000.0;
monster.y += (monster.vy * ms) / 1000.0;
stop_logic(monster);
monster.last.move = new Date();
xy_u_logic(monster);
if (monster.moving && monster.attack > 100 && monster.target) {
attack_target_or_move();
}
} else if (monster.s.sleeping || monster.working) {
} else if (can_walk(monster)) {
// for the .s.stunned check
if (monster.s.magiport) {
} else if (monster.target || focus) {
attack_target_or_move();
} else if (!mode.upush_test) {
if (monster.map_def.position && !mode.all_roam) {
var position = monster.map_def.position;
var radius = monster.map_def.radius;
var dx = Math.random() * radius - radius / 2;
var dy = Math.random() * radius - radius / 2;
if (abs(dx) + abs(dy) < 80) {
// optimization to prevent short walks
if (dx < 0 && dx > -60) {
dx = -60;
}
if (dx > 0 && dx < 60) {
dx = 60;
}
if (dy < 0 && dy > -60) {
dy = -60;
}
if (dy > 0 && dy < 60) {
dy = 60;
}
}
monster.going_x = monster.x + dx;
monster.going_y = monster.y + dy;
if (monster.going_x > position[0] + radius) {
monster.going_x = position[0] + radius;
}
if (monster.going_y > position[1] + radius) {
monster.going_y = position[1] + radius;
}
if (monster.going_x < position[0] - radius) {
monster.going_x = position[0] - radius;
}
if (monster.going_y < position[1] - radius) {
monster.going_y = position[1] - radius;
}
start_moving_element(monster);
} else if (
monster.map_def.roam ||
G.monsters[monster.type].roam ||
mode.all_roam ||
(monster.map_def.polygon && !monster.irregular)
) {
perfc.roams += 1;
var map_def = monster.map_def;
var tries = 1;
if (!monster.map_def.roam && !mode.all_roam && monster.map_def.polygon) {
tries = 12;
}
if (!monster.rmove) {
monster.rmove = parseInt(Math.random() * 100);
}
monster.dmove = monster.dmove || 0;
for (var t = 0; t < tries; t++) {
var moves = [
[1, 0],
[0.8, 0.8],
[0, 1],
[-0.8, 0.8],
[-1, 0],
[-0.8, -0.8],
[0, -1],
[0.8, -0.8],
];
var multipliers = [500, 200, 100, 50, 10];
if (Math.random() < 0.1) {
monster.rmove = parseInt(Math.random() * moves.length);
}
var move = moves[monster.rmove % moves.length];
var d = multipliers[monster.dmove % multipliers.length];
monster.going_x = monster.x + move[0] * d;
monster.going_y = monster.y + move[1] * d;
if (tries > 1 && monster.map_def.polygon) {
if (is_point_inside([monster.going_x, monster.going_y], monster.map_def.polygon)) {
break;
} else {
monster.rmove++;
monster.dmove++;
}
}
}
// console.log(monster.rmove+","+monster.dmove+" "+monster.going_x+","+monster.going_y);
if (can_move(monster)) {
start_moving_element(monster);
monster.dmove = 0;
} else {
monster.rmove++;
monster.dmove++;
}
} else if (
monster.map_def.stype != "pet" &&
monster.map_def.stype != "spawn" &&
monster.map_def.stype != "trap"
) {
var map_def = monster.map_def;
var to_move = true;
if (map_def.polygon) {
var p = random_point(map_def.polygon, monster.base);
monster.going_x = p[0];
monster.going_y = p[1];
} else {
monster.going_x = map_def.boundary[0] + Math.random() * (map_def.boundary[2] - map_def.boundary[0]);
monster.going_y = map_def.boundary[1] + Math.random() * (map_def.boundary[3] - map_def.boundary[1]);
}
if (monster.irregular == 3) {
// new [01/03/19]
monster.m++;
setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500);
remove_monster(monster, { nospawn: true, method: "disappear" });
} else if (monster.irregular == 2) {
server_log("Irregular2 move for " + monster.id);
if (monster.in != monster.oin) {
port_monster(monster, { map: monster.oin, x: monster.going_x, y: monster.going_y, in: monster.oin });
} else {
recalculate_move(monster);
}
monster.irregular = 1;
} else if (monster.irregular == 1) {
if (!can_move(monster)) {
monster.m++;
server_log("Irregular1 respawn: " + monster.id);
setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500);
remove_monster(monster, { nospawn: true, method: "disappear" });
} else {
server_log("No longer irregular: " + monster.id);
delete monster["irregular"];
}
}
if (monster.going_x != monster.x || monster.going_y != monster.y) {
// so Automatron's don't move
start_moving_element(monster);
}
}
}
}
if (!monster.dead && (monster.u || events.length)) {
monster.u = false;
to_push.push({ id: id, entity: monster, data: monster_to_client(monster, events) });
monster_map[id] = to_push[to_push.length - 1];
}
}
for (var id in instance.players) {
var player = instance.players[id];
if (!player) {
continue;
}
for (var name in player.s) {
var def = G.conditions[name];
var ref = player.s[name];
var value = player.s[name].ms;
player.s[name].ms -= ms;
if (def && def.interval) {
if (!player.s[name].last || mssince(player.s[name].last) >= def.interval) {
player.s[name].last = new Date();
if (name == "eburn") {
disappearing_text(player.socket, player, "-50", { color: "red", xy: 1 });
player.hp = max(1, player.hp - 50);
}
if (name == "eheal") {
disappearing_text(player.socket, player, "+50", { color: "heal", xy: 1 });
player.hp = min(player.max_hp, player.hp + 50);
}
if (name == "burned") {
var damage = ceil(ref.intensity / 5);
disappearing_text(player.socket, player, "-" + damage, { color: "red", xy: 1 });
player.hp = max(0, player.hp - damage);
xy_emit(player, "hit", {
source: "burn",
hid: ref.fid,
id: player.name,
damage: damage,
kill: player.hp <= 0,
});
player_rip_logic(player);
}
resend(player, "u+cid+nc");
}
}
if (in_arr(name, ["damage_received"])) {
player.s[name].amount = max(0, (player.s[name].amount * (4000 - ms)) / 4000.0);
}
if (player.s[name].ms <= 0) {
delete player.s[name];
if (name == "blink") {
if (player.s.dampened) {
xy_emit(player, "ui", { type: "dampened", name: player.name });
} else {
decay_s(player, 30000);
transport_player_to(player, ref.in, [ref.x, ref.y, ref.d], "blink");
}
}
if (name == "magiport") {
var dampened = false;
for (var id in instances[ref.in].monsters) {
var m = instances[ref.in].monsters[id];
if (m.type == "fieldgen0" && point_distance(ref.x, ref.y, m.x, m.y) < 300) {
xy_emit(player, "ui", { type: "dampened", name: player.name });
dampened = true;
break;
}
}
if (!dampened) {
var skip = false;
if (ref.map != player.map) {
skip = true;
}
decay_s(player, 30000);
transport_player_to(player, ref.in, [ref.x, ref.y], "magiport");
if (skip) {
resend(player, "u+cid");
return; // player isn't in this instance any more
}
}
}
if (def) {
player.socket.emit("game_response", { response: "ex_condition", name: name });
}
if (def && def.ui) {
resend(player, "u+cid");
} // +cid to update the UI's [23/10/18]
else {
resend(player, "u");
} // #TODO: don't need to resend actually, maybe reconsider [27/06/18]
}
}
for (var name in player.q) {
var value = player.q[name].ms;
var ref = player.q[name];
player.q[name].ms -= ms;
if (name == "upgrade") {
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
var def = player.items[ref.num].p;
var change = false;
if (value < ref.len * 0.8 && def.nums[0] === undefined) {
def.nums[0] = parseInt(player.p.u_roll * 10000) % 10;
change = true;
}
if (value < ref.len * 0.64 && def.nums[1] === undefined) {
def.nums[1] = parseInt(player.p.u_roll * 1000) % 10;
change = true;
}
if (value < ref.len * 0.4 && def.nums[2] === undefined) {
def.nums[2] = parseInt(player.p.u_roll * 100) % 10;
change = true;
}
if (value < min(3000, ref.len * 0.3) && def.nums[3] === undefined) {
def.nums[3] = parseInt(player.p.u_roll * 10);
change = true;
}
if (value < min(2200, ref.len * 0.22) && player.p.u_item && !player.p.u_fail && !def.success) {
def.success = true;
change = true;
}
if (value < min(2200, ref.len * 0.22) && (player.p.u_itemx || player.p.u_fail) && !def.failure) {
def.failure = true;
change = true;
}
if (change) {
player.socket.emit("q_data", { q: player.q, num: ref.num, p: def });
}
}
}
if (name == "compound") {
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
var def = player.items[ref.num].p;
var change = false;
if (value < 8000 && def.nums[0] === undefined) {
def.nums[0] = parseInt(player.p.c_roll * 10000) % 10;
change = true;
}
if (value < 6400 && def.nums[1] === undefined) {
def.nums[1] = parseInt(player.p.c_roll * 1000) % 10;
change = true;
}
if (value < 5000 && def.nums[2] === undefined) {
def.nums[2] = parseInt(player.p.c_roll * 100) % 10;
change = true;
}
if (value < 3000 && def.nums[3] === undefined) {
def.nums[3] = parseInt(player.p.c_roll * 10);
change = true;
}
if (value < 2200 && player.p.c_item && !def.success) {
def.success = true;
change = true;
}
if (value < 2200 && player.p.c_itemx && !def.failure) {
def.failure = true;
change = true;
}
if (change) {
player.socket.emit("q_data", { q: player.q, num: ref.num, p: def });
}
}
}
if (player.q[name].ms <= 0) {
delete player.q[name];
if (name == "exchange") {
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
player.citems[ref.num] = player.items[ref.num] = null;
}
exchange(player, ref.id, { v: ref.v });
if (ref.qs) {
player.socket.emit("game_response", { response: ref.qs + "_success", suffix: ref.s || "" });
} else {
xy_emit(G.maps.main.exchange, "upgrade", { type: "exchange", success: 1 });
}
resend(player, "reopen+u+cid");
}
if (name == "compound") {
if (player.p.c_item) {
var item = player.p.c_item;
var def = G.items[item.name];
player.hitchhikers.push([
"game_response",
{
response: "compound_success",
stale: ref.stale,
level: item.level,
num: data.num,
up: item.extra || undefined,
},
]);
if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 1800000 && !player.stealth) {
broadcast("server_message", {
message: player.name + " received " + item_to_phrase(item),
color: colors.server_success,
item: cache_item(item),
type: "server_csuccess",
name: player.name,
});
}
xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 1 });
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
player.items[ref.num] = item;
player.citems[ref.num] = cache_item(player.items[ref.num]);
}
achievement_logic_compound_success(player, item);
} else {
var item = player.p.c_itemx;
var def = G.items[item.name];
player.hitchhikers.push([
"game_response",
{ response: "compound_fail", level: item.level, num: data.num, stale: ref.stale },
]);
if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 920000 && !player.stealth) {
broadcast("server_message", {
message: player.name + " lost " + item_to_phrase(item) + "'s",
color: colors.server_failure,
item: cache_item(item),
type: "server_cfail",
name: player.name,
});
}
xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 0 });
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
player.citems[ref.num] = player.items[ref.num] = null;
player.esize++;
}
}
delete player.p.c_item;
delete player.p.c_itemx;
delete player.p.c_roll;
resend(player, "reopen+u+cid+nc+inv");
}
if (name == "upgrade") {
var success = false;
var announce = false;
var new_level = player.p.u_level + 1;
var item = player.p.u_item || player.p.u_itemx;
var p = player.items[ref.num] && player.items[ref.num].p;
if (player.p.u_item && player.items[ref.num] && player.items[ref.num].name == "placeholder") {
success = true;
player.items[ref.num] = player.p.u_item;
player.citems[ref.num] = cache_item(player.items[ref.num]);
} else if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
player.citems[ref.num] = player.items[ref.num] = null;
player.esize++;
}
if (player.p.u_type == "offering") {
if (success) {
player.hitchhikers.push(["game_response", { response: "upgrade_offering_success", stale: ref.stale }]);
}
} else if (player.p.u_type == "stat") {
if (success) {
player.hitchhikers.push([
"game_response",
{
response: "upgrade_success_stat",
stale: ref.stale,
stat_type: p && G.items[p.scroll].stat,
num: ref.num,
},
]);
}
} else if (!ref.silent) {
announce = true;
}
if (success && !player.p.u_fail) {
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) {
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 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 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); });