mirror of
https://github.com/gooba42/adventureland.git
synced 2026-10-14 10:33:47 -05:00
14025 lines
411 KiB
JavaScript
14025 lines
411 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
|
|
fear_affects_heal: 0, // when feared heal output is lowered
|
|
};
|
|
var events = {
|
|
// SEASONS
|
|
holidayseason: false,
|
|
lunarnewyear: false,
|
|
valentines: false,
|
|
pinkgoo: 0, // every N minutes - 60
|
|
snowman: 20 * 60, // 1200 normally - 60 - at sprocess_game_data
|
|
egghunt: 0, // every N minutes - 60
|
|
halloween: true,
|
|
// RANDOM
|
|
goblin: false,
|
|
goldenbat: 160000,
|
|
cutebee: 960000,
|
|
goldenbot: 200000,
|
|
hide_and_seek: 0,
|
|
// DAILIES
|
|
goobrawl: false,
|
|
crabxx: false,
|
|
abtesting: false,
|
|
// NIGHTLIES
|
|
icegolem: false,
|
|
franky: false,
|
|
};
|
|
var dailies = ["crabxx", "goobrawl", "abtesting"];
|
|
shuffle(dailies);
|
|
var nightlies = ["icegolem", "franky"];
|
|
shuffle(nightlies);
|
|
if (events.holidayseason) {
|
|
events.snowman = 60;
|
|
}
|
|
if (events.valentines) {
|
|
events.pinkgoo = 60;
|
|
}
|
|
TIMEO = {
|
|
EU: 1,
|
|
US: -5,
|
|
ASIA: 7,
|
|
};
|
|
var perfc = {
|
|
game_loop: {},
|
|
instance_loop: {},
|
|
instance_delay: {},
|
|
cps: 0,
|
|
sxyu: 0,
|
|
game_loops: 0,
|
|
instance_loops: 0,
|
|
roams: 0,
|
|
roam_ops: 0,
|
|
};
|
|
var instances = {};
|
|
// at first there were no instances, monsters were global
|
|
// instances improve performance and allow things like dynamic events, dungeons
|
|
|
|
var luckm = 1;
|
|
var xpm = 1;
|
|
var goldm = 1;
|
|
var buym = 1;
|
|
|
|
region = process.argv[process.argv.length - 3];
|
|
server_name = process.argv[process.argv.length - 2];
|
|
port = process.argv[process.argv.length - 1];
|
|
|
|
E.schedule.time_offset = TIMEO[region];
|
|
|
|
if (server_name == "HARDCORE") {
|
|
gameplay = "hardcore";
|
|
xpm = 2188;
|
|
// luckm=500;
|
|
goldm = 12000;
|
|
is_pvp = true;
|
|
variables.ip_limit = 1;
|
|
variables.character_limit = 1;
|
|
B.rip_time = -1;
|
|
B.heal_multiplier = 0.6;
|
|
B.sell_dist = 9999999 + 1000;
|
|
events.golden_bat = 8000;
|
|
B.arena_limit = 1;
|
|
B.v = false;
|
|
B.free_last_hits = true;
|
|
mode.prevent_external = 1;
|
|
mode.pvp_level_gap = 1;
|
|
} else if (server_name == "TEST") {
|
|
gameplay = "test";
|
|
is_pvp = true;
|
|
variables.ip_limit = 5;
|
|
variables.character_limit = 5;
|
|
B.rip_time = -1;
|
|
B.sell_dist = 9999999 + 1000;
|
|
B.arena_limit = 1;
|
|
B.rbugs = 1;
|
|
mode.prevent_external = 1;
|
|
} else if (server_name == "DUNGEON") {
|
|
gameplay = "dungeon";
|
|
is_pvp = true;
|
|
variables.ip_limit = 1;
|
|
variables.character_limit = 1;
|
|
B.start_map = observer_map = "d_e";
|
|
B.free_last_hits = true;
|
|
B.pause_instances = false;
|
|
for (var name in events) {
|
|
if (is_number(events[name])) {
|
|
events[name] = (events[name] !== 0 && 9999999999) || 0;
|
|
} else {
|
|
events[name] = false;
|
|
}
|
|
}
|
|
} else {
|
|
gameplay = "normal";
|
|
}
|
|
|
|
// B.u_boundary=12; B.u_vision=12; B["vision"]=[320,270]
|
|
// B.vision[0]*=100; B.vision[1]*=100;
|
|
|
|
if (server_name.startsWith("PVP") || server_name.startsWith("HARDCORE")) {
|
|
is_pvp = true;
|
|
// variables.ip_limit=2+1;
|
|
// variables.character_limit=1+1;
|
|
luckm *= 1.15;
|
|
xpm *= 1.2;
|
|
goldm *= 1.25;
|
|
}
|
|
|
|
// var secure_app=require('https').createServer(s_options,https_handler); #GTODO: Implement secure communications at one point
|
|
|
|
function init_game() {
|
|
appengine_call(
|
|
"create_server",
|
|
{
|
|
keyword: variables.keyword,
|
|
port: port,
|
|
region: region,
|
|
name: server_name,
|
|
pvp: is_pvp || "",
|
|
gameplay: gameplay,
|
|
},
|
|
function (result) {
|
|
try {
|
|
if (result.exists) {
|
|
return [console.log("Server Exists!"), process.exit()];
|
|
}
|
|
// console.log(result);
|
|
server_id = result.id;
|
|
server_auth = result.auth;
|
|
server_name = result.name;
|
|
server_log("Server Live: " + server_name + " " + server_id, 1);
|
|
server_log("Node Version: " + process.version, 1);
|
|
//server_log("Socket.IO Version: "+require("socket.io").version);
|
|
S = result.data;
|
|
G = result.game;
|
|
server_log("Game Version: " + G.version, 1);
|
|
D = result.dynamics;
|
|
if (S) {
|
|
init_server_data(S);
|
|
}
|
|
if (G) {
|
|
sprocess_game_data();
|
|
}
|
|
try {
|
|
app.listen(port);
|
|
init_io();
|
|
// app2.listen(parseInt(port)+40);
|
|
} catch (e) {
|
|
server.exists = true;
|
|
server.started = true;
|
|
log_trace("port", e);
|
|
return shutdown_routine();
|
|
}
|
|
//create_instance("main2","main2");
|
|
var start = new Date();
|
|
if (gameplay == "normal" || gameplay == "hardcore" || gameplay == "test") {
|
|
create_instance("main");
|
|
create_instance("tunnel");
|
|
create_instance("cave");
|
|
create_instance("tavern");
|
|
create_instance("resort_e");
|
|
create_instance("resort");
|
|
create_instance("mansion");
|
|
create_instance("woffice");
|
|
create_instance("halloween");
|
|
create_instance("spookytown");
|
|
if (gameplay == "normal") {
|
|
create_instance("test");
|
|
}
|
|
create_instance("arena", "arena", { pvp: 1 });
|
|
create_instance("bank");
|
|
create_instance("bank_b");
|
|
create_instance("bank_u");
|
|
//create_instance("batcave","batcave");
|
|
create_instance("winterland");
|
|
create_instance("winter_inn");
|
|
create_instance("winter_inn_rooms");
|
|
create_instance("winter_cave");
|
|
create_instance("winter_cove");
|
|
create_instance("desertland");
|
|
create_instance("level1");
|
|
create_instance("level2");
|
|
create_instance("level2n");
|
|
create_instance("level2e");
|
|
create_instance("level2s");
|
|
create_instance("level2w");
|
|
create_instance("level3");
|
|
create_instance("level4");
|
|
create_instance("cyberland");
|
|
create_instance("hut");
|
|
create_instance("mtunnel");
|
|
create_instance("ship0");
|
|
create_instance("goobrawl");
|
|
create_instance("gateway");
|
|
create_instance("ucliffs");
|
|
create_instance("uhills");
|
|
create_instance("mforest");
|
|
server_bfs("crypt");
|
|
server_bfs("winter_instance");
|
|
server_bfs("spider_instance");
|
|
server_bfs("tomb");
|
|
server_bfs("dungeon0");
|
|
server_bfs("cgallery");
|
|
} else if (gameplay == "dungeon") {
|
|
for (var name in G.maps) {
|
|
if (G.maps[name].world == "dungeon") {
|
|
create_instance(name);
|
|
}
|
|
}
|
|
}
|
|
create_instance("jail");
|
|
console.log("Calculations took: " + mssince(start) + "ms");
|
|
shuffle(hiding_places);
|
|
init_tavern();
|
|
init_server();
|
|
server.started = true;
|
|
server.live = true;
|
|
server.last_update = new Date();
|
|
} catch (e) {
|
|
log_trace("init", e);
|
|
}
|
|
},
|
|
function () {
|
|
console.log("#X init_game's create_server call failed");
|
|
},
|
|
);
|
|
}
|
|
|
|
init_game();
|
|
|
|
function reload_server(to_broadcast, change) {
|
|
appengine_call(
|
|
"reload_server",
|
|
{ keyword: variables.keyword, port: port, region: region, pvp: is_pvp || "", gameplay: gameplay },
|
|
function (result) {
|
|
try {
|
|
G = result.game;
|
|
D = result.dynamics;
|
|
sprocess_game_data();
|
|
prop_cache = {};
|
|
if (to_broadcast) {
|
|
broadcast("reloaded", { change: change || "" });
|
|
}
|
|
} catch (e) {
|
|
broadcast("notice", { message: "Server Live Reload Failed" });
|
|
log_trace("#X live_reload", e);
|
|
}
|
|
},
|
|
function () {
|
|
// setTimeout(init_game,1000);
|
|
server_log("#X live_reload failed", 1);
|
|
broadcast("notice", { message: "Live Reload Failed" });
|
|
},
|
|
);
|
|
}
|
|
|
|
function decode_http_data(data) {
|
|
// #TODO: Find a better way to transfer data [28/09/17]
|
|
var result = decodeURIComponent(data)
|
|
.replace_all("u'", "'")
|
|
.replace_all("'", '"')
|
|
.replace_all("+", " ")
|
|
.replace_all("%2B", "+");
|
|
return result;
|
|
}
|
|
|
|
function parse_http_json(data) {
|
|
var result = decode_http_data(data);
|
|
var json = {};
|
|
try {
|
|
json = JSON.parse(result);
|
|
} catch (e) {
|
|
console.log("\n" + data);
|
|
log_trace("parse_http_json", e);
|
|
}
|
|
return json;
|
|
}
|
|
|
|
function http_handler(request, response) {
|
|
var body = [];
|
|
request
|
|
.on("error", function (err) {
|
|
server_log("http_err: " + err, 1);
|
|
})
|
|
.on("data", function (chunk) {
|
|
body.push(chunk);
|
|
})
|
|
.on("end", function () {
|
|
body = Buffer.concat(body).toString();
|
|
try {
|
|
// server_log("http_handle's end");
|
|
var url_parts = url.parse(request.url, true);
|
|
var args = url_parts.query;
|
|
var output = "";
|
|
//console.log(body);
|
|
(body || "").split("&").forEach(function (pv) {
|
|
var pvp = pv.split("=");
|
|
args[pvp[0]] = pvp[1];
|
|
});
|
|
if (args.checkin) {
|
|
// console.log(JSON.stringify(args));
|
|
// safe_search(request,"::1");
|
|
// console.log(JSON.stringify(request.headers));
|
|
console.log("start");
|
|
req = request;
|
|
console.log(req.connection ? req.connection.remoteAddress : null);
|
|
console.log(req.socket ? req.socket.remoteAddress : null);
|
|
console.log(req.connection && req.connection.socket ? req.connection.socket.remoteAddress : null);
|
|
console.log(req.info ? req.info.remoteAddress : null);
|
|
var ip = getClientIp(request);
|
|
var id = id_to_id[args.id];
|
|
// console.log(ip);
|
|
if (players[id] && players[id].ipass == args.ipass) {
|
|
players[id].last_ip = ip;
|
|
players[id].last_ipass = new Date();
|
|
// server_log("ipass for "+players[id].name);
|
|
}
|
|
}
|
|
if (args.spass != variables.access_master) {
|
|
response.writeHead(200);
|
|
response.end(output);
|
|
return;
|
|
}
|
|
if (args.aevent == "shutdown") {
|
|
shutdown_routine();
|
|
}
|
|
if (args.aevent == "cupdate") {
|
|
var id = id_to_id[args.id];
|
|
server_log("cupdate for " + args.id + " socket.id: " + id);
|
|
if (players[id]) {
|
|
var player = players[id];
|
|
player.cash = args.cash;
|
|
if (args.ncash && args.ncash != "0") {
|
|
player.socket.emit("game_log", { message: "Received " + args.ncash + " shells", color: colors.cash });
|
|
}
|
|
|
|
resend(player, "reopen+nc");
|
|
}
|
|
output = "yes";
|
|
}
|
|
if (args.aevent == "new_friend") {
|
|
var id = id_to_id[args.id];
|
|
server_log("new_friend for " + args.id + " socket.id: " + id, 1);
|
|
if (players[id]) {
|
|
var player = players[id];
|
|
player.friends = parse_http_json(args.friends);
|
|
player.socket.emit("friend", { event: "new", name: args.name, friends: player.friends });
|
|
resend(player, "redata");
|
|
}
|
|
output = "yes";
|
|
}
|
|
if (args.aevent == "lost_friend") {
|
|
var id = id_to_id[args.id];
|
|
server_log("lost_friend for " + args.id + " socket.id: " + id, 1);
|
|
if (players[id]) {
|
|
var player = players[id];
|
|
player.friends = parse_http_json(args.friends);
|
|
player.socket.emit("friend", { event: "lost", friends: player.friends }); // ,name:args.name
|
|
resend(player, "redata");
|
|
}
|
|
output = "yes";
|
|
}
|
|
if (args.aevent == "eval") {
|
|
var data = parse_http_json(args.data);
|
|
try {
|
|
eval(decode_http_data(args.code));
|
|
} catch (e) {
|
|
console.log("\n" + args.code);
|
|
log_trace("chttp_eval", e);
|
|
}
|
|
output = JSON.stringify(output);
|
|
}
|
|
response.writeHead(200);
|
|
response.end(output);
|
|
} catch (e) {
|
|
log_trace("chttp_err", e);
|
|
}
|
|
});
|
|
}
|
|
|
|
function player_to_server(player, place) {
|
|
var char = {};
|
|
for (prop in player) {
|
|
if (
|
|
!in_arr(prop, [
|
|
"auth",
|
|
"last_sync",
|
|
"socket",
|
|
"character",
|
|
"last_upush",
|
|
"push",
|
|
"last",
|
|
"last_u",
|
|
"width",
|
|
"height",
|
|
"u",
|
|
"user",
|
|
])
|
|
) {
|
|
char[prop] = player[prop];
|
|
}
|
|
}
|
|
if (place == "sync" && !Object.keys(player.q).length && player.type != "merchant") {
|
|
delete char.p;
|
|
} // ~20KB - too much [19/11/18]
|
|
return char;
|
|
}
|
|
|
|
function player_to_client(player, stranger) {
|
|
var data = {};
|
|
[
|
|
"hp",
|
|
"max_hp",
|
|
"mp",
|
|
"max_mp",
|
|
"xp",
|
|
"attack",
|
|
"heal",
|
|
"frequency",
|
|
"speed",
|
|
"range",
|
|
"armor",
|
|
"resistance",
|
|
"level",
|
|
"party",
|
|
"rip",
|
|
"npc",
|
|
"allow",
|
|
"code",
|
|
"afk",
|
|
"target",
|
|
"focus",
|
|
"role",
|
|
"s",
|
|
"c",
|
|
"q",
|
|
"b",
|
|
"age",
|
|
"pdps",
|
|
"id",
|
|
"x",
|
|
"y",
|
|
"moving",
|
|
"going_x",
|
|
"going_y",
|
|
"abs",
|
|
"move_num",
|
|
"angle",
|
|
"cid",
|
|
"guild",
|
|
"team",
|
|
].forEach(function (p) {
|
|
// removed "vx","vy"
|
|
if (player[p] !== undefined) {
|
|
data[p] = player[p];
|
|
}
|
|
});
|
|
["stand"].forEach(function (p) {
|
|
if (player.p && player.p[p] !== undefined) {
|
|
data[p] = player.p[p];
|
|
}
|
|
});
|
|
if (player.afk == "code") {
|
|
data.controller = player.controller;
|
|
}
|
|
if (player.rip && ((player.tcx && player.tcx.gravestone) || player.cx.gravestone)) {
|
|
player.rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone;
|
|
}
|
|
data.skin = player.tskin || player.skin;
|
|
data.cx = player.tcx || player.cx;
|
|
data.slots = player.cslots;
|
|
if (player.tp) {
|
|
data.tp = true;
|
|
}
|
|
data.ctype = player.type;
|
|
data.owner = (!player.private && player.owner) || "";
|
|
|
|
if (player.is_npc) {
|
|
// data.id="$"+data.id;
|
|
data.name = player.name;
|
|
if (player.direction !== undefined) {
|
|
data.direction = player.direction;
|
|
data.npc = player.npc;
|
|
}
|
|
}
|
|
|
|
if (!stranger) {
|
|
[
|
|
"int",
|
|
"str",
|
|
"dex",
|
|
"vit",
|
|
"for",
|
|
"mp_cost",
|
|
"mp_reduction",
|
|
"max_xp",
|
|
"goldm",
|
|
"xpm",
|
|
"luckm",
|
|
"map",
|
|
"in",
|
|
"isize",
|
|
"esize",
|
|
"gold",
|
|
"cash",
|
|
"targets",
|
|
"m",
|
|
"evasion",
|
|
"miss",
|
|
"reflection",
|
|
"lifesteal",
|
|
"manasteal",
|
|
"rpiercing",
|
|
"apiercing",
|
|
"crit",
|
|
"critdamage",
|
|
"dreturn",
|
|
"tax",
|
|
"xrange",
|
|
"pnresistance",
|
|
"firesistance",
|
|
"fzresistance",
|
|
"phresistance",
|
|
"stresistance",
|
|
"incdmgamp",
|
|
"stun",
|
|
"blast",
|
|
"explosion",
|
|
"courage",
|
|
"mcourage",
|
|
"pcourage",
|
|
"fear",
|
|
].forEach(function (p) {
|
|
//"vision",
|
|
data[p] = player[p];
|
|
});
|
|
data.items = player.citems;
|
|
if (player.user !== undefined) {
|
|
data.user = player.cuser;
|
|
if (player.user) {
|
|
data.user.gold = player.user.gold;
|
|
}
|
|
}
|
|
if (player.socket) {
|
|
data.cc = get_call_cost(player);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function monster_to_client(monster, events) {
|
|
var data = {};
|
|
var def = G.monsters[monster.type];
|
|
[
|
|
"speed",
|
|
"hp",
|
|
"mp",
|
|
"max_mp",
|
|
"attack",
|
|
"xp",
|
|
"frequency",
|
|
"armor",
|
|
"resistance",
|
|
"1hp",
|
|
"skin",
|
|
"cooperative",
|
|
"drops",
|
|
].forEach(function (p) {
|
|
//same array as game.js adopt_soft_properties
|
|
if (p in monster && monster[p] != def[p]) {
|
|
data[p] = monster[p];
|
|
}
|
|
});
|
|
if (monster.max_hp != def.hp) {
|
|
data.max_hp = monster.max_hp;
|
|
}
|
|
[
|
|
"id",
|
|
"x",
|
|
"y",
|
|
"moving",
|
|
"going_x",
|
|
"going_y",
|
|
"abs",
|
|
"move_num",
|
|
"angle",
|
|
"type",
|
|
"cid",
|
|
"target",
|
|
"focus",
|
|
"s",
|
|
].forEach(function (p) {
|
|
// removed "vx","vy" from both datasets [16/04/18]
|
|
if (monster[p] !== undefined) {
|
|
data[p] = monster[p];
|
|
}
|
|
});
|
|
if (monster.level > 1) {
|
|
data.level = monster.level;
|
|
}
|
|
if (monster.pet) {
|
|
data.pet = true;
|
|
data.owner = monster.owner;
|
|
data.name = monster.name;
|
|
}
|
|
if (monster.trap) {
|
|
data.trap = true;
|
|
data.owner = monster.owner;
|
|
}
|
|
|
|
if (events && events.length) {
|
|
data.events = events;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function player_to_summary(player) {
|
|
var summary = {
|
|
skin: player.tskin || player.skin,
|
|
level: player.level,
|
|
type: player.type,
|
|
x: player.x,
|
|
y: player.y,
|
|
in: player.in,
|
|
map: player.map,
|
|
name: player.name,
|
|
hp: player.hp,
|
|
max_hp: player.max_hp,
|
|
};
|
|
if (player.rip) {
|
|
summary.rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone || true;
|
|
}
|
|
summary.cx = player.tcx || player.cx || {};
|
|
return summary;
|
|
}
|
|
|
|
function save_state(player) {
|
|
player.state = {
|
|
map: player.map,
|
|
in: player.in,
|
|
hp: player.hp,
|
|
mp: player.mp,
|
|
s: player.s,
|
|
x: player.x,
|
|
y: player.y,
|
|
restored: false,
|
|
};
|
|
}
|
|
|
|
function clean_slate(player) {
|
|
restore_state(player);
|
|
save_state(player);
|
|
player.hp = player.max_hp;
|
|
player.mp = player.max_mp;
|
|
player.s = {};
|
|
player.c = {};
|
|
}
|
|
|
|
function restore_state(player, dc) {
|
|
if (player.state && !player.state.restored) {
|
|
player.hp = player.state.hp;
|
|
player.mp = player.state.mp;
|
|
player.s = player.state.s;
|
|
player.rip = false;
|
|
if (player.state.rip) {
|
|
player.rip = true;
|
|
}
|
|
if (dc) {
|
|
player.map = player.state.map;
|
|
player.in = player.state.in;
|
|
player.x = player.state.x;
|
|
player.y = player.state.y;
|
|
}
|
|
player.state.restored = true;
|
|
}
|
|
delete player.team;
|
|
delete player.duel;
|
|
}
|
|
|
|
function party_to_client(oname) {
|
|
var list = parties[oname];
|
|
var party = {};
|
|
var output = 0;
|
|
var length = 0;
|
|
var newbies = 0;
|
|
var odps = {};
|
|
var c = {};
|
|
var add = 36000;
|
|
calculate_party(oname);
|
|
list.forEach(function (name) {
|
|
var player = players[name_to_id[name]];
|
|
var dps_multiplier = 1;
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (player.type == "merchant") {
|
|
return;
|
|
}
|
|
if (player.type == "priest") {
|
|
dps_multiplier = 1.36;
|
|
}
|
|
length += 1;
|
|
if (player.level < 60) {
|
|
newbies += 1;
|
|
}
|
|
output += player.pdps * dps_multiplier + add;
|
|
odps[player.owner] = (odps[player.owner] || 0) + player.pdps * dps_multiplier + add;
|
|
c[player.owner] = (c[player.owner] || 0) + 1;
|
|
});
|
|
list.forEach(function (name) {
|
|
var player = players[name_to_id[name]];
|
|
var dps_multiplier = 1;
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (player.type == "priest") {
|
|
dps_multiplier = 1.36;
|
|
}
|
|
player.share = 0;
|
|
if (!output) {
|
|
// to handle an all merchant party
|
|
player.share = 1.0 / (max(1, list.length) + EPS);
|
|
} else if (player.type != "merchant") {
|
|
player.share = odps[player.owner] / (c[player.owner] + EPS) / (output + EPS);
|
|
player.share = min(1, player.share);
|
|
}
|
|
player.party_length = length;
|
|
player.party_gold = 5;
|
|
player.party_luck = newbies * 10;
|
|
player.party_xp = [0, 0, 10, 16, 20, 24, 25, 30, 36, 40, 40, 40, 40][length] || 0;
|
|
party[name] = {
|
|
skin: player.tskin || player.skin,
|
|
level: player.level,
|
|
type: player.type,
|
|
x: player.x,
|
|
y: player.y,
|
|
in: player.in,
|
|
map: player.map,
|
|
share: player.share,
|
|
pdps: player.pdps,
|
|
l: length,
|
|
xp: player.party_xp,
|
|
luck: player.party_luck,
|
|
gold: player.party_gold,
|
|
};
|
|
if (player.rip) {
|
|
party[name].rip = (player.tcx && player.tcx.gravestone) || player.cx.gravestone || true;
|
|
}
|
|
if (Object.keys(player.tcx || player.cx).length) {
|
|
party[name].cx = player.tcx || player.cx;
|
|
}
|
|
});
|
|
// git test
|
|
return party;
|
|
}
|
|
|
|
function send_party_update(oname) {
|
|
var party = party_to_client(oname);
|
|
parties[oname].forEach(function (name) {
|
|
var player = players[name_to_id[name]];
|
|
if (!player) {
|
|
console.log("#X party player not found: " + oname);
|
|
leave_party(oname, { name: name });
|
|
return;
|
|
}
|
|
players[name_to_id[name]].socket.emit("party_update", { list: parties[oname], party: party });
|
|
});
|
|
}
|
|
|
|
function calculate_party(oname) {
|
|
var list = parties[oname];
|
|
list.forEach(function (name) {
|
|
var player = players[name_to_id[name]];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (player.type == "merchant") {
|
|
player.party_weight = 0;
|
|
}
|
|
player.party_weight = 20;
|
|
});
|
|
}
|
|
|
|
var stat_to_attr = {
|
|
str: "str",
|
|
int: "int",
|
|
dex: "dex",
|
|
vit: "vit",
|
|
for: "for",
|
|
armor: "armor",
|
|
resistance: "resistance",
|
|
pnresistance: "pnresistance",
|
|
firesistance: "firesistance",
|
|
fzresistance: "fzresistance",
|
|
phresistance: "phresistance",
|
|
stresistance: "stresistance",
|
|
incdmgamp: "incdmgamp",
|
|
stun: "stun",
|
|
blast: "blast",
|
|
explosion: "explosion",
|
|
evasion: "evasion",
|
|
cuteness: "cuteness",
|
|
bling: "bling",
|
|
dreturn: "dreturn",
|
|
reflection: "reflection",
|
|
crit: "crit",
|
|
critdamage: "critdamage",
|
|
miss: "miss",
|
|
avoidance: "avoidance",
|
|
hp: "max_hp",
|
|
mp: "max_mp",
|
|
speed: "speed",
|
|
lifesteal: "lifesteal",
|
|
manasteal: "manasteal",
|
|
apiercing: "apiercing",
|
|
rpiercing: "rpiercing",
|
|
output: "output",
|
|
attack: "a_attack",
|
|
mp_cost: "a_mp_cost",
|
|
mp_reduction: "mp_reduction",
|
|
xp: "xxp",
|
|
luck: "xluck",
|
|
gold: "xgold",
|
|
range: "range",
|
|
courage: "courage",
|
|
mcourage: "mcourage",
|
|
pcourage: "pcourage",
|
|
};
|
|
|
|
function apply_stats(player, prop, args) {
|
|
for (var stat in prop) {
|
|
if (args && args.no_range && stat == "range") {
|
|
continue;
|
|
}
|
|
if (stat_to_attr[stat]) {
|
|
player[stat_to_attr[stat]] = player[stat_to_attr[stat]] + prop[stat];
|
|
} else if (stat == "frequency") {
|
|
player.frequency += prop[stat] / 100.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function calculate_player_stats(player) {
|
|
if (player.is_npc) {
|
|
return;
|
|
}
|
|
player.max_xp = G.levels[player.level + ""];
|
|
var level_up = false;
|
|
while (player.xp >= player.max_xp) {
|
|
level_up = true;
|
|
player.xp -= player.max_xp;
|
|
player.level++;
|
|
player.max_xp = G.levels[player.level + ""];
|
|
player.hp = 0;
|
|
achievement_logic_level(player);
|
|
}
|
|
if (level_up) {
|
|
if (player.level >= 80) {
|
|
realm_broadcast("server_message", { message: player.name + " is now level " + player.level, color: "#968CFA" });
|
|
} else if (player.level >= 70) {
|
|
broadcast("server_message", { message: player.name + " is now level " + player.level + "!", color: "#968CFA" });
|
|
}
|
|
xy_emit(player, "ui", { type: "level_up", name: player.name });
|
|
}
|
|
// disappearing_text(player.socket,player,"LEVEL UP!",{xy:1,size:"huge",color:"#724A8F"});
|
|
if (player.xp < 0) {
|
|
player.xp = 0;
|
|
player.warnings = (player.warnings || 0) + 1;
|
|
if (player.warnings == 2) {
|
|
console.log("'Your monster!' logged out ->");
|
|
player.socket.emit("ui_log", "You monster!");
|
|
player.socket.disconnect();
|
|
return;
|
|
}
|
|
}
|
|
var class_def = G.classes[player.type];
|
|
var item_attack = 0;
|
|
var the_date = new Date();
|
|
if (!class_def) {
|
|
class_def = G.classes.merchant;
|
|
} // when npc's get resend't
|
|
player.range = class_def.range;
|
|
player.max_hp = class_def.hp;
|
|
player.max_mp = class_def.mp;
|
|
["stealth"].forEach(function (prop) {
|
|
player[prop] = false;
|
|
});
|
|
[
|
|
"a_mp_cost",
|
|
"a_attack",
|
|
"stones",
|
|
"lifesteal",
|
|
"manasteal",
|
|
"incdmgamp",
|
|
"mp_reduction",
|
|
"stun",
|
|
"blast",
|
|
"explosion",
|
|
"cuteness",
|
|
"bling",
|
|
].forEach(function (prop) {
|
|
player[prop] = 0;
|
|
});
|
|
[
|
|
"speed",
|
|
"attack",
|
|
"frequency",
|
|
"mp_cost",
|
|
"armor",
|
|
"resistance",
|
|
"apiercing",
|
|
"rpiercing",
|
|
"a",
|
|
"aura",
|
|
"evasion",
|
|
"miss",
|
|
"reflection",
|
|
"crit",
|
|
"critdamage",
|
|
"dreturn",
|
|
"computer",
|
|
"xxp",
|
|
"xluck",
|
|
"xgold",
|
|
"output",
|
|
"courage",
|
|
"mcourage",
|
|
"pcourage",
|
|
"pnresistance",
|
|
"firesistance",
|
|
"fzresistance",
|
|
"phresistance",
|
|
"stresistance",
|
|
].forEach(function (prop) {
|
|
player[prop] = class_def[prop] || 0;
|
|
});
|
|
if (!player.a) {
|
|
player.a = {};
|
|
} //abilities
|
|
if (!player.aura) {
|
|
player.aura = {};
|
|
}
|
|
if (player.paura) {
|
|
for (var id in player.paura) {
|
|
player.aura[id] = player.paura[id];
|
|
}
|
|
}
|
|
for (stat in class_def.stats) {
|
|
player[stat] = class_def.stats[stat] + player.level * class_def.lstats[stat];
|
|
if (player.level > 40) {
|
|
player[stat] += (player.level - 40) * class_def.lstats[stat];
|
|
}
|
|
if (player.level > 55) {
|
|
player[stat] += (player.level - 55) * class_def.lstats[stat];
|
|
}
|
|
if (player.level > 65) {
|
|
player[stat] += (player.level - 65) * class_def.lstats[stat];
|
|
}
|
|
if (player.level > 80) {
|
|
player[stat] -= (player.level - 80) * class_def.lstats[stat];
|
|
}
|
|
player[stat] = floor(player[stat]);
|
|
}
|
|
if (!player.slots) {
|
|
player.slots = {};
|
|
}
|
|
// players.citems[27]=null; //- to reproduce the bug
|
|
while (player.items.length > 42 && !player.items[player.items.length - 1]) {
|
|
player.items.splice(player.items.length - 1);
|
|
}
|
|
while (player.citems.length > 42 && !player.citems[player.citems.length - 1]) {
|
|
player.citems.splice(player.citems.length - 1);
|
|
}
|
|
player.isize = 42;
|
|
player.sets = {};
|
|
player.esize = player.isize - player.items.length;
|
|
player.xpm = player.goldm = player.luckm = 1;
|
|
for (var i = 0; i < player.items.length; i++) {
|
|
var current = player.items[i];
|
|
if (!current) {
|
|
player.esize++;
|
|
} else if (current.name == "supercomputer") {
|
|
player.tracker = player.computer = player.supercomputer = true;
|
|
} else if (current.name == "computer") {
|
|
player.computer = true;
|
|
} else if (current.name == "tracker") {
|
|
player.tracker = true;
|
|
} else if (current.expires) {
|
|
// &&!current.ex = ex=elixier
|
|
if (the_date > current.expires) {
|
|
player.items[i] = null;
|
|
player.citems[i] = null;
|
|
} else if (G.items[current.name].gain) {
|
|
var prop = calculate_item_properties(current);
|
|
player.stones++;
|
|
player["x" + G.items[current.name].gain] = prop[G.items[current.name].gain];
|
|
}
|
|
}
|
|
}
|
|
player.monster_stats = {};
|
|
if (player.tracker) {
|
|
for (var name in G.monsters) {
|
|
var mx = max(
|
|
(player.p.stats.monsters[name] || 0) + (player.p.stats.monsters_diff[name] || 0),
|
|
(player.max_stats.monsters[name] || [0, 0])[0],
|
|
);
|
|
if (!mx || !G.monsters[name].achievements) {
|
|
continue;
|
|
}
|
|
G.monsters[name].achievements.forEach(function (def) {
|
|
if (mx < def[0]) {
|
|
return;
|
|
}
|
|
if (def[1] == "stat") {
|
|
player.monster_stats[def[2]] = (player.monster_stats[def[2]] || 0) + def[3];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
character_slots.forEach(function (slot) {
|
|
var current = player.slots[slot];
|
|
if (!current) {
|
|
return;
|
|
}
|
|
var def = G.items[current.name];
|
|
if (!def) {
|
|
console.log("#X Undefined item: " + current.name + " (" + slot + ")");
|
|
return;
|
|
}
|
|
var prop = calculate_item_properties(current, { class: player.type, map: player.map });
|
|
if (prop.class && !prop.class.includes(player.type)) {
|
|
return;
|
|
}
|
|
|
|
apply_stats(player, prop, { no_range: slot == "offhand" && def.type == "weapon" });
|
|
if (prop.attack) {
|
|
if (slot == "offhand") {
|
|
item_attack += prop.attack * 0.7;
|
|
} else {
|
|
item_attack += prop.attack;
|
|
}
|
|
}
|
|
if (def.ability) {
|
|
player.a[def.ability] = {
|
|
attr0: ((player.a[def.ability] && player.a[def.ability].attr0) || 0) + prop.attr0,
|
|
attr1: ((player.a[def.ability] && player.a[def.ability].attr1) || 0) + prop.attr1,
|
|
};
|
|
}
|
|
if (def.aura) {
|
|
player.aura[def.aura] = { attr0: prop.attr0 || 0, attr1: prop.attr1 || 0 };
|
|
}
|
|
if (slot == "mainhand") {
|
|
apply_stats(player, class_def.doublehand[def.wtype] || class_def.mainhand[def.wtype] || {});
|
|
}
|
|
if (slot == "offhand") {
|
|
apply_stats(
|
|
player,
|
|
class_def.offhand[def.wtype] ||
|
|
class_def.offhand[def.type] || {
|
|
no_range: !player.slots.mainhand,
|
|
},
|
|
);
|
|
}
|
|
if (prop.set) {
|
|
player.sets[def.set] = (player.sets[def.set] || 0) + 1;
|
|
}
|
|
});
|
|
for (var set in player.sets) {
|
|
var prop = G.sets[set] && G.sets[set][player.sets[set]];
|
|
if (!prop) {
|
|
continue;
|
|
}
|
|
apply_stats(player, prop);
|
|
}
|
|
for (var condition in player.s) {
|
|
var prop = G.conditions[condition];
|
|
apply_stats(player, player.s[condition]);
|
|
if (!prop) {
|
|
continue;
|
|
}
|
|
apply_stats(player, prop);
|
|
}
|
|
apply_stats(player, player.monster_stats);
|
|
if (
|
|
player.slots.mainhand &&
|
|
player.slots.offhand &&
|
|
G.items[player.slots.mainhand.name].wtype == "stars" &&
|
|
G.items[player.slots.offhand.name].wtype != "stars"
|
|
) {
|
|
item_attack /= 3.0;
|
|
}
|
|
if (player.slots.cape && player.slots.cape.name == "stealthcape") {
|
|
player.stealth = true;
|
|
}
|
|
item_attack = max(item_attack, 5);
|
|
if (player.type == "paladin") {
|
|
player.attack += item_attack * (player.str / 20.0 + player.int / 40.0);
|
|
} else {
|
|
player.attack += item_attack * (player[class_def.main_stat] / 20.0);
|
|
}
|
|
player.attack += player.a_attack;
|
|
if (player.type == "priest") {
|
|
player.attack *= 1.6;
|
|
}
|
|
if (player.type == "warrior") {
|
|
player.courage += round(player.str / 30);
|
|
}
|
|
if (player.type == "priest") {
|
|
player.mcourage += round(player.int / 30);
|
|
}
|
|
if (player.type == "paladin") {
|
|
player.pcourage += round(player.str / 30 + player.int / 30);
|
|
}
|
|
//console.log(player.speed)
|
|
//player.speed+=player.dex/20.0+player.str/40.0+min(player.level,40)/4.0+max(0,min(player.level-40,20))/5.0+max(0,player.level-60)/7.0
|
|
player.speed +=
|
|
min(player.dex, 256) / 32.0 +
|
|
min(player.str, 256) / 64.0 +
|
|
min(player.level, 40) / 10.0 +
|
|
max(0, min(player.level - 40, 20)) / 15.0 +
|
|
max(0, min(86, player.level) - 60) / 16.0;
|
|
|
|
player.aggro_diff = player.bling / 100 - player.cuteness / 100;
|
|
|
|
//console.log(player.speed)
|
|
//player.max_hp+=player.str*5+player.vit*player.level/2; //player.str*10.0+player.level*10+player.vit*25
|
|
player.max_hp += player.str * 21 + player.vit * (48 + player.level / 3.0);
|
|
player.max_hp = max(1, player.max_hp);
|
|
player.max_mp = max(1, player.max_mp);
|
|
player.max_mp += player.int * 15.0 + player.level * 5;
|
|
//player.armor+=player.str/2.0;
|
|
player.armor += min(player.str, 160) + max(0, player.str - 160) * 0.25;
|
|
player.resistance += min(player.int, 180) + max(0, player.int - 180) * 0.25;
|
|
player.frequency +=
|
|
min(player.level, 80) / 164.0 +
|
|
min(160, player.dex) / 640.0 +
|
|
max(player.dex - 160, 0) / 925.0 +
|
|
player.int / 1575.0; // 120 635 1275 is the original mix
|
|
// player.frequency=9000;
|
|
player.attack_ms = round(1000.0 / player.frequency);
|
|
if (player.last_attack_ms && player.attack_ms != player.last_attack_ms) {
|
|
// server_log("skill_timeout_correction: "+player.last_attack_ms+" to "+player.attack_ms+" timeout: "+(player.attack_ms-mssince(player.last.attack)))
|
|
player.socket.emit("skill_timeout", {
|
|
name: "attack",
|
|
ms: player.attack_ms - mssince(player.last.attack),
|
|
reason: "attack_ms",
|
|
});
|
|
}
|
|
player.last_attack_ms = player.attack_ms;
|
|
player.mp_cost +=
|
|
min(player.level, 80) * (player.mp_cost / 10.0) +
|
|
player.a_mp_cost +
|
|
player.crit * 1.25 +
|
|
player.lifesteal * 1.5 +
|
|
player.manasteal / 5.0;
|
|
if (player.damage_type == "physical") {
|
|
player.mp_cost += player.apiercing / 15.0;
|
|
} else {
|
|
player.mp_cost += player.rpiercing / 15.0;
|
|
}
|
|
player.mp_cost = max(1, player.mp_cost);
|
|
if (!player.hp && !player.rip) {
|
|
player.hp = player.max_hp;
|
|
player.mp = player.max_mp;
|
|
} // used for level-ups
|
|
if ((player.gold || 0) <= 0) {
|
|
player.gold = 0;
|
|
}
|
|
player.heal = 0;
|
|
if (player.type == "priest") {
|
|
player.heal = player.attack;
|
|
}
|
|
player.output = max(5, player.output);
|
|
if (player.output) {
|
|
player.attack = (player.attack * player.output) / 100.0;
|
|
}
|
|
if (player.s.damage_received) {
|
|
player.attack += (player.s.damage_received.amount * 4) / 100;
|
|
}
|
|
["attack", "heal", "hp", "mp", "max_hp", "max_mp", "range", "mp_cost", "resistance", "armor"].forEach(
|
|
function (prop) {
|
|
player[prop] = round(player[prop]);
|
|
},
|
|
);
|
|
player.hp = max(0, min(player.hp, player.max_hp));
|
|
player.mp = max(0, min(player.mp, player.max_mp));
|
|
|
|
if (player.party && parties[player.party]) {
|
|
if (player.party_xp) {
|
|
player.xxp += player.party_xp;
|
|
}
|
|
if (player.party_luck) {
|
|
player.xluck += player.party_luck;
|
|
}
|
|
if (player.party_gold) {
|
|
player.xgold += player.party_gold;
|
|
}
|
|
}
|
|
|
|
if (goldm != 1) {
|
|
player.xgold += (goldm - 1) * 100.0;
|
|
player.xluck += (luckm - 1) * 100.0;
|
|
player.xxp += (xpm - 1) * 100.0;
|
|
}
|
|
player.luckm = 1 + player.xluck / 100.0;
|
|
player.xpm = 1 + player.xxp / 100.0;
|
|
player.goldm = 1 + player.xgold / 100.0;
|
|
["luckm", "xpm", "goldm"].forEach(function (p) {
|
|
player[p] = max(0.01, player[p]);
|
|
});
|
|
|
|
if (player.tskin == "konami") {
|
|
player.range = 120;
|
|
player.frequency += 0.25;
|
|
player.goldm *= 0.25;
|
|
player.luckm += 0.25;
|
|
}
|
|
|
|
if (player.s.invis) {
|
|
player.speed = max(player.speed * 0.6, player.speed - 25);
|
|
player.attack = round(player.attack * 1.25);
|
|
}
|
|
if (player.s.invincible) {
|
|
player.attack = round(player.attack * 0.45);
|
|
}
|
|
if (player.s.dash) {
|
|
player.speed = 500;
|
|
}
|
|
calculate_common_stats(player);
|
|
|
|
player.tax =
|
|
(player.level > 80 && 0.01) ||
|
|
(player.level > 80 && 0.012) ||
|
|
(player.level > 70 && 0.02) ||
|
|
(player.level > 60 && 0.025) ||
|
|
(player.level > 50 && 0.03) ||
|
|
(player.level > 20 && 0.04) ||
|
|
0.05;
|
|
|
|
player.fear = Math.max(
|
|
0,
|
|
player.targets_p - player.courage,
|
|
player.targets_m - player.mcourage,
|
|
player.targets_u - player.pcourage,
|
|
);
|
|
const sredux = [0, 20, 40, 70, 80, 90, 100];
|
|
player.speed -= sredux[Math.min(sredux.length - 1, player.fear)];
|
|
if (player.fear > 2) {
|
|
player.attack = Math.round(player.attack * 0.2);
|
|
if (mode.fear_affects_heal) {
|
|
player.heal = Math.round(player.heal * 0.2);
|
|
}
|
|
} else if (player.fear > 1) {
|
|
player.attack = Math.round(player.attack * 0.4);
|
|
if (mode.fear_affects_heal) {
|
|
player.heal = Math.round(player.heal * 0.4);
|
|
}
|
|
} else if (player.fear) {
|
|
player.attack = Math.round(player.attack * 0.6);
|
|
if (mode.fear_affects_heal) {
|
|
player.heal = Math.round(player.heal * 0.6);
|
|
}
|
|
}
|
|
|
|
if (player.map == "winterland") {
|
|
player.speed *= 0.95;
|
|
}
|
|
|
|
if (player.p.stand || player.s.hardshell) {
|
|
player.speed = 10;
|
|
}
|
|
player.evasion = min(50, player.evasion);
|
|
player.reflection = min((player.s.reflection && 50) || 30, player.reflection);
|
|
player.speed = min(player.speed, player.cruise || 200000);
|
|
player.speed = round(max(5, player.speed));
|
|
if (!player.gold && player.gold !== 0) {
|
|
player.gold = 0;
|
|
server_log("#X - GOLD BUG calculate", 1);
|
|
}
|
|
recalculate_vxy(player);
|
|
perfc.cps += 1;
|
|
}
|
|
|
|
function calculate_common_stats(entity) {
|
|
if (entity.s.poisoned) {
|
|
entity.frequency *= 0.9;
|
|
}
|
|
if (entity.s.frozen) {
|
|
entity.frequency *= 0.3;
|
|
}
|
|
if (entity.speed < 1) {
|
|
entity.speed = 1;
|
|
}
|
|
if (entity.s.tangled) {
|
|
entity.speed = min(entity.speed, 24);
|
|
}
|
|
}
|
|
|
|
function calculate_monster_stats(monster) {
|
|
var def = G.monsters[monster.type];
|
|
["attack", "speed", "frequency", "armor", "resistance", "output", "incdmgamp", "avoidance"].forEach(function (p) {
|
|
monster[p] = def[p] || 0;
|
|
});
|
|
monster.output = 100;
|
|
if (monster.target || monster.focus) {
|
|
monster.speed = G.monsters[monster.type].charge;
|
|
}
|
|
for (var name in monster.s) {
|
|
// if(G.conditions[name])
|
|
// {
|
|
// for(var p in G.conditions[name])
|
|
// if(p in monster)
|
|
// monster[p]+=G.conditions[name][p];
|
|
// }
|
|
|
|
var prop = G.conditions[name];
|
|
apply_stats(monster, monster.s[name]);
|
|
if (!prop) {
|
|
continue;
|
|
}
|
|
apply_stats(monster, prop);
|
|
}
|
|
monster.attack = round((monster.attack * monster.output) / 100.0);
|
|
if (monster.focus && instances[monster.in].monsters[monster.focus]) {
|
|
monster.speed = min(monster.speed, instances[monster.in].monsters[monster.focus].speed + 4);
|
|
}
|
|
if (monster.level > 1) {
|
|
var att_mult = 0.125;
|
|
var freq_mult = 0.034;
|
|
var speed_mult = 0.24; // speed originally 0.34
|
|
var mlevel = min(monster.level, 12);
|
|
if (monster.map_def.grow) {
|
|
att_mult = 0.05;
|
|
freq_mult = 0.008;
|
|
speed_mult = 0.16;
|
|
}
|
|
monster.attack = parseInt(monster.attack * (1 + mlevel * att_mult));
|
|
monster.speed += mlevel * speed_mult;
|
|
monster.frequency += mlevel * freq_mult;
|
|
}
|
|
if (E.schedule.night) {
|
|
monster.speed = ceil(monster.speed * 0.7);
|
|
}
|
|
calculate_common_stats(monster);
|
|
recalculate_vxy(monster);
|
|
}
|
|
|
|
function ccms(monster) {
|
|
if (Object.keys(monster.s).length) {
|
|
calculate_monster_stats(monster);
|
|
}
|
|
}
|
|
|
|
function can_equip_item(player, item, slot) {
|
|
var class_def = G.classes[player.type];
|
|
if (!slot) {
|
|
slot = item.type;
|
|
}
|
|
if (slot == "offhand" && item.type != "weapon") {
|
|
slot = item.type;
|
|
} //Easiest solution to the slot:"offhand" challenge [15/11/16]
|
|
if (item.type == "tool") {
|
|
slot = "mainhand";
|
|
}
|
|
if (item.type == "test" && slot != "test") {
|
|
return slot;
|
|
}
|
|
if (
|
|
!in_arr(item.type, [
|
|
"helmet",
|
|
"pants",
|
|
"chest",
|
|
"weapon",
|
|
"amulet",
|
|
"earring",
|
|
"shoes",
|
|
"gloves",
|
|
"ring",
|
|
"shield",
|
|
"belt",
|
|
"source",
|
|
"orb",
|
|
"quiver",
|
|
"cape",
|
|
"misc_offhand",
|
|
"tool",
|
|
])
|
|
) {
|
|
return "no";
|
|
}
|
|
if (slot != item.type && in_arr(item.type, ["shield", "source", "quiver", "misc_offhand"]) && slot != "offhand") {
|
|
return "no";
|
|
}
|
|
if (in_arr(slot, ["weapon", "mainhand", "offhand", "tool"])) {
|
|
if (
|
|
slot == "weapon" &&
|
|
player.slots.mainhand &&
|
|
class_def.mainhand[G.items[player.slots.mainhand.name].wtype] &&
|
|
!player.slots.offhand &&
|
|
class_def.offhand[item.wtype]
|
|
) {
|
|
slot = "offhand";
|
|
} else if (
|
|
slot == "offhand" &&
|
|
(!player.slots.mainhand ||
|
|
(player.slots.mainhand && class_def.mainhand[G.items[player.slots.mainhand.name].wtype])) &&
|
|
class_def.offhand[item.wtype]
|
|
) {
|
|
slot = "offhand";
|
|
} else if (slot == "weapon" || slot == "mainhand") {
|
|
if (class_def.doublehand[item.wtype] && !player.slots.offhand) {
|
|
} else if (!class_def.mainhand[item.wtype]) {
|
|
return "no";
|
|
}
|
|
slot = "mainhand";
|
|
} else {
|
|
return "no";
|
|
}
|
|
} else if (item.type == "shield" || item.type == "misc_offhand") {
|
|
if (
|
|
class_def.offhand[item.type] &&
|
|
!(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])
|
|
) {
|
|
slot = "offhand";
|
|
} else {
|
|
return "no";
|
|
}
|
|
} else if (item.type == "quiver") {
|
|
if (
|
|
class_def.offhand.quiver &&
|
|
!(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])
|
|
) {
|
|
slot = "offhand";
|
|
} else {
|
|
return "no";
|
|
}
|
|
} else if (item.type == "source") {
|
|
if (
|
|
class_def.offhand.source &&
|
|
!(player.slots.mainhand && class_def.doublehand[G.items[player.slots.mainhand.name].wtype])
|
|
) {
|
|
slot = "offhand";
|
|
} else {
|
|
return "no";
|
|
}
|
|
} else if (item.type == "ring") {
|
|
if (slot == "ring1" || slot == "ring2") {
|
|
} else if (!player.slots["ring1"]) {
|
|
slot = "ring1";
|
|
} else {
|
|
slot = "ring2";
|
|
}
|
|
} else if (item.type == "earring") {
|
|
if (slot == "earring1" || slot == "earring2") {
|
|
} else if (!player.slots["earring1"]) {
|
|
slot = "earring1";
|
|
} else {
|
|
slot = "earring2";
|
|
}
|
|
} else if (slot != item.type) {
|
|
return "no";
|
|
}
|
|
return slot;
|
|
}
|
|
|
|
function consume(player, num, quantity) {
|
|
var available = player.items[num].q || 1;
|
|
if (quantity > available) {
|
|
exception = not_enough_items;
|
|
}
|
|
if (available == quantity) {
|
|
player.items[num] = null;
|
|
player.esize++;
|
|
} else {
|
|
player.items[num].q -= quantity;
|
|
}
|
|
player.citems[num] = cache_item(player.items[num]);
|
|
}
|
|
|
|
function consume_one(player, num) {
|
|
consume(player, num, 1);
|
|
}
|
|
|
|
function consume_one_by_id(player, id) {
|
|
for (var i = 0; i < player.items.length; i++) {
|
|
if (player.items[i] && player.items[i].name == id) {
|
|
consume_one(player, i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function create_new_item(name, quantity) {
|
|
var new_item = { name: name };
|
|
if (G.items[name].s) {
|
|
new_item.q = quantity || 1;
|
|
}
|
|
if (G.items[name].upgrade || G.items[name].compound) {
|
|
new_item.level = 0;
|
|
}
|
|
return new_item;
|
|
}
|
|
|
|
function create_new_sitem(item, quantity) {
|
|
let s_item = create_new_item(item.name, quantity);
|
|
if (item.v) {
|
|
s_item.v = item.v;
|
|
}
|
|
if (item.data) {
|
|
s_item.data = item.data;
|
|
}
|
|
if (item.p) {
|
|
s_item.p = item.p;
|
|
}
|
|
return s_item;
|
|
}
|
|
|
|
function aadd_item(player, new_item) {
|
|
// admin [24/06/23]
|
|
add_item(player, new_item, { announce: false });
|
|
reopen(player, "u+cid+reopen");
|
|
}
|
|
|
|
function add_item(player, new_item, args) {
|
|
// # IMPORTANT: ALWAYS KEEP CAN_ADD_ITEM IN SYNC!
|
|
var done = false;
|
|
var num = 0;
|
|
var a = false;
|
|
if (!args) {
|
|
args = {};
|
|
}
|
|
if (!new_item.name) {
|
|
new_item = create_new_item(new_item, args.q || 1);
|
|
}
|
|
if (args.p) {
|
|
new_item.p = args.p;
|
|
}
|
|
if (
|
|
(args.m || args.r) &&
|
|
((G.items[new_item.name].upgrade && Math.random() < 1.0 / 500) ||
|
|
(G.items[new_item.name].compound && Math.random() < 1.0 / 20000))
|
|
) {
|
|
new_item.p = "shiny";
|
|
a = true;
|
|
}
|
|
if (G.items[new_item.name].s) {
|
|
for (var i = 0; i < player.items.length; i++) {
|
|
var item = player.items[i];
|
|
if (can_stack(item, new_item)) {
|
|
if ((args.v && is_in_pvp(player, 1)) || new_item.v) {
|
|
item.v = new Date();
|
|
}
|
|
if (new_item.m) {
|
|
item.m = new_item.m;
|
|
}
|
|
item.q = parseInt((new_item.q || 1) + (item.q || 1)); // to fix the side-effects of not including parseInt on 'sell'/'buy' [22/02/18]
|
|
player.citems[i] = cache_item(player.items[i]);
|
|
done = true;
|
|
num = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!new_item.oo) {
|
|
new_item.oo = player.name;
|
|
}
|
|
if (!done) {
|
|
for (var i = 0; i < player.items.length; i++) {
|
|
var item = player.items[i];
|
|
if (!item) {
|
|
if (args.v && is_in_pvp(player, 1)) {
|
|
new_item.v = new Date();
|
|
}
|
|
player.items[i] = new_item;
|
|
player.citems[i] = cache_item(player.items[i]);
|
|
player.esize--; // esize-- needed for concurrent party chest operations [18/10/18]
|
|
done = true;
|
|
num = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!done) {
|
|
player.items.push(new_item);
|
|
player.citems[player.items.length - 1] = cache_item(player.items[player.items.length - 1]);
|
|
num = player.items.length - 1;
|
|
player.esize--;
|
|
}
|
|
if (
|
|
args.m &&
|
|
player.s.mluck &&
|
|
get_player(player.s.mluck.f) &&
|
|
Math.random() <= 0.02 &&
|
|
can_add_item(get_player(player.s.mluck.f), new_item.name)
|
|
) {
|
|
var mr = get_player(player.s.mluck.f);
|
|
var item = create_new_item(new_item.name);
|
|
item.m = player.name;
|
|
if (new_item.data) {
|
|
item.data = new_item.data;
|
|
} // "cxjar"
|
|
var num = add_item(mr, item, { m: 1 });
|
|
mr.socket.emit("game_log", { message: "Found " + item_to_phrase(item), color: "#64B867" });
|
|
xy_emit(mr, "ui", { type: "+M", name: mr.name, item: cache_item(item), num: num, cevent: "mluck", event: "mluck" });
|
|
resend(mr, "reopen+nc+inv");
|
|
}
|
|
if (args.announce !== false && (a || (a_score[new_item.name] || 0) < G.items[new_item.name].a) && !player.stealth) {
|
|
var event = G.items[new_item.name].event;
|
|
var e_type = "server_found";
|
|
var e_phrase = " found ";
|
|
if (!args.found) {
|
|
e_type = "server_received";
|
|
e_phrase = " received ";
|
|
}
|
|
if (args.phrase) {
|
|
e_type = "server_received";
|
|
e_phrase = " " + args.phrase.toLowerCase() + " ";
|
|
}
|
|
broadcast("server_message", {
|
|
message: player.name + e_phrase + item_to_phrase(new_item),
|
|
color: "#85C76B",
|
|
type: e_type,
|
|
item: cache_item(new_item),
|
|
name: player.name,
|
|
event: event,
|
|
});
|
|
a_score[new_item.name] = (a_score[new_item.name] || 0) + 1.05;
|
|
}
|
|
if (args.log) {
|
|
player.socket.emit("game_response", { response: "add_item", item: new_item });
|
|
}
|
|
return num;
|
|
}
|
|
|
|
function list_to_pseudo_items(list, type, add) {
|
|
var items = [];
|
|
var ex = {};
|
|
if (!type || type == "multi") {
|
|
list.forEach(function (el) {
|
|
if (G.items[el[1]].s) {
|
|
if (ex[el[1]]) {
|
|
ex[el[1]].q += max(1, el[0]);
|
|
} else {
|
|
ex[el[1]] = { name: el[1], q: max(1, el[0]) };
|
|
items.push(ex[el[1]]);
|
|
}
|
|
} else {
|
|
items.push({ name: el[1], q: max(1, el[0]) });
|
|
}
|
|
});
|
|
} else if (type == "chest") {
|
|
list.forEach(function (name) {
|
|
if (G.items[name].s) {
|
|
if (ex[name]) {
|
|
ex[name].q += 1;
|
|
} else {
|
|
ex[name] = create_new_item(name);
|
|
items.push(ex[name]);
|
|
}
|
|
} else {
|
|
items.push(create_new_item(name));
|
|
}
|
|
});
|
|
(add || []).forEach(function (item) {
|
|
items.push(item);
|
|
});
|
|
}
|
|
return items;
|
|
}
|
|
|
|
function bank_add_item(player, slot, new_item) {
|
|
if (!new_item.name) {
|
|
new_item = create_new_item(new_item);
|
|
}
|
|
if (new_item.q) {
|
|
for (var i = 0; i < player.user[slot].length; i++) {
|
|
var item = player.user[slot][i];
|
|
if (can_stack(item, new_item)) {
|
|
item.q = (item.q || 1) + (new_item.q || 1);
|
|
player.cuser[slot][i] = cache_item(player.user[slot][i]);
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
for (var i = 0; i < player.user[slot].length; i++) {
|
|
var item = player.user[slot][i];
|
|
if (!item) {
|
|
player.user[slot][i] = new_item;
|
|
player.cuser[slot][i] = cache_item(player.user[slot][i]);
|
|
return i;
|
|
}
|
|
}
|
|
player.user[slot].push(new_item);
|
|
const num = player.user[slot].length - 1;
|
|
player.cuser[slot][num] = cache_item(player.user[slot][num]);
|
|
return num;
|
|
}
|
|
|
|
function drop_item_logic(drop, def, pvp) {
|
|
var added = false;
|
|
if (def[1] == "shells") {
|
|
drop.cash += def[2];
|
|
} else if (def[1] == "cxjar" || def[1] == "emotionjar") {
|
|
var item = { name: def[1], q: def[2], data: def[3] };
|
|
if (pvp) {
|
|
item.v = new Date();
|
|
}
|
|
for (var i = 0; i < drop.items.length; i++) {
|
|
if (can_stack(drop.items[i], item)) {
|
|
drop.items[i].q += def[2];
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!added) {
|
|
drop.items.push(item);
|
|
}
|
|
} else if (def[1] == "open") {
|
|
chest_exchange(drop, def[2]);
|
|
} else {
|
|
var item = create_new_item(def[1], def[2]);
|
|
if (pvp) {
|
|
item.v = new Date();
|
|
}
|
|
for (var i = 0; i < drop.items.length; i++) {
|
|
if (can_stack(drop.items[i], item)) {
|
|
drop.items[i].q += def[2] ?? 1;
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!added) {
|
|
drop.items.push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drop_one_thing(player, items, args) {
|
|
if (!args) {
|
|
args = {};
|
|
}
|
|
var drop_id = randomStr(30);
|
|
var chest = args.chest || "chest1";
|
|
if (!is_array(items)) {
|
|
items = [items];
|
|
}
|
|
drop = chests[drop_id] = { items: [], cash: 0 };
|
|
for (var i = 0; i < items.length; i++) {
|
|
if (is_string(items[i])) {
|
|
drop_item_logic(drop, [1, items[i]], is_in_pvp(player, 1));
|
|
}
|
|
}
|
|
drop.gold = args.gold || 0;
|
|
drop.x = (args.x !== undefined && args.x) || player.x;
|
|
drop.y = (args.y !== undefined && args.y) || player.y;
|
|
drop.map = args.map || player.map;
|
|
drop.date = new Date();
|
|
player.socket.emit("drop", {
|
|
x: drop.x,
|
|
y: drop.y,
|
|
items: drop.items.length,
|
|
chest: chest,
|
|
id: drop_id,
|
|
map: drop.map,
|
|
owners: [player.owner],
|
|
});
|
|
}
|
|
|
|
function drop_something(player, monster, share) {
|
|
if (monster.pet || monster.trap) {
|
|
return;
|
|
}
|
|
achievement_logic_monster_kill(player, monster);
|
|
share = (share === undefined && 1) || share || 0;
|
|
// console.log("share: "+share);
|
|
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));
|
|
}
|
|
});
|
|
}
|
|
// Home-server monster-specific drops
|
|
if (
|
|
player.p.home &&
|
|
player.p.home === region + server_name &&
|
|
D.drops.monsters_home_server[monster.type] &&
|
|
player.tskin != "konami"
|
|
) {
|
|
D.drops.monsters_home_server[monster.type].forEach(function (item) {
|
|
let itemShouldDrop = shouldItemDrop(item);
|
|
if (itemShouldDrop || mode.drop_all) {
|
|
drop_item_logic(drop, item, is_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.poison &&
|
|
Math.random() < (attacker.a.poison.attr0 * (G.maps[attacker.map].poison_multiplier || 1)) / 100.0
|
|
) {
|
|
info.conditions.push("poisoned");
|
|
}
|
|
if (
|
|
info.procs &&
|
|
attacker.a.burn &&
|
|
info.procs &&
|
|
Math.random() < (attacker.a.burn.attr0 * (G.maps[attacker.map].burn_multiplier || 1)) / 100.0
|
|
) {
|
|
info.conditions.push("burned");
|
|
}
|
|
if (info.procs && attacker.a.weave && info.procs) {
|
|
info.conditions.push("woven");
|
|
}
|
|
if (info.procs && attacker.stun && Math.random() < attacker.stun / 100.0 && info.damage_type == "physical") {
|
|
info.conditions.push("stunned");
|
|
}
|
|
|
|
var pid = randomStr(6);
|
|
info.first_attack = info.attack = attack;
|
|
info.def = def;
|
|
def.damage_type = info.damage_type;
|
|
def.pid = pid;
|
|
info.attacker = attacker;
|
|
info.target = target;
|
|
info.atype = atype;
|
|
projectiles[pid] = info;
|
|
|
|
var action = {
|
|
attacker: attacker.id,
|
|
target: target.id,
|
|
type: atype,
|
|
source: atype,
|
|
x: target.x,
|
|
y: target.y,
|
|
eta: 0,
|
|
m: target.m,
|
|
pid: pid,
|
|
};
|
|
|
|
if (def.projectile) {
|
|
action.projectile = def.projectile;
|
|
if (target.is_monster && G.monsters[target.type].escapist) {
|
|
var dampened = false;
|
|
for (var id in instances[target.in].monsters) {
|
|
var m = instances[target.in].monsters[id];
|
|
if (m.type == "fieldgen0" && point_distance(target.x, target.y, m.x, m.y) < 300) {
|
|
target.s.dampened = { ms: 2000 };
|
|
add_condition(target, "dampened", { ms: 2000 });
|
|
dampened = true;
|
|
}
|
|
}
|
|
if (!dampened) {
|
|
port_monster(target, random_place(target.map));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(G.projectiles[def.projectile] && G.projectiles[def.projectile].instant)) {
|
|
action.eta = Math.floor((1000 * dist) / G.projectiles[def.projectile].speed);
|
|
} else {
|
|
action.instant = true;
|
|
}
|
|
|
|
info.eta = future_ms(action.eta);
|
|
|
|
if (info.heal) {
|
|
action.heal = attack;
|
|
} else if (attack) {
|
|
action.damage = attack;
|
|
}
|
|
if (info.positive) {
|
|
action.positive = true;
|
|
}
|
|
if (info.conditions.length) {
|
|
action.conditions = info.conditions;
|
|
}
|
|
info.action = action;
|
|
xy_emit(attacker, "action", action, target.id);
|
|
|
|
if (!action.eta) {
|
|
projectiles_loop();
|
|
}
|
|
|
|
action.response = "data";
|
|
action.place = atype;
|
|
|
|
return action;
|
|
}
|
|
|
|
function complete_attack(attacker, target, info) {
|
|
var defense = "armor";
|
|
var pierce = "apiercing";
|
|
var combo = 1;
|
|
var combo_m = 1;
|
|
var atype = info.atype;
|
|
var evade = false;
|
|
var first = true;
|
|
var attack = info.attack;
|
|
var o_attack;
|
|
var i_attack = info.attack;
|
|
var def = info.def;
|
|
if (G.monsters[attacker.type] && G.monsters[attacker.type].good) {
|
|
info.heal = info.positive = true;
|
|
}
|
|
var change = false;
|
|
var targets = [[target, "normal"]];
|
|
var otarget = target;
|
|
var events = [];
|
|
info.action.map = attacker.map;
|
|
info.action.in = attacker.in;
|
|
|
|
if (info.damage_type == "pure" || target === attacker) {
|
|
defense = "none_existent";
|
|
pierce = "non_existent";
|
|
info.apiercing = 0;
|
|
} 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,
|
|
monsters_home_server: D.drops.monsters_home_server || {}, //add this
|
|
}; // ,computer:false
|
|
function register_table(table) {
|
|
if (table) {
|
|
table.forEach(function (drop) {
|
|
if (drop[1] == "open" && !data.tables[drop[2]]) {
|
|
data.tables[drop[2]] = D.drops[drop[2]];
|
|
register_table(D.drops[drop[2]]);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (player.computer || 1) {
|
|
// data.computer=true;
|
|
data.drops = D.drops.monsters;
|
|
data.drops_home = D.drops.monsters_home_server || {}; //mirror monsters_home
|
|
} else {
|
|
data.drops = {};
|
|
data.drops_home = {};
|
|
for (var name in data.monsters) {
|
|
if (data.monsters[name] >= 100 && D.drops.monsters[name]) {
|
|
data.drops[name] = D.drops.monsters[name];
|
|
}
|
|
if (data.monsters[name] >= 100 && D.drops.monsters_home_server[name]) {
|
|
data.drops_home[name] = D.drops.monsters_home_server[name];
|
|
}
|
|
}
|
|
}
|
|
if (D.drops.maps.global) {
|
|
data.global = D.drops.maps.global;
|
|
register_table(data.global);
|
|
}
|
|
if (D.drops.maps.global_static) {
|
|
data.global_static = D.drops.maps.global_static;
|
|
register_table(data.global_static);
|
|
}
|
|
for (var name in data.drops) {
|
|
register_table(data.drops[name]);
|
|
}
|
|
for (var name in data.drops_home) {
|
|
//include home tables
|
|
register_table(data.drops_home[name]);
|
|
}
|
|
for (var name in data.maps) {
|
|
register_table(data.maps[name]);
|
|
}
|
|
for (var name in G.items) {
|
|
if (
|
|
G.items[name].e &&
|
|
(player.computer ||
|
|
1 ||
|
|
(player.p.stats.exchanges && player.p.stats.exchanges[name] && player.p.stats.exchanges[name] >= 100))
|
|
) {
|
|
if (G.items[name].upgrade || G.items[name].compound) {
|
|
for (var i = 0; i < 13; i++) {
|
|
if (D.drops[name + i]) {
|
|
data.tables[name + i] = D.drops[name + i];
|
|
register_table(data.tables[name + i]);
|
|
}
|
|
}
|
|
} else if (D.drops[name]) {
|
|
data.tables[name] = D.drops[name];
|
|
register_table(data.tables[name]);
|
|
}
|
|
}
|
|
}
|
|
socket.emit("tracker", data);
|
|
});
|
|
socket.on("set_home", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (player.p.dt.last_homeset && hsince(player.p.dt.last_homeset) < 36) {
|
|
return fail_response("sh_time", { hours: 36 - hsince(player.p.dt.last_homeset) });
|
|
}
|
|
player.p.dt.last_homeset = new Date();
|
|
player.p.home = region + server_name;
|
|
delete player.s.hopsickness;
|
|
success_response("home_set", { home: player.p.home });
|
|
});
|
|
socket.on("code", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.run) {
|
|
players[socket.id].code = true;
|
|
} else {
|
|
players[socket.id].code = false;
|
|
}
|
|
resend(players[socket.id], "u+cid");
|
|
});
|
|
socket.on("property", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.typing) {
|
|
if (!player.s.typing || player.s.typing.ms < 3000) {
|
|
reduce_call_cost();
|
|
}
|
|
player.s.typing = { ms: 4000 };
|
|
resend(player, "u+cid+nc");
|
|
} else {
|
|
var original = player.afk;
|
|
if (player.afk == "bot" || player.afk == "code") {
|
|
} else if (data.afk === true) {
|
|
player.afk = true;
|
|
} else if (data.afk === false) {
|
|
if (player.afk !== undefined) {
|
|
player.afk = false;
|
|
}
|
|
}
|
|
if (original !== player.afk) {
|
|
reduce_call_cost();
|
|
resend(player, "u+cid+nc");
|
|
}
|
|
}
|
|
});
|
|
socket.on("cruise", function (speed) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
player.cruise = parseInt(speed);
|
|
resend(player, "u+cid");
|
|
success_response("cruise", { speed: player.cruise });
|
|
});
|
|
socket.on("test", function (data) {
|
|
if (is_sdk) {
|
|
console.log(data.test);
|
|
}
|
|
socket.emit("test", { date: new Date() });
|
|
});
|
|
socket.on("blocker", function (data) {
|
|
if (data.type == "pvp") {
|
|
if (instances["arena"].allow) {
|
|
socket.emit("blocker", { type: "pvp", allow: 1 });
|
|
} else {
|
|
socket.emit("blocker", { type: "pvp" });
|
|
}
|
|
}
|
|
});
|
|
socket.on("mail_take_item", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (mode.prevent_external) {
|
|
return socket.emit("game_response", { response: "not_in_this_server" });
|
|
}
|
|
if (!player.esize) {
|
|
return socket.emit("game_response", "inv_size");
|
|
}
|
|
appengine_call(
|
|
"take_item_from_mail",
|
|
{ owner: player.owner, mid: data.id },
|
|
function (result) {
|
|
var player = players[socket.id];
|
|
if (result.failed) {
|
|
return socket.emit("game_response", { response: "mail_item_already_taken" });
|
|
}
|
|
var item = JSON.parse(result.item);
|
|
add_item(player, item, { announce: false });
|
|
resend(player, "reopen");
|
|
socket.emit("game_response", { response: "mail_item_taken" });
|
|
},
|
|
function () {
|
|
socket.emit("game_response", { response: "mail_take_item_failed" });
|
|
},
|
|
);
|
|
});
|
|
socket.on("mail", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (mode.prevent_external) {
|
|
return fail_response("not_in_this_server");
|
|
}
|
|
var item = null;
|
|
var retries = 1;
|
|
if (player.gold < 48000) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
player.gold -= 48000;
|
|
if (data.item && player.items[0] && player.items[0].name != "placeholder") {
|
|
if (player.items[0].l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (player.items[0].b || player.items[0].v) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
if (player.gold < 312000) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
player.gold -= 312000;
|
|
item = JSON.stringify(player.items[0]);
|
|
player.items[0] = player.citems[0] = null;
|
|
retries = 3;
|
|
}
|
|
appengine_call(
|
|
"send_mail",
|
|
{
|
|
fro: player.name,
|
|
to: data.to,
|
|
subject: data.subject || "",
|
|
message: data.message || "",
|
|
rid: randomStr(50),
|
|
retries: retries,
|
|
item: item,
|
|
},
|
|
function (result) {
|
|
var player = players[socket.id];
|
|
if (result.failed) {
|
|
if (player) {
|
|
socket.emit("game_response", {
|
|
response: "mail_failed",
|
|
to: data.to,
|
|
reason: result.reason,
|
|
cevent: "mail_failed",
|
|
});
|
|
}
|
|
if (player && item && result.return && player.esize) {
|
|
var r = JSON.parse(item);
|
|
add_item(player, r);
|
|
resend(player, "reopen");
|
|
} else {
|
|
console.log("#M unsent mail, lost item: " + item);
|
|
}
|
|
return;
|
|
}
|
|
if (player) {
|
|
socket.emit("game_response", { response: "mail_sent", to: data.to, cevent: "mail_sent" });
|
|
}
|
|
},
|
|
function () {
|
|
var player = players[socket.id];
|
|
if (player) {
|
|
socket.emit("game_response", { response: "mail_failed", reason: "coms_failure" });
|
|
}
|
|
if (item) {
|
|
console.log("#M unsent mail, lost item: " + item);
|
|
}
|
|
},
|
|
);
|
|
resend(player, "reopen");
|
|
success_response("mail_sending", { sucess: false, in_progress: true, received: "unknown" });
|
|
});
|
|
socket.on("leave", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!can_walk(player)) {
|
|
return fail_response("transport_failed");
|
|
}
|
|
if (
|
|
(0 && player.s.block) ||
|
|
player.targets > 5 ||
|
|
!(player.map == "jail" || player.map == "cyberland" || instances[player.in].solo)
|
|
) {
|
|
return fail_response("cant_escape");
|
|
}
|
|
transport_player_to(player, B.start_map);
|
|
success_response();
|
|
});
|
|
socket.on("transport", function (data) {
|
|
var player = players[socket.id];
|
|
var can_reach = false;
|
|
if (!player) {
|
|
return;
|
|
} // this was missing, probably caused a production exception - should be added everywhere [04/09/16]
|
|
if (!can_walk(player) || player.map == "jail") {
|
|
return fail_response("transport_failed");
|
|
}
|
|
var new_map = G.maps[data.to];
|
|
var s = data.s || 0;
|
|
var the_door = null;
|
|
if (!new_map || !instances[data.to] || !instances[data.to].allow) {
|
|
return fail_response("cant_enter");
|
|
}
|
|
if ((0 && player.s.block) || player.targets > 5) {
|
|
return fail_response("cant_escape");
|
|
}
|
|
|
|
(G.maps[player.map].doors || []).forEach(function (door) {
|
|
if (
|
|
!can_reach &&
|
|
door[4] == data.to &&
|
|
s == (door[5] || 0) &&
|
|
distance(
|
|
{
|
|
map: player.map,
|
|
x: G.maps[player.map].spawns[door[6]][0],
|
|
y: G.maps[player.map].spawns[door[6]][1],
|
|
width: door[2],
|
|
height: door[3],
|
|
},
|
|
player,
|
|
) < B.door_dist
|
|
) {
|
|
can_reach = "door";
|
|
the_door = door;
|
|
}
|
|
});
|
|
if ((player.map == "woffice" && gameplay == "hardcore") || player.role == "gm") {
|
|
can_reach = "transport";
|
|
}
|
|
if (
|
|
!can_reach &&
|
|
G.maps[player.map].ref.transporter &&
|
|
simple_distance(G.maps[player.map].ref.transporter, player) < B.transporter_dist &&
|
|
G.npcs.transporter.places[data.to] === s
|
|
) {
|
|
can_reach = "transport";
|
|
}
|
|
if (can_reach == "transport" && player.s.dampened) {
|
|
return fail_response("transport_cant_dampened");
|
|
}
|
|
if (!can_reach) {
|
|
return fail_response("transport_cant_reach");
|
|
}
|
|
if (the_door && the_door[7] == "protected") {
|
|
var protected = false;
|
|
for (var x in instances[player.in].monsters || {}) {
|
|
if (instances[player.in].monsters[x].map_def.gatekeeper) {
|
|
protected = true;
|
|
}
|
|
}
|
|
if (protected) {
|
|
return fail_response("transport_cant_protection");
|
|
}
|
|
}
|
|
if (
|
|
the_door &&
|
|
the_door[7] == "ulocked" &&
|
|
G.maps[data.to].mount &&
|
|
player.user &&
|
|
!player.user.unlocked[data.to]
|
|
) {
|
|
return fail_response("transport_cant_locked");
|
|
}
|
|
|
|
if (instances[data.to].mount && !player.user) {
|
|
if (player.mounting || player.unmounting) {
|
|
return fail_response("bank_opi");
|
|
}
|
|
add_call_cost(32, undefined, "bank");
|
|
player.mounting = new Date();
|
|
player.mount_to = data.to;
|
|
player.mount_s = s;
|
|
sync_loop();
|
|
return success_response({ success: false, in_progress: true });
|
|
} else if (!instances[data.to].mount && player.user) {
|
|
// previously handled at transport_player_to
|
|
if (player.mounting || player.unmounting) {
|
|
return fail_response("bank_opi");
|
|
}
|
|
add_call_cost(16, undefined, "bank");
|
|
player.unmounting = new Date();
|
|
player.unmount_to = data.to;
|
|
player.unmount_s = s;
|
|
sync_loop();
|
|
return success_response({ success: false, in_progress: true });
|
|
} else if (0 && instances[data.to].mount && player.user && data.to != player.map) {
|
|
// an easy prevention for the bank re-entry nuisance, bank can be re-entered physically but not digitally [03/11/16]
|
|
// commented out for bank_u / bank_b [06/05/20]
|
|
return fail_response("transport_failed");
|
|
} else {
|
|
decay_s(player, 5200);
|
|
transport_player_to(player, data.to, s);
|
|
return success_response();
|
|
}
|
|
});
|
|
socket.on("enter", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (gameplay != "normal") {
|
|
return fail_response("transport_failed");
|
|
}
|
|
if (!can_walk(player) || player.map == "jail") {
|
|
return fail_response("transport_failed");
|
|
}
|
|
if (player.s.block || player.targets > 5) {
|
|
return fail_response("cant_escape");
|
|
}
|
|
// if(player.role!="gm" && !(data.place==player.map && player.map=="cgallery"))
|
|
// {
|
|
// if(data.place!="resort" && !G.maps[player.map].ref.transporter || simple_distance(G.maps[player.map].ref.transporter,player)>80) return socket.emit("game_response","transport_cant_reach");
|
|
// if(data.place=="resort" && player.map!="resort") return socket.emit("game_response","transport_cant_reach");
|
|
// }
|
|
server_log(data);
|
|
var name = randomStr(24);
|
|
if (data.place == "resort" && 0) {
|
|
var name = "resort_" + data.name;
|
|
instance = instances[name] || create_instance(name, "resort_map");
|
|
transport_player_to(player, name);
|
|
} else if (data.place == "duelland") {
|
|
if (instances[data.name] && instances[data.name].map == "duelland") {
|
|
transport_player_to(player, data.name);
|
|
instance_emit(data.name, "game_log", { message: player.name + " is spectating the duel", color: "gray" });
|
|
} else {
|
|
return fail_response("cant_enter");
|
|
}
|
|
} else if (
|
|
data.place == "crypt" ||
|
|
data.place == "winter_instance" ||
|
|
data.place == "spider_instance" ||
|
|
data.place == "tomb"
|
|
) {
|
|
var f = "cave";
|
|
var ref = G.maps.cave.spawns[2];
|
|
var item = "cryptkey";
|
|
if (data.place == "winter_instance") {
|
|
f = "winterland";
|
|
ref = G.maps.winterland.spawns[5];
|
|
item = "frozenkey";
|
|
}
|
|
if (data.place == "spider_instance") {
|
|
f = "gateway";
|
|
ref = G.maps.gateway.spawns[3];
|
|
item = "spiderkey";
|
|
}
|
|
if (data.place == "tomb") {
|
|
f = "mansion";
|
|
ref = G.maps.mansion.spawns[1];
|
|
item = "tombkey";
|
|
}
|
|
if (simple_distance(player, { in: f, map: f, x: ref[0], y: ref[1] }) > 120) {
|
|
return fail_response("transport_cant_reach");
|
|
}
|
|
if (data.name) {
|
|
// Player requested to enter an existing instance
|
|
if (instances[data.name] && instances[data.name].map == data.place) {
|
|
// The instance exists
|
|
transport_player_to(player, data.name);
|
|
} else {
|
|
// The instance doesn't exist
|
|
return fail_response("transport_cant_invalid");
|
|
}
|
|
} else {
|
|
if (!consume_one_by_id(player, item)) {
|
|
return fail_response("transport_cant_item");
|
|
}
|
|
instance = create_instance(name, data.place);
|
|
transport_player_to(player, name);
|
|
}
|
|
resend(player, "u+cid+reopen");
|
|
} else if (data.place == "dungeon0" && player.role == "gm") {
|
|
instance = create_instance(name, "dungeon0", { solo: player.id });
|
|
transport_player_to(player, name);
|
|
} else if (data.place == "cgallery" && player.role == "gm") {
|
|
var point = null;
|
|
if (player.map == "cgallery") {
|
|
point = [player.x, player.y];
|
|
}
|
|
instance = create_instance(name, "cgallery", { solo: player.id });
|
|
transport_player_to(player, name, point);
|
|
var bodies = [];
|
|
var heads = [];
|
|
var hairs = [];
|
|
var wings = [];
|
|
var hats = [];
|
|
for (var s in G.sprites) {
|
|
var current = G.sprites[s];
|
|
var matrix = current.matrix;
|
|
if (current.skip || current.rskip) {
|
|
continue;
|
|
}
|
|
if (!in_arr(current.type, ["body", "head", "hair", "s_wings", "hat"])) {
|
|
continue;
|
|
}
|
|
for (var i = 0; i < matrix.length; i++) {
|
|
for (var j = 0; j < matrix[i].length; j++) {
|
|
if (!matrix[i][j]) {
|
|
continue;
|
|
}
|
|
if (current.type == "body") {
|
|
bodies.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "head") {
|
|
heads.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "hair") {
|
|
hairs.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "hat") {
|
|
hats.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "s_wings") {
|
|
wings.push(matrix[i][j]);
|
|
}
|
|
T[matrix[i][j]] = current.type;
|
|
}
|
|
}
|
|
}
|
|
var xs = [-80, -40, 0, 40, 80];
|
|
for (var n = 0; n < bodies.length; n++) {
|
|
var npc = create_npc(
|
|
{
|
|
name: bodies[n],
|
|
level: 1,
|
|
speed: 12,
|
|
hp: 1000,
|
|
skin: bodies[n],
|
|
cx: {},
|
|
},
|
|
{
|
|
position: [xs[n % xs.length], -parseInt(n / xs.length) * 40],
|
|
id: bodies[n],
|
|
},
|
|
instance,
|
|
);
|
|
npc.ctype = "body";
|
|
npc.cx = { head: heads[n] };
|
|
npc.id = "body: " + bodies[n];
|
|
instance.players[NPC_prefix + npc.id] = npc;
|
|
}
|
|
for (var n = 0; n < heads.length; n++) {
|
|
var npc = create_npc(
|
|
{ name: heads[n], level: 1, speed: 12, hp: 1000, skin: bodies[0], cx: {} },
|
|
{ position: [xs[n % xs.length] + 240, -parseInt(n / xs.length) * 40], id: heads[n] },
|
|
instance,
|
|
);
|
|
npc.ctype = "head";
|
|
npc.cx = { head: heads[n] };
|
|
npc.id = "head: " + heads[n];
|
|
instance.players[NPC_prefix + npc.id] = npc;
|
|
}
|
|
for (var n = 0; n < hairs.length; n++) {
|
|
var npc = create_npc(
|
|
{ name: hairs[n], level: 1, speed: 12, hp: 1000, skin: bodies[1], cx: {} },
|
|
{ position: [xs[n % xs.length] + 480, -parseInt(n / xs.length) * 40], id: hairs[n] },
|
|
instance,
|
|
);
|
|
npc.ctype = "hair";
|
|
npc.cx = { head: heads[0], hair: hairs[n] };
|
|
npc.id = "hair: " + hairs[n];
|
|
instance.players[NPC_prefix + npc.id] = npc;
|
|
}
|
|
for (var n = 0; n < hats.length; n++) {
|
|
var npc = create_npc(
|
|
{ name: hats[n], level: 1, speed: 12, hp: 1000, skin: bodies[2], cx: {} },
|
|
{ position: [xs[n % xs.length] + 720, -parseInt(n / xs.length) * 40], id: hats[n] },
|
|
instance,
|
|
);
|
|
npc.ctype = "hat";
|
|
npc.cx = { head: heads[0], hat: hats[n] };
|
|
npc.id = "hair: " + hats[n];
|
|
instance.players[NPC_prefix + npc.id] = npc;
|
|
}
|
|
} else {
|
|
return fail_response("transport_cant_reach");
|
|
}
|
|
success_response();
|
|
});
|
|
socket.on("town", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
// if(player.last.town && mssince(player.last.town)<1200) return; // bad ui experience [Unknown] - got reported and disabled [25/03/22]
|
|
if (!can_walk(player) || player.map == "jail") {
|
|
return fail_response("transport_failed");
|
|
}
|
|
if ((0 && player.s.block) || player.targets > 5) {
|
|
return fail_response("cant_escape");
|
|
}
|
|
player.c.town = { ms: min((player.c.town && player.c.town.ms) || 5000, 3000) };
|
|
resend(player, "u+nc");
|
|
success_response({ success: false, in_progress: true });
|
|
});
|
|
socket.on("respawn", function (data) {
|
|
const player = players[socket.id];
|
|
if (!player || !player.rip) {
|
|
return fail_response("invalid");
|
|
}
|
|
|
|
if (player.rip_time) {
|
|
const msRemaining = B.rip_time - mssince(player.rip_time);
|
|
if (msRemaining > 0) {
|
|
return fail_response("cant_respawn", { ms: msRemaining });
|
|
}
|
|
}
|
|
|
|
delete player.s.block;
|
|
player.hp = player.max_hp;
|
|
player.mp = round(player.max_mp / 2);
|
|
player.rip = false;
|
|
if (gameplay == "hardcore") {
|
|
reset_player(player, 1);
|
|
}
|
|
|
|
if (data && data.safe) {
|
|
// gameplay=="hardcore" &&
|
|
transport_player_to(player, "woffice", 0, 1);
|
|
} else {
|
|
var place = G.maps[player.map].on_death || G.maps[B.start_map].on_death || ["main", 0];
|
|
transport_player_to(player, place[0], place[1], 1);
|
|
}
|
|
if (player.party) {
|
|
send_party_update(player.party);
|
|
}
|
|
invincible_logic(player);
|
|
resend(player, "u+cid");
|
|
success_response("data", { place: "respawn", cevent: "respawn" });
|
|
});
|
|
socket.on("random_look", function (data) {
|
|
return socket.emit("game_log", "socket.emit('enter',{place:'cgallery'})");
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var bodies = [];
|
|
var heads = [];
|
|
var hairs = [];
|
|
var wings = [];
|
|
var hats = [];
|
|
if (player.rlooks == 25 && !is_sdk && player.role != "gm") {
|
|
return;
|
|
}
|
|
if (player.role == "gm") {
|
|
reduce_call_cost(40);
|
|
}
|
|
player.rlooks = (player.rlooks || 0) + 1;
|
|
for (var s in G.sprites) {
|
|
var current = G.sprites[s];
|
|
var matrix = current.matrix;
|
|
if (current.skip || current.rskip) {
|
|
continue;
|
|
}
|
|
if (!in_arr(current.type, ["body", "head", "hair", "s_wings", "hat"])) {
|
|
continue;
|
|
}
|
|
for (var i = 0; i < matrix.length; i++) {
|
|
for (var j = 0; j < matrix[i].length; j++) {
|
|
if (!matrix[i][j]) {
|
|
continue;
|
|
}
|
|
if (current.type == "body") {
|
|
bodies.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "head") {
|
|
heads.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "hair") {
|
|
hairs.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "hat") {
|
|
hats.push(matrix[i][j]);
|
|
}
|
|
if (current.type == "s_wings") {
|
|
wings.push(matrix[i][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var head = "head";
|
|
if (Math.random() < 0.1) {
|
|
head = random_one(heads);
|
|
}
|
|
player.tskin = random_one(bodies);
|
|
player.tcx = [head, random_one(hairs)];
|
|
if (Math.random() < 0.08) {
|
|
player.tcx.push(random_one(wings));
|
|
}
|
|
if (Math.random() < 0.4) {
|
|
player.tcx.push(random_one(hats));
|
|
}
|
|
resend(player, "u+cid");
|
|
});
|
|
socket.on("unlock", function (data) {
|
|
return; // [27/06/18]
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (gameplay == "normal") {
|
|
return;
|
|
} // ? [27/06/18]
|
|
if (data.name == "code") {
|
|
var item = player.items[data.num || 0];
|
|
if (!item || item.name != "computer" || item.charges === 0 || item.charges < 0 || player.unlocking_code) {
|
|
return;
|
|
}
|
|
if (!item.charges) {
|
|
item.charges = 2;
|
|
}
|
|
item.charges--;
|
|
player.citems[data.num || 0] = player.items[data.num || 0]; // explicitly linked to the actual item
|
|
player.unlocking_code = true;
|
|
appengine_call(
|
|
"user_operation",
|
|
{ auth: player.auth, operation: "code_unlock", suffix: "/code_unlock/" + player.owner, retries: 4 },
|
|
function (result) {
|
|
server_log("user_operation_code: " + JSON.stringify(result), 1);
|
|
if (result.failed) {
|
|
if (result.reason == "already") {
|
|
socket.emit(
|
|
"game_log",
|
|
"Your CODE slots are already unlocked. You can lend your Ancient Computer to a friend in need, that's why there are 2 charges :]",
|
|
);
|
|
item.charges++;
|
|
} else {
|
|
socket.emit("game_log", "Unlock Failed. Email hello@adventure.land with a screenshot.");
|
|
}
|
|
return;
|
|
}
|
|
socket.emit("game_log", "CODE slots increased to 100!");
|
|
resend(player, "reopen");
|
|
},
|
|
);
|
|
}
|
|
});
|
|
socket.on("dismantle", function (data) {
|
|
var player = players[socket.id];
|
|
var check = true;
|
|
var items = [];
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!player.computer && simple_distance(G.maps.main.ref.craftsman, player) > B.sell_dist) {
|
|
return fail_response("distance");
|
|
}
|
|
// if(player.esize<=0) return socket.emit("game_response","inventory_full");
|
|
var item = player.items[data.num];
|
|
if (item && item.level && G.items[item.name].compound) {
|
|
var cost = min(50000000, calculate_item_value(item) * 10);
|
|
if (player.gold < cost) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
if (player.esize < 2) {
|
|
return fail_response("inv_size");
|
|
}
|
|
if (G.items[item.name].type == "booster") {
|
|
return fail_response("dismantle_cant");
|
|
}
|
|
player.gold -= cost;
|
|
consume_one(player, data.num);
|
|
add_item(
|
|
player,
|
|
{ name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) },
|
|
{ announce: false },
|
|
);
|
|
add_item(
|
|
player,
|
|
{ name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) },
|
|
{ announce: false },
|
|
);
|
|
add_item(
|
|
player,
|
|
{ name: item.name, level: item.level - 1, grace: parseInt((item.grace || 0) / 3) },
|
|
{ announce: false },
|
|
);
|
|
resend(player, "reopen+nc+inv");
|
|
return success_response("dismantle", { name: item.name, level: item.level, cost: cost, cevent: true });
|
|
}
|
|
if (!item || !G.dismantle[item.name]) {
|
|
return fail_response("dismantle_cant");
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
if (player.gold < G.dismantle[item.name].cost) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
if (
|
|
!can_add_items(player, list_to_pseudo_items(G.dismantle[item.name].items), {
|
|
space: ((item.q || 1) == 1 && 1) || 0,
|
|
})
|
|
) {
|
|
return fail_response("inv_size");
|
|
}
|
|
player.gold -= G.dismantle[item.name].cost;
|
|
consume_one(player, data.num);
|
|
G.dismantle[item.name].items.forEach(function (e) {
|
|
if (e[0] < 1 && Math.random() > e[0]) {
|
|
return;
|
|
}
|
|
add_item(player, e[1], { q: max(1, e[0]), p: item.p && !G.titles[item.p].misc && item.p });
|
|
});
|
|
resend(player, "reopen+nc+inv");
|
|
success_response("dismantle", { name: item.name, cevent: true });
|
|
});
|
|
socket.on("craft", function (data) {
|
|
var player = players[socket.id];
|
|
var check = true;
|
|
var items = [];
|
|
var quantity = {};
|
|
var place = {};
|
|
var space = false;
|
|
var p = {};
|
|
var locked = false;
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
data.items.forEach(function (x) {
|
|
if (!player.items[x[1]]) {
|
|
check = false;
|
|
} else if (player.items[x[1]].l || player.items[x[1]].b) {
|
|
check = false;
|
|
locked = true;
|
|
} else {
|
|
var name = player.items[x[1]].name;
|
|
if (player.items[x[1]].level) {
|
|
name += "+" + player.items[x[1]].level;
|
|
}
|
|
items.push(name);
|
|
quantity[player.items[x[1]].name] = player.items[x[1]].q || 1; // (quantity[player.items[x[1]].name]||0)+ removed this part, seems like a bad initial addition [17/06/18]
|
|
place[player.items[x[1]].name] = x[1];
|
|
if (player.items[x[1]].p && !G.titles[player.items[x[1]].p].misc) {
|
|
p[player.items[x[1]].p] = player.items[x[1]].p;
|
|
}
|
|
}
|
|
});
|
|
if (locked) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (!check) {
|
|
return fail_response("no_item");
|
|
}
|
|
//if(items.length<2) return socket.emit("game_response","craft_atleast2");
|
|
items.sort();
|
|
var key = items.join(",");
|
|
// console.log(key);
|
|
if (!D.craftmap[key]) {
|
|
return fail_response("craft_cant");
|
|
}
|
|
var name = D.craftmap[key];
|
|
var enough = true;
|
|
if (
|
|
!player.computer &&
|
|
!G.craft[name].quest &&
|
|
simple_distance(get_npc_coords("craftsman"), player) > B.sell_dist
|
|
) {
|
|
return fail_response("distance");
|
|
}
|
|
if (
|
|
!player.computer &&
|
|
G.craft[name].quest &&
|
|
simple_distance(get_npc_coords(G.craft[name].quest), player) > B.sell_dist
|
|
) {
|
|
return fail_response("distance");
|
|
}
|
|
if (player.gold < G.craft[name].cost) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
G.craft[name].items.forEach(function (i) {
|
|
if (quantity[i[1]] < i[0]) {
|
|
enough = false;
|
|
}
|
|
if (quantity[i[1]] == i[0]) {
|
|
space = true;
|
|
}
|
|
});
|
|
if (!space && !can_add_item(player, create_new_item(name))) {
|
|
return fail_response("inventory_full");
|
|
}
|
|
if (!enough) {
|
|
return fail_response("craft_cant_quantity");
|
|
}
|
|
player.gold -= G.craft[name].cost;
|
|
G.craft[name].items.forEach(function (x) {
|
|
consume(player, place[x[1]], x[0]);
|
|
});
|
|
var i = add_item(player, name, { r: 1, p: Object.keys(p).length && random_one(p) });
|
|
resend(player, "reopen+nc+inv");
|
|
success_response("craft", { num: i, name: name, cevent: true });
|
|
});
|
|
socket.on("exchange", function (data) {
|
|
const player = players[socket.id];
|
|
if (!player) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (player.q.exchange) {
|
|
return fail_response("exchange_existing");
|
|
}
|
|
if (player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
|
|
const item = player.items[data.item_num];
|
|
if (!item) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (player.esize <= 0 && !((item.q || 1) == 1)) {
|
|
// TODO: If there are no items in the drop table, we could add support to exchange it
|
|
return fail_response("inventory_full");
|
|
}
|
|
const def = G.items[item.name];
|
|
const suffix = def && (def.compound || def.upgrade) ? item.level || 0 : "";
|
|
const dropId = item.name + suffix;
|
|
if (!def || !def.e || !D.drops[dropId]) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!player.computer) {
|
|
const dist = distance(player, def.quest ? G.quests[def.quest] : G.maps.main.exchange);
|
|
if (dist > B.sell_dist) {
|
|
return fail_response("distance");
|
|
}
|
|
}
|
|
if (def.e > 1 && item.q < def.e) {
|
|
return fail_response("exchange_notenough");
|
|
}
|
|
player.p.stats.exchanges[dropId] = (player.p.stats.exchanges[dropId] || 0) + 1;
|
|
consume(player, data.item_num, def.e);
|
|
const num = add_item(player, "placeholder");
|
|
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) {
|
|
const ms = -mssince(player.last.potion);
|
|
if (ms > 0) {
|
|
return fail_response("not_ready", { ms: ms });
|
|
}
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
var timeout = 2000;
|
|
var timeout_ui = null;
|
|
var xp = false;
|
|
consume_one(player, data.num);
|
|
(def.gives || []).forEach(function (p) {
|
|
var amount = p[1];
|
|
if (player.s.poisoned) {
|
|
amount = round(amount / 2);
|
|
}
|
|
player[p[0]] = (player[p[0]] || 0) + amount;
|
|
player[p[0]] = max(1, player[p[0]]);
|
|
if (p[0] == "hp") {
|
|
if (amount > 0) {
|
|
disappearing_text(socket, player, "+" + amount, { color: "hp", xy: 1, s: "hp" });
|
|
} else {
|
|
disappearing_text(socket, player, amount, { color: "hp", xy: 1, s: "hp" });
|
|
}
|
|
}
|
|
if (p[0] == "mp") {
|
|
disappearing_text(socket, player, "+" + amount, { color: "mp", xy: 1, s: "mp" });
|
|
}
|
|
if (p[0] == "xp") {
|
|
disappearing_text(socket, player, "+1M", { color: "1mxp", xy: 1, s: "xp", size: "large" });
|
|
xp = true;
|
|
timeout_ui = 120;
|
|
timeout = 0.1;
|
|
}
|
|
});
|
|
if (def.debuff) {
|
|
for (var c in player.s) {
|
|
if (G.conditions[c] && G.conditions[c].debuff && (!G.conditions[c].persistent || c == "hopsickness")) {
|
|
delete player.s[c];
|
|
}
|
|
}
|
|
}
|
|
player.hp = min(player.hp, player.max_hp);
|
|
player.mp = min(player.mp, player.max_mp);
|
|
if (def.cooldown) {
|
|
timeout = def.cooldown;
|
|
}
|
|
player.last.potion = future_ms(timeout);
|
|
if (!xp) {
|
|
to_update = "u+cid+reopen+nc";
|
|
}
|
|
socket.emit("eval", { code: "pot_timeout(" + (timeout_ui || timeout) + ")" });
|
|
} else if (!data.consume) {
|
|
// console.log(data);
|
|
var slot = data.slot || def.type;
|
|
var existing;
|
|
var comp = can_equip_item(player, def, slot);
|
|
if (comp == "no") {
|
|
resend(player, "u+cid+reopen"); // if you drop something on an invalid slot, it stays there for a bit otherwise [29/08/22]
|
|
return fail_response("cant_equip");
|
|
}
|
|
slot = comp;
|
|
existing = player.slots[slot];
|
|
player.slots[slot] = player.items[data.num];
|
|
player.cslots[slot] = cache_item(player.slots[slot]);
|
|
if (existing && existing.b) {
|
|
existing = null;
|
|
}
|
|
player.items[data.num] = existing;
|
|
player.citems[data.num] = cache_item(existing);
|
|
player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 120, 120000) };
|
|
resolve.slot = slot;
|
|
} else {
|
|
return fail_response("cant_consume");
|
|
}
|
|
|
|
if (to_update) {
|
|
resend(player, to_update);
|
|
}
|
|
success_response(resolve_type, resolve);
|
|
});
|
|
socket.on("misc_npc", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var npc = players[data.npc];
|
|
if (!npc || !npc.npc) {
|
|
return;
|
|
}
|
|
if (simple_distance(player, npc) > 1000) {
|
|
return socket.emit("game_response", "distance");
|
|
}
|
|
if (data.npc == NPC_prefix + "Marven" && npc.misc == true) {
|
|
server_log("Here!");
|
|
}
|
|
});
|
|
socket.on("unequip", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player || !data.slot || !player.slots[data.slot]) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (data.slot == "elixir") {
|
|
return fail_response("cant");
|
|
}
|
|
var item = player.slots[data.slot];
|
|
var done = false;
|
|
if (in_arr(data.slot, trade_slots) && item.giveaway !== undefined) {
|
|
return fail_response("giveaway");
|
|
}
|
|
// an oversight allowed .giveaway items to be equipped, so now they can be unequipped from regular slots [04/11/21]
|
|
if (player.esize <= 0 && !item.b) {
|
|
return fail_response("no_space");
|
|
}
|
|
player.slots[data.slot] = null;
|
|
player.cslots[data.slot] = null;
|
|
player.c = {};
|
|
if (!item.b) {
|
|
add_item(player, item, { announce: false });
|
|
}
|
|
resend(player, "reopen+u+cid");
|
|
success_response("data");
|
|
});
|
|
socket.on("secondhands", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (simple_distance(G.maps.main.ref.secondhands, player) > 500) {
|
|
return socket.emit("game_response", "distance");
|
|
}
|
|
socket.emit("secondhands", csold);
|
|
});
|
|
socket.on("lostandfound", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data == "info") {
|
|
return socket.emit("game_response", { response: "lostandfound_info", gold: S.gold });
|
|
}
|
|
if (!player.donation) {
|
|
return socket.emit("game_response", "lostandfound_donate");
|
|
}
|
|
if (simple_distance(G.maps.woffice.ref.lostandfound, player) > 500) {
|
|
return socket.emit("game_response", "distance");
|
|
}
|
|
socket.emit("lostandfound", cfound);
|
|
});
|
|
socket.on("split", function (data) {
|
|
const player = players[socket.id];
|
|
const item = player.items[data.num];
|
|
if (!item) {
|
|
return fail_response("no_item");
|
|
}
|
|
if (!G.items[item.name].s) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!player.esize) {
|
|
return fail_response("cant_space");
|
|
}
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
|
|
let quantity = min(max(parseInt(data.quantity) || 0, 1), (item && item.q) || 1);
|
|
quantity = min(quantity, G.items[item.name].s);
|
|
if (quantity >= item.q) {
|
|
// They requested to split the whole stack...
|
|
return success_response({ from: data.num, to: data.num, q: quantity });
|
|
}
|
|
|
|
consume(player, data.num, quantity);
|
|
|
|
const new_item = cache_item(item);
|
|
if (item.q) {
|
|
new_item.q = quantity;
|
|
}
|
|
|
|
let num;
|
|
for (let i = 0; i < player.isize; i++) {
|
|
if (player.items[i]) {
|
|
continue;
|
|
}
|
|
player.items[i] = new_item;
|
|
player.citems[i] = cache_item(new_item);
|
|
num = i;
|
|
break;
|
|
}
|
|
resend(player, "reopen");
|
|
success_response({ from: data.num, to: num, q: quantity });
|
|
});
|
|
socket.on("sell", function (data) {
|
|
var player = players[socket.id];
|
|
var num = data.num;
|
|
var item = player.items[data.num];
|
|
var quantity = min(max(parseInt(data.quantity) || 0, 1), (item && item.q) || 1);
|
|
var can_reach = false;
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!item) {
|
|
return fail_response("no_item");
|
|
}
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
(G.maps[player.map].merchants || []).forEach(function (m) {
|
|
if (simple_distance(player, m) < B.sell_dist) {
|
|
can_reach = m;
|
|
}
|
|
});
|
|
if (player.computer && !can_reach) {
|
|
can_reach = G.maps.main.merchants[0];
|
|
}
|
|
if (!can_reach) {
|
|
return fail_response("distance");
|
|
}
|
|
can_reach.name = player.name;
|
|
var value = calculate_item_value(item);
|
|
consume(player, data.num, quantity);
|
|
player.gold += value * quantity;
|
|
var new_item = cache_item(item);
|
|
if (item.q) {
|
|
new_item.q = quantity;
|
|
}
|
|
xy_emit(can_reach, "ui", {
|
|
type: "-$",
|
|
id: can_reach.id,
|
|
name: player.name,
|
|
item: new_item,
|
|
num: num,
|
|
event: "sell",
|
|
});
|
|
resend(player, "reopen+nc+inv");
|
|
success_response("gold_received", { gold: value * quantity, item: new_item, cevent: "sell" });
|
|
secondhands_logic(item, quantity); // In the end, so if this fails, the "sell" still succeeds - otherwise it could cause a infinite gold loophole [10/07/18]
|
|
});
|
|
socket.on("buy_shells", function (data) {
|
|
return socket.emit("game_log", "No longer possible");
|
|
var player = players[socket.id];
|
|
if (!player || player.user || gameplay == "hardcore" || gameplay == "test") {
|
|
return game_response("cant_in_bank");
|
|
}
|
|
var gold = parseInt(data.gold) || 0;
|
|
if (gold < 1000000 || gold > player.gold) {
|
|
return socket.emit("game_response", "not_enough_gold");
|
|
}
|
|
var shells = Math.floor(gold / G.multipliers.shells_to_gold);
|
|
player.gold -= gold;
|
|
socket.emit("game_log", "Gave " + to_pretty_num(gold) + " gold");
|
|
appengine_call(
|
|
"bill_user",
|
|
{ auth: player.auth, amount: -shells, reason: "buy_shells", name: player.name },
|
|
function (result) {
|
|
server_log("buy_with_cash: " + JSON.stringify(result));
|
|
if (result.failed || !result.done) {
|
|
socket.emit("game_log", "Purchase failed");
|
|
return;
|
|
}
|
|
player.cash = result.cash;
|
|
socket.emit("game_log", "Received " + to_pretty_num(shells) + " shells");
|
|
|
|
resend(player, "reopen+nc");
|
|
},
|
|
);
|
|
resend(player, "reopen+nc");
|
|
});
|
|
socket.on("buy_with_cash", function (data) {
|
|
var player = players[socket.id];
|
|
var name = data.name;
|
|
var def = G.items[data.name];
|
|
var quantity = min(max(parseInt(data.quantity) || 0, 1), (def && def.s) || 9999);
|
|
if (!player || player.user || gameplay == "hardcore" || gameplay == "test") {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
var cost = def.cash * quantity;
|
|
if (!def.cash || def.ignore) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (def.p2w) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!def.s) {
|
|
quantity = 1;
|
|
}
|
|
if (!can_add_item(player, create_new_item(name, quantity))) {
|
|
return fail_response("no_space");
|
|
}
|
|
appengine_call(
|
|
"bill_user",
|
|
{ auth: player.auth, amount: cost, reason: data.name, name: player.name },
|
|
function (result) {
|
|
server_log("buy_with_cash: " + JSON.stringify(result));
|
|
if (result.failed || !result.done) {
|
|
socket.emit("game_log", "Purchase failed");
|
|
return;
|
|
}
|
|
player.cash = result.cash;
|
|
var new_item = create_new_item(name, quantity);
|
|
var done = false;
|
|
add_item(player, new_item, { announce: false });
|
|
socket.emit("game_log", "Spent " + to_pretty_num(cost) + " shells");
|
|
|
|
resend(player, "reopen+nc+inv");
|
|
},
|
|
);
|
|
success_response({ success: false, in_progress: true });
|
|
});
|
|
socket.on("bless_server", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player || player.user || gameplay == "hardcore" || gameplay == "test") {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
var cost = 1200;
|
|
if (player.blessing) return fail_response("in_progress");
|
|
player.blessing = true;
|
|
appengine_call(
|
|
"bill_user",
|
|
{ auth: player.auth, amount: cost, reason: "blessing", name: player.name },
|
|
function (result) {
|
|
player.blessing = false;
|
|
server_log("buy_with_cash: " + JSON.stringify(result));
|
|
if (result.failed || !result.done) {
|
|
socket.emit("game_log", "Purchase failed");
|
|
socket.emit("game_response", "blessed_fail");
|
|
return;
|
|
}
|
|
player.cash = result.cash;
|
|
|
|
S.blessed_minutes = 60 * 24 * 3;
|
|
S.blessed_by = player.name;
|
|
|
|
socket.emit("game_log", "Spent " + to_pretty_num(cost) + " shells");
|
|
socket.emit("game_response", "blessed");
|
|
|
|
resend(player, "reopen+nc+inv");
|
|
bless_loop();
|
|
|
|
discord_call(player.name + " blessed " + region + " " + server_name);
|
|
|
|
realm_broadcast("server_message", {
|
|
message: player.name + " blessed " + region + " " + server_name,
|
|
color: "#8F70D8",
|
|
});
|
|
},
|
|
);
|
|
success_response({ success: false, in_progress: true });
|
|
});
|
|
socket.on("sbuy", function (data) {
|
|
var player = players[socket.id];
|
|
var done = false;
|
|
var npc = G.maps.main.ref.secondhands;
|
|
var c = S.sold;
|
|
var cc = csold;
|
|
var e = "+$p";
|
|
var ev = "secondhands";
|
|
var mult = 2;
|
|
var src = "scnd";
|
|
if (data.f) {
|
|
npc = G.maps.woffice.ref.lostandfound;
|
|
c = S.found;
|
|
cc = cfound;
|
|
e = "+$f";
|
|
ev = "lostandfound";
|
|
mult = 4;
|
|
src = "lost";
|
|
}
|
|
if (!player || player.user) {
|
|
return game_response("cant_in_bank");
|
|
}
|
|
if (player.s.hopsickness && ev == "lostandfound") {
|
|
return fail_response("cant_when_sick", { goblin: true });
|
|
}
|
|
if (simple_distance(npc, player) > 500) {
|
|
return socket.emit("game_response", "distance");
|
|
}
|
|
for (var i = 0; i < c.length; i++) {
|
|
if (c[i].rid == data.rid) {
|
|
if (mult == 2 && G.items[c[i].name].cash) {
|
|
mult = 3;
|
|
}
|
|
var gold = calculate_item_value(c[i]) * mult * (c[i].q || 1);
|
|
var item = c[i];
|
|
if (!can_add_item(player, c[i])) {
|
|
return disappearing_text(socket, player, "NO SPACE");
|
|
}
|
|
if (gold > player.gold) {
|
|
return socket.emit("game_response", "buy_cost");
|
|
}
|
|
player.gold -= gold;
|
|
item.src = src;
|
|
add_item(player, c[i], { announce: false });
|
|
c.splice(i, 1);
|
|
cc.splice(i, 1);
|
|
socket.emit("game_log", "Spent " + to_pretty_num(gold) + " gold");
|
|
resend(player, "reopen+nc+inv");
|
|
socket.emit(ev, cc);
|
|
done = true;
|
|
xy_emit(npc, "ui", { type: e, id: npc.id, name: player.name, item: cache_item(item, 1) });
|
|
break;
|
|
}
|
|
}
|
|
if (!done) {
|
|
return socket.emit("game_log", "Item gone");
|
|
}
|
|
});
|
|
socket.on("buy", function (data) {
|
|
var player = players[socket.id];
|
|
var can_reach = false;
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
var name = data.name;
|
|
var quantity = min(max(parseInt(data.quantity) || 0, 1), (G.items[name] && G.items[name].s) || 9999);
|
|
var cost = 0;
|
|
var added = false;
|
|
var done = false;
|
|
if (!can_buy[name] && gameplay != "test") {
|
|
return fail_response("buy_cant_npc");
|
|
}
|
|
if (!can_add_item(player, create_new_item(name, quantity))) {
|
|
return fail_response("buy_cant_space");
|
|
}
|
|
(G.maps[player.map].items[name] || []).forEach(function (l) {
|
|
if (simple_distance(player, l) < B.sell_dist) {
|
|
can_reach = l;
|
|
}
|
|
});
|
|
if (player.computer && !can_reach) {
|
|
can_reach = G.maps.main.merchants[0];
|
|
}
|
|
if (!can_reach) {
|
|
return fail_response("distance");
|
|
}
|
|
can_reach.name = player.name;
|
|
var def = G.items[name];
|
|
if (!def.s) {
|
|
quantity = 1;
|
|
}
|
|
cost = quantity * G.items[name].g;
|
|
if (G.items[name].p2w) {
|
|
cost *= G.inflation;
|
|
}
|
|
if (player.gold < cost) {
|
|
return fail_response("buy_cost");
|
|
}
|
|
var new_item = create_new_item(name, quantity);
|
|
player.gold -= cost;
|
|
var num = add_item(player, new_item, { announce: false });
|
|
xy_emit(can_reach, "ui", {
|
|
type: "+$",
|
|
id: can_reach.id,
|
|
name: player.name,
|
|
item: cache_item(new_item),
|
|
event: "buy",
|
|
});
|
|
|
|
resend(player, "reopen+nc+inv");
|
|
success_response("buy_success", { cost: cost, num: num, name: name, q: quantity, cevent: "buy" });
|
|
});
|
|
socket.on("send", function (data) {
|
|
var player = players[socket.id];
|
|
var receiver = players[name_to_id[data.name || ""]];
|
|
var num;
|
|
var s_item;
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!receiver || receiver.user) {
|
|
return fail_response("receiver_unavailable");
|
|
}
|
|
if (distance(receiver, player, true) > B.dist || receiver.map != player.map) {
|
|
return fail_response("distance");
|
|
}
|
|
if (data.num !== undefined) {
|
|
data.num = max(0, parseInt(data.num) || 0);
|
|
var item = player.items[data.num];
|
|
if (!item) {
|
|
return fail_response("send_no_item");
|
|
}
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (item.l || (item.acl && receiver.owner != player.owner)) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
data.q = min(item.q || 1, max(1, parseInt(data.q || 1) || 1));
|
|
if (!data.q) {
|
|
return fail_response("no_item");
|
|
}
|
|
if (!can_add_item(receiver, create_new_item(item.name, data.q))) {
|
|
return fail_response("send_no_space");
|
|
}
|
|
if ((item.q || 1) == data.q) {
|
|
player.items[data.num] = player.citems[data.num] = null;
|
|
player.esize++;
|
|
} else {
|
|
player.items[data.num].q -= data.q;
|
|
player.citems[data.num] = cache_item(player.items[data.num]);
|
|
}
|
|
|
|
if (item.q) {
|
|
s_item = create_new_sitem(item, data.q);
|
|
num = add_item(receiver, s_item, { announce: false });
|
|
} else {
|
|
s_item = item;
|
|
num = add_item(receiver, item, { announce: false });
|
|
}
|
|
|
|
if (receiver.owner != player.owner) {
|
|
item.src = "snd";
|
|
}
|
|
|
|
add_to_history(player, { name: "item", to: receiver.name, item: item.name, q: data.q, level: item.level });
|
|
add_to_history(receiver, { name: "item", from: player.name, item: item.name, q: data.q, level: item.level });
|
|
|
|
xy_emit(player, "ui", {
|
|
type: "item_sent",
|
|
receiver: receiver.name,
|
|
sender: player.name,
|
|
item: cache_item(s_item, null, { q: data.q }),
|
|
num: num,
|
|
fnum: data.num,
|
|
event: true,
|
|
});
|
|
|
|
resend(player, "reopen+nc+inv");
|
|
resend(receiver, "reopen+nc+inv");
|
|
receiver.socket.emit("game_response", {
|
|
response: "item_received",
|
|
name: player.name,
|
|
item: item.name,
|
|
q: data.q,
|
|
num: num,
|
|
cevent: true,
|
|
});
|
|
player.socket.emit("game_response", {
|
|
response: "item_sent",
|
|
name: receiver.name,
|
|
item: item.name,
|
|
q: data.q,
|
|
num: data.num,
|
|
cevent: true,
|
|
place: "send",
|
|
});
|
|
} else if (data.gold !== undefined) {
|
|
data.gold = min(player.gold, max(1, parseInt(data.gold || 1) || 1)) || player.gold;
|
|
if (!data.gold) {
|
|
return fail_response("invalid");
|
|
}
|
|
player.gold -= data.gold;
|
|
if (!is_same(player, receiver) && data.gold != 1) {
|
|
data.gold = parseInt(data.gold * 0.975);
|
|
}
|
|
receiver.gold += data.gold;
|
|
|
|
add_to_history(player, { name: "gold", to: receiver.name, amount: data.gold });
|
|
add_to_history(receiver, { name: "gold", from: player.name, amount: data.gold });
|
|
|
|
xy_emit(player, "ui", {
|
|
type: "gold_sent",
|
|
receiver: receiver.name,
|
|
sender: player.name,
|
|
gold: data.gold,
|
|
event: true,
|
|
});
|
|
|
|
resend(player, "reopen+nc+inv");
|
|
resend(receiver, "reopen+nc+inv");
|
|
receiver.socket.emit("game_response", {
|
|
response: "gold_received",
|
|
name: player.name,
|
|
gold: data.gold,
|
|
cevent: true,
|
|
});
|
|
player.socket.emit("game_response", {
|
|
response: "gold_sent",
|
|
name: receiver.name,
|
|
gold: data.gold,
|
|
cevent: true,
|
|
place: "send",
|
|
});
|
|
} else if (data.cx !== undefined) {
|
|
var cxl = map_cx(player);
|
|
var count = all_cx(player, 1);
|
|
if (!(cxl[data.cx] && player.p.acx[cxl[data.cx]] && count[data.cx] > 0)) {
|
|
return fail_response("send_no_cx");
|
|
}
|
|
// count[cxl[data.cx]] before [28/01/22]
|
|
if (receiver.owner != player.owner) {
|
|
return fail_response("send_diff_owner");
|
|
}
|
|
player.p.acx[cxl[data.cx]] -= 1;
|
|
if (!player.p.acx[cxl[data.cx]]) {
|
|
delete player.p.acx[cxl[data.cx]];
|
|
}
|
|
receiver.p.acx[cxl[data.cx]] = (receiver.p.acx[cxl[data.cx]] || 0) + 1;
|
|
|
|
xy_emit(player, "ui", {
|
|
type: "cx_sent",
|
|
receiver: receiver.name,
|
|
sender: player.name,
|
|
cx: data.cx,
|
|
event: true,
|
|
});
|
|
|
|
receiver.socket.emit("game_response", {
|
|
response: "cx_received",
|
|
name: player.name,
|
|
cx: data.cx,
|
|
acx: receiver.p.acx,
|
|
cevent: true,
|
|
});
|
|
player.socket.emit("game_response", {
|
|
response: "cx_sent",
|
|
name: receiver.name,
|
|
cx: data.cx,
|
|
acx: player.p.acx,
|
|
cevent: true,
|
|
place: "send",
|
|
});
|
|
}
|
|
});
|
|
socket.on("donate", function (data) {
|
|
var player = players[socket.id];
|
|
var XPX = 3.2;
|
|
if (!player || player.user) {
|
|
return game_response("cant_in_bank");
|
|
}
|
|
var gold = max(1, min(parseInt(data.gold) || 0, 1000000000));
|
|
if (gold > player.gold) {
|
|
return socket.emit("game_response", "gold_not_enough");
|
|
}
|
|
if (gold >= 1000000) {
|
|
response = "thx";
|
|
player.donation = true;
|
|
} else if (gold < 100000) {
|
|
response = "low";
|
|
} else {
|
|
add_item(player, "gum");
|
|
response = "gum";
|
|
}
|
|
if (S.gold < 500000000) {
|
|
XPX = 4.8;
|
|
} else if (S.gold <= 1000000000) {
|
|
XPX = 4;
|
|
}
|
|
player.gold -= gold;
|
|
S.gold += gold;
|
|
if (gold >= 5000000) {
|
|
lstack(S.logs.donate, { name: player.name, gold: gold, xp: XPX });
|
|
}
|
|
if (player.type == "merchant") {
|
|
player.xp += parseInt(gold * XPX);
|
|
}
|
|
resend(player, "reopen");
|
|
socket.emit("game_response", { response: "donate_" + response, gold: gold, xprate: XPX });
|
|
});
|
|
socket.on("destroy", function (data) {
|
|
var player = players[socket.id];
|
|
var add = "+nc+inv";
|
|
data.num = max(0, parseInt(data.num) || 0);
|
|
if (!player.items[data.num]) {
|
|
return fail_response("no_item", { num: data.num });
|
|
}
|
|
var item = player.items[data.num];
|
|
var name = player.items[data.num].name;
|
|
data.q = min(max(parseInt(data.q) || 0, 1), (item && item.q) || 1);
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder", { num: data.num });
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked", { num: data.num });
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked", { num: data.num });
|
|
}
|
|
if (item.level != 13) {
|
|
consume(player, data.num, data.q);
|
|
//player.items[data.num]=player.citems[data.num]=null;
|
|
}
|
|
if (data.statue && distance(player, G.maps.spookytown.ref.poof) < B.sell_dist) {
|
|
var grade = (calculate_item_grade(G.items[name]) || 0) + 1;
|
|
if (item.name == "shadowstone") {
|
|
add = "+u+cid";
|
|
player.s.invis = { ms: 99999 };
|
|
}
|
|
if (
|
|
G.items[item.name].upgrade &&
|
|
Math.random() < 1.0 / ((gameplay == "hardcore" && 10000) || 3000013 * grade * grade)
|
|
) {
|
|
add = "+u+cid";
|
|
item.level = 13;
|
|
player.items[data.num] = item;
|
|
player.citems[data.num] = cache_item(player.items[data.num]);
|
|
|
|
const announce = !item.silent;
|
|
|
|
if (announce && !player.stealth) {
|
|
broadcast("server_message", {
|
|
message: player.name + " received " + item_to_phrase(item),
|
|
color: colors.server_success,
|
|
item: cache_item(item),
|
|
type: "server_usuccess",
|
|
name: player.name,
|
|
});
|
|
}
|
|
}
|
|
xy_emit(G.maps.spookytown.ref.poof, "upgrade", { type: "poof", success: 1 });
|
|
}
|
|
resend(player, "reopen" + add);
|
|
success_response("destroyed", { name: name, num: data.num, cevent: "destroy" });
|
|
});
|
|
socket.on("join_giveaway", function (data) {
|
|
var player = players[socket.id];
|
|
var seller = players[id_to_id[data.id]];
|
|
var num;
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!in_arr(data.slot, trade_slots)) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!seller || seller.npc || is_invis(seller)) {
|
|
return fail_response("seller_gone");
|
|
}
|
|
if (seller.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (distance(seller, player, true) > B.dist || seller.map != player.map) {
|
|
return fail_response("distance");
|
|
}
|
|
if (seller.id == player.id) {
|
|
return fail_response("hmm");
|
|
}
|
|
var item = seller.slots[data.slot];
|
|
if (!item || (data.rid && item.rid != data.rid)) {
|
|
return fail_response("item_gone");
|
|
}
|
|
if (!item.giveaway) {
|
|
return fail_response("sneaky");
|
|
}
|
|
if (!player.auth_id) {
|
|
return fail_response("need_auth");
|
|
}
|
|
//if(item.list.includes(player.name)) return socket.emit("game_log","Already joined!");
|
|
//if(player.type!="merchant") return socket.emit("game_log","Only merchants can join giveaways!");
|
|
|
|
//item.list.push(player.name);
|
|
|
|
item.registry = item.registry || {};
|
|
item.registry[player.auth_id] = player.name;
|
|
item.list = Object.values(item.registry);
|
|
|
|
socket.emit("game_log", "Joined the giveaway!");
|
|
seller.socket.emit("game_response", {
|
|
response: "giveaway_join",
|
|
name: player.name,
|
|
slot: data.slot,
|
|
cevent: true,
|
|
});
|
|
|
|
resend(player, "reopen");
|
|
resend(seller, "reopen+u+cid");
|
|
success_response({});
|
|
});
|
|
socket.on("trade_wishlist", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
data.q = min(9999, max(1, parseInt(data.q || 1) || 1));
|
|
if (!in_arr(data.slot, trade_slots) || !G.items[data.name] || data.name == "placeholder") {
|
|
return fail_response("invalid");
|
|
}
|
|
if (player.slots[data.slot] && !player.slots[data.slot].b) {
|
|
return fail_response("slot_occuppied");
|
|
}
|
|
if (!get_trade_slots(player).includes(data.slot)) {
|
|
return fail_response("invalid");
|
|
}
|
|
var item = {
|
|
name: data.name,
|
|
rid: randomStr(4),
|
|
price: round(min(99999999999, max(parseInt(data.price) || 1, 1))),
|
|
b: true,
|
|
};
|
|
if (G.items[data.name].upgrade || G.items[data.name].compound) {
|
|
item.q = min(99, data.q);
|
|
item.level = round(min(12, max(parseInt(data.level) || 0, 0)));
|
|
} else {
|
|
item.q = data.q;
|
|
}
|
|
player.slots[data.slot] = item;
|
|
player.cslots[data.slot] = cache_item(player.slots[data.slot], true);
|
|
resend(player, "reopen+u+cid+nc");
|
|
success_response({});
|
|
});
|
|
socket.on("trade_sell", function (data) {
|
|
// if(!is_sdk) return;
|
|
var player = players[socket.id];
|
|
var buyer = players[id_to_id[data.id]];
|
|
var num;
|
|
var actual = null;
|
|
var num = null;
|
|
data.q = max(1, parseInt(data.q || 1) || 1);
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!in_arr(data.slot, trade_slots)) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!buyer || buyer.npc || is_invis(buyer)) {
|
|
return fail_response("buyer_gone");
|
|
}
|
|
if (player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (distance(buyer, player, true) > B.dist) {
|
|
return fail_response("distance");
|
|
}
|
|
if (buyer.id == player.id) {
|
|
return fail_response("hmm");
|
|
}
|
|
var item = buyer.slots[data.slot];
|
|
if (!item || (data.rid && item.rid != data.rid)) {
|
|
return fail_response("item_gone");
|
|
}
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (!item.b && !B.rbugs) {
|
|
return fail_response("sneaky");
|
|
}
|
|
if ((item.q || 1) < data.q) {
|
|
return fail_response("dont_have_enough");
|
|
}
|
|
if (item.price * data.q > buyer.gold) {
|
|
return fail_response("buyer_gold");
|
|
}
|
|
for (var i = 0; i < player.isize; i++) {
|
|
if (
|
|
player.items[i] &&
|
|
player.items[i].name == item.name &&
|
|
(player.items[i].q || 1) >= data.q &&
|
|
player.items[i].level == item.level &&
|
|
!player.items[i].l
|
|
) {
|
|
actual = player.items[i];
|
|
num = i;
|
|
break;
|
|
}
|
|
}
|
|
if (!actual) {
|
|
return fail_response("no_item");
|
|
}
|
|
if (actual.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
if (!can_add_item(buyer, create_new_item(item.name, data.q))) {
|
|
return fail_response("trade_bspace");
|
|
}
|
|
var price = item.price * data.q;
|
|
player.gold += round(price * (1 - player.tax));
|
|
add_to_trade_history(player, "sell", buyer.name, cache_item(actual, true, { q: data.q }), price);
|
|
buyer.gold -= price;
|
|
add_to_trade_history(buyer, "buy", player.name, cache_item(actual, true, { q: data.q }), price);
|
|
S.gold += price - round(price * (1 - player.tax));
|
|
|
|
if ((item.q || 1) == data.q) {
|
|
buyer.slots[data.slot] = buyer.cslots[data.slot] = null;
|
|
} else {
|
|
buyer.slots[data.slot].q -= data.q;
|
|
buyer.cslots[data.slot] = cache_item(buyer.slots[data.slot], true);
|
|
}
|
|
|
|
// up to here
|
|
|
|
if ((player.items[i].q || 1) == data.q) {
|
|
player.items[num] = player.citems[num] = null;
|
|
} else {
|
|
player.items[num].q -= data.q;
|
|
player.citems[num] = cache_item(player.items[num]);
|
|
}
|
|
|
|
if (G.items[item.name].s) {
|
|
bnum = add_item(buyer, create_new_item(item.name, data.q), { announce: false });
|
|
} else {
|
|
bnum = add_item(buyer, actual, { announce: false });
|
|
}
|
|
|
|
if (player.type == "merchant") {
|
|
merchant_xp_logic(player, buyer, price, price - round(price * (1 - player.tax)));
|
|
}
|
|
if (buyer.type == "merchant") {
|
|
merchant_xp_logic(buyer, player, price, price - round(price * (1 - buyer.tax)));
|
|
}
|
|
|
|
socket.emit(
|
|
"game_log",
|
|
"Sales tax " + to_pretty_num(price - round(price * (1 - player.tax))) + " gold [" + player.tax * 100 + "%]",
|
|
);
|
|
socket.emit("game_log", "Received " + to_pretty_num(round(price * (1 - player.tax))) + " gold");
|
|
buyer.socket.emit("game_log", "Spent " + to_pretty_num(price) + " gold");
|
|
|
|
xy_emit(buyer, "ui", {
|
|
type: "+$$",
|
|
seller: player.name,
|
|
buyer: buyer.name,
|
|
item: cache_item(actual, true, { q: data.q, price: item.price }),
|
|
slot: data.slot,
|
|
num: bnum,
|
|
snum: num,
|
|
});
|
|
|
|
resend(player, "reopen");
|
|
resend(buyer, "reopen+u+cid");
|
|
success_response({});
|
|
});
|
|
socket.on("trade_buy", function (data) {
|
|
var player = players[socket.id];
|
|
var seller = players[id_to_id[data.id]];
|
|
var num;
|
|
data.q = max(1, parseInt(data.q || 1) || 1);
|
|
if (!player || player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!in_arr(data.slot, trade_slots)) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (!seller || seller.npc || is_invis(seller)) {
|
|
return fail_response("seller_gone");
|
|
}
|
|
if (seller.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (distance(seller, player, true) > B.dist || seller.map != player.map) {
|
|
return fail_response("distance");
|
|
}
|
|
if (seller.id == player.id) {
|
|
return fail_response("hmm");
|
|
}
|
|
var item = seller.slots[data.slot];
|
|
if (!item || (data.rid && item.rid != data.rid)) {
|
|
return fail_response("item_gone");
|
|
}
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (item.b || item.giveaway) {
|
|
return fail_response("sneaky");
|
|
}
|
|
if (item.price * data.q > player.gold) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
if ((item.q || 1) < data.q) {
|
|
return fail_response("insufficient_q");
|
|
}
|
|
if (!can_add_item(player, create_new_item(item.name, data.q))) {
|
|
return fail_response("no_space");
|
|
}
|
|
var price = item.price * data.q;
|
|
player.gold -= price;
|
|
add_to_trade_history(player, "buy", seller.name, cache_item(seller.slots[data.slot], true, { q: data.q }), price);
|
|
seller.gold += round(price * (1 - seller.tax));
|
|
add_to_trade_history(
|
|
seller,
|
|
"sell",
|
|
player.name,
|
|
cache_item(seller.slots[data.slot], true, { q: data.q }),
|
|
price,
|
|
);
|
|
S.gold += price - round(price * (1 - seller.tax));
|
|
|
|
if ((item.q || 1) == data.q) {
|
|
seller.slots[data.slot] = seller.cslots[data.slot] = null;
|
|
} else {
|
|
seller.slots[data.slot].q -= data.q;
|
|
seller.cslots[data.slot] = cache_item(seller.slots[data.slot], true);
|
|
}
|
|
|
|
if (item.q) {
|
|
num = add_item(player, create_new_sitem(item, data.q), { announce: false });
|
|
} else {
|
|
num = add_item(player, item, { announce: false });
|
|
}
|
|
|
|
if (seller.owner != player.owner) {
|
|
item.src = "tb";
|
|
}
|
|
|
|
if (player.type == "merchant") {
|
|
merchant_xp_logic(player, seller, price, price - round(price * (1 - player.tax)));
|
|
}
|
|
if (seller.type == "merchant") {
|
|
merchant_xp_logic(seller, player, price, price - round(price * (1 - seller.tax)));
|
|
}
|
|
|
|
socket.emit("game_log", "Spent " + to_pretty_num(price) + " gold");
|
|
seller.socket.emit(
|
|
"game_log",
|
|
"Sales tax " + to_pretty_num(price - round(price * (1 - seller.tax))) + " gold [" + seller.tax * 100 + "%]",
|
|
);
|
|
seller.socket.emit("game_log", "Received " + to_pretty_num(round(price * (1 - seller.tax))) + " gold");
|
|
|
|
xy_emit(seller, "ui", {
|
|
type: "+$$",
|
|
seller: seller.name,
|
|
buyer: player.name,
|
|
item: cache_item(item, true, { q: data.q }),
|
|
slot: data.slot,
|
|
num: data.num,
|
|
});
|
|
|
|
resend(player, "reopen");
|
|
resend(seller, "reopen+u+cid");
|
|
success_response({});
|
|
});
|
|
socket.on("trade_history", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
server_log("trade_history: " + player.name);
|
|
socket.emit("trade_history", player.p.trade_history || []);
|
|
});
|
|
socket.on("merchant", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var initial = player.p.stand;
|
|
server_log("merchant: " + player.name);
|
|
if (data.close || player.p.stand) {
|
|
player.p.stand = false;
|
|
for (var i = 0; i < player.items.length; i++) {
|
|
if (player.items[i] && player.items[i].b == "stand") {
|
|
delete player.items[i].b;
|
|
}
|
|
}
|
|
}
|
|
if (data.num !== undefined) {
|
|
var item = player.items[data.num];
|
|
if (item && G.items[item.name].stand) {
|
|
player.p.stand = G.items[item.name].stand;
|
|
item.b = "stand";
|
|
} else {
|
|
return fail_response("invalid");
|
|
}
|
|
}
|
|
if (initial != player.p.stand) {
|
|
// All unneccessary causes of resend's should be patched [03/08/18]
|
|
reslot_player(player);
|
|
resend(player, "u+cid");
|
|
}
|
|
success_response({});
|
|
});
|
|
socket.on("imove", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
data.a = max(0, parseInt(data.a) || 0);
|
|
data.b = max(0, parseInt(data.b) || 0);
|
|
if (data.a == data.b) {
|
|
return fail_response("invalid");
|
|
}
|
|
var a = min(data.a, data.b);
|
|
var b = max(data.a, data.b);
|
|
var a_item;
|
|
if (!(b < player.isize || b < player.items.length)) {
|
|
return fail_response("invalid");
|
|
}
|
|
// while(b>=player.items.length) player.items.push(null); [22/11/16]
|
|
if (player.items[a] && player.items[a].name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (player.items[b] && player.items[b].name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (can_stack(player.items[a], player.items[b])) {
|
|
player.items[data.a].q = (player.items[a].q || 1) + (player.items[b].q || 1);
|
|
player.items[data.b] = null;
|
|
player.esize++;
|
|
} else {
|
|
a_item = player.items[a];
|
|
player.items[a] = player.items[b];
|
|
player.items[b] = a_item;
|
|
}
|
|
player.citems[data.a] = cache_item(player.items[data.a]);
|
|
player.citems[data.b] = cache_item(player.items[data.b]);
|
|
resend(player, "reopen+nc+inv");
|
|
return success_response("data");
|
|
});
|
|
socket.on("bank", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!player.user || player.mounting || player.unmounting) {
|
|
return fail_response("bank_unavailable");
|
|
}
|
|
var success = {};
|
|
if (data.operation == "withdraw") {
|
|
var amount = max(0, min(parseInt(data.amount) || 0, player.user.gold));
|
|
player.user.gold -= amount;
|
|
player.gold += amount;
|
|
success = { response: "bank_withdraw", gold: amount, cevent: true };
|
|
}
|
|
if (data.operation == "deposit") {
|
|
var amount = max(0, min(parseInt(data.amount) || 0, player.gold));
|
|
player.user.gold += amount;
|
|
player.gold -= amount;
|
|
success = { response: "bank_store", gold: amount, cevent: true };
|
|
}
|
|
if (data.operation == "unlock") {
|
|
if (!bank_packs[data.pack]) {
|
|
return fail_response("invalid");
|
|
}
|
|
var gold = bank_packs[data.pack][1];
|
|
var shells = bank_packs[data.pack][2];
|
|
if (!gold) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
if (player.user[data.pack]) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (data.gold) {
|
|
if (player.gold < gold) {
|
|
return fail_response("gold_not_enough");
|
|
}
|
|
player.gold -= gold;
|
|
player.user[data.pack] = [];
|
|
player.cuser[data.pack] = [];
|
|
success = { response: "bank_new_pack", pack: data.pack, gold: gold, cevent: true };
|
|
} else if (data.shells) {
|
|
if (player["unlocking_" + data.pack]) {
|
|
return fail_response("bank_opi");
|
|
}
|
|
player["unlocking_" + data.pack] = true;
|
|
appengine_call(
|
|
"bill_user",
|
|
{
|
|
auth: player.auth,
|
|
amount: shells,
|
|
reason: data.pack,
|
|
name: player.name,
|
|
suffix: "/" + player.name + "/" + data.pack,
|
|
override: true,
|
|
},
|
|
function (result) {
|
|
server_log("buy_with_cash: " + JSON.stringify(result));
|
|
player["unlocking_" + data.pack] = false;
|
|
if (result.failed || !result.done) {
|
|
return socket.emit("game_log", "Purchase failed");
|
|
}
|
|
player.cash = result.cash;
|
|
|
|
player.user[data.pack] = [];
|
|
player.cuser[data.pack] = [];
|
|
game_response("bank_new_pack", { cevent: true, pack: data.pack, shells: shells });
|
|
|
|
resend(player, "reopen");
|
|
},
|
|
);
|
|
success = { success: false, in_progress: true };
|
|
}
|
|
}
|
|
if (data.operation == "move") {
|
|
//within a .itemsN
|
|
if (!player.user[data.pack] || bank_packs[data.pack][0] != player.map) {
|
|
return fail_response("invalid");
|
|
}
|
|
server_log("storage move " + JSON.stringify(data));
|
|
data.a = max(0, min(41, parseInt(data.a) || 0));
|
|
data.b = max(0, min(41, parseInt(data.b) || 0));
|
|
if (data.a == data.b) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (player.user[data.pack][data.a] && player.user[data.pack][data.a].name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (player.user[data.pack][data.b] && player.user[data.pack][data.b].name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (can_stack(player.user[data.pack][data.a], player.user[data.pack][data.b])) {
|
|
player.user[data.pack][data.b].q =
|
|
(player.user[data.pack][data.a].q || 1) + (player.user[data.pack][data.b].q || 1);
|
|
player.user[data.pack][data.a] = null;
|
|
} else {
|
|
var temp = player.user[data.pack][data.a];
|
|
player.user[data.pack][data.a] = player.user[data.pack][data.b];
|
|
player.user[data.pack][data.b] = temp;
|
|
}
|
|
player.cuser[data.pack][data.a] = cache_item(player.user[data.pack][data.a]);
|
|
player.cuser[data.pack][data.b] = cache_item(player.user[data.pack][data.b]);
|
|
}
|
|
if (data.operation == "swap") {
|
|
//between .items and a .itemsN
|
|
var operation = "swap";
|
|
if (!player.user[data.pack] || bank_packs[data.pack][0] != player.map) {
|
|
return fail_response("invalid");
|
|
}
|
|
data.str = parseInt(data.str);
|
|
data.inv = parseInt(data.inv);
|
|
if (data.inv == -1 || (!data.inv && data.inv !== 0)) {
|
|
operation = "pull";
|
|
for (var i = 0; i < player.isize; i++) {
|
|
if (!player.items[i]) {
|
|
data.inv = i;
|
|
break;
|
|
}
|
|
}
|
|
// if(data.inv==-1) { socket.emit("game_log","Inventory is full"); return; }
|
|
}
|
|
if (data.str == -1 || (!data.str && data.str !== 0)) {
|
|
if (operation == "pull") {
|
|
return fail_response("invalid");
|
|
}
|
|
operation = "store";
|
|
for (var i = 0; i < 42; i++) {
|
|
if (!player.user[data.pack][i]) {
|
|
data.str = i;
|
|
break;
|
|
}
|
|
}
|
|
// if(data.str==-1) { socket.emit("game_log","Storage is full"); return; }
|
|
}
|
|
server_log("storage swap " + JSON.stringify(data));
|
|
data.str = max(0, min(41, parseInt(data.str) || 0));
|
|
data.inv = max(0, min(player.isize - 1, parseInt(data.inv) || 0));
|
|
var bank_item = player.user[data.pack][data.str];
|
|
var inv_item = player.items[data.inv];
|
|
if (inv_item && inv_item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (inv_item && inv_item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
if (inv_item) {
|
|
delete inv_item.m;
|
|
delete inv_item.v;
|
|
}
|
|
if (operation == "swap") {
|
|
player.user[data.pack][data.str] = inv_item;
|
|
player.items[data.inv] = bank_item;
|
|
player.cuser[data.pack][data.str] = cache_item(player.user[data.pack][data.str]);
|
|
player.citems[data.inv] = cache_item(player.items[data.inv]);
|
|
success = { operation: "swap", pack: data.pack, inv: data.inv, str: data.str };
|
|
} else if (operation == "store" && inv_item) {
|
|
if (!can_add_item(player.user[data.pack], inv_item)) {
|
|
return fail_response("storage_full");
|
|
}
|
|
player.items[data.inv] = player.citems[data.inv] = null;
|
|
const num = bank_add_item(player, data.pack, inv_item);
|
|
success = { operation: "swap", pack: data.pack, inv: data.inv, str: num };
|
|
} else if (operation == "pull" && bank_item) {
|
|
if (!can_add_item(player, bank_item)) {
|
|
return fail_response("inventory_full");
|
|
}
|
|
player.user[data.pack][data.str] = player.cuser[data.pack][data.str] = null;
|
|
const num = add_item(player, bank_item, { announce: false });
|
|
success = { operation: "swap", pack: data.pack, inv: num, str: data.str };
|
|
}
|
|
}
|
|
if (!player.user.gold && player.user.gold !== 0) {
|
|
player.user.gold = 0;
|
|
server_log("#X - GOLD BUG bank", 1);
|
|
}
|
|
resend(player, "reopen");
|
|
success_response(success);
|
|
});
|
|
socket.on("throw", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var item = player.items[data.num];
|
|
if (item) {
|
|
if (item.name == "placeholder") {
|
|
return fail_response("item_placeholder");
|
|
}
|
|
if (item.l) {
|
|
return fail_response("item_locked");
|
|
}
|
|
if (item.b) {
|
|
return fail_response("item_blocked");
|
|
}
|
|
var def = G.items[item.name];
|
|
if (!def.throw) {
|
|
return fail_response("invalid");
|
|
}
|
|
var x = parseFloat(data.x) || 0;
|
|
var y = parseFloat(data.y) || 0;
|
|
if (distance(player, { map: player.map, in: player.in, x: x, y: y }) > player.str * 3) {
|
|
fail_response("too_far");
|
|
}
|
|
consume_one(player, data.num);
|
|
if (item.name == "confetti") {
|
|
player.thrilling = future_s(20);
|
|
xy_emit(
|
|
{ map: player.map, in: player.in, x: x, y: y },
|
|
"eval",
|
|
"confetti_shower({in:'" + player.in + "',map:'" + player.map + "',real_x:" + x + ",real_y:" + y + "})",
|
|
);
|
|
}
|
|
if (item.name == "firecrackers") {
|
|
player.thrilling = future_s(200);
|
|
xy_emit(
|
|
{ map: player.map, in: player.in, x: x, y: y },
|
|
"eval",
|
|
"firecrackers({in:'" + player.in + "',map:'" + player.map + "',real_x:" + x + ",real_y:" + y + "})",
|
|
);
|
|
for (var id in instances[player.in].monsters) {
|
|
var monster = instances[player.in].monsters[id];
|
|
if (
|
|
monster.target &&
|
|
is_same(player, get_player(monster.target), 1) &&
|
|
distance({ map: player.map, in: player.in, x: x, y: y }, monster) < 64
|
|
) {
|
|
stop_pursuit(monster, { force: true, cause: "firecrackers" });
|
|
}
|
|
}
|
|
}
|
|
if (item.name == "whiteegg") {
|
|
xy_emit({ map: player.map, in: player.in, x: x, y: y }, "eval", "egg_splash(" + x + "," + y + ")");
|
|
for (var id in instances[player.in].monsters) {
|
|
var monster = instances[player.in].monsters[id];
|
|
if (!monster.target && distance({ map: player.map, in: player.in, x: x, y: y }, monster) < 32) {
|
|
target_player(monster, player);
|
|
}
|
|
}
|
|
}
|
|
if (item.name == "smoke") {
|
|
xy_emit({ map: player.map, in: player.in, x: x, y: y }, "eval", "assassin_smoke(" + x + "," + y + ");");
|
|
}
|
|
resend(player, "reopen+nc+inv");
|
|
success_response({});
|
|
} else {
|
|
fail_response("no_item");
|
|
}
|
|
});
|
|
socket.on("poke", function (data) {
|
|
var player = players[socket.id];
|
|
var level = 1;
|
|
if (!player || !player.slots.gloves || player.slots.gloves.name != "poker") {
|
|
return;
|
|
}
|
|
if (player.pokes >= 50) {
|
|
return socket.emit("game_log", "You are out of pokes!");
|
|
}
|
|
player.pokes = (player.pokes || 0) + 1;
|
|
if (player.slots.gloves.level >= 10) {
|
|
level = 4;
|
|
} else if (player.slots.gloves.level == 9) {
|
|
level = 3;
|
|
} else if (player.slots.gloves.level == 8) {
|
|
level = 2;
|
|
}
|
|
xy_emit(player, "poke", { name: data.name, level: level, who: player.name });
|
|
});
|
|
socket.on("merge", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var container = player.slots[data.container];
|
|
var pet = player.slots[data.pet];
|
|
if (!container || container.type != "container" || !pet || pet.type != "container") {
|
|
return socket.emit("game_response", "merge_mismatch");
|
|
}
|
|
if (G.items[container.name].grade < G.items[pet.name].grade || container.data) {
|
|
return socket.emit("game_response", "merge_mismatch");
|
|
}
|
|
container.data = G.items[pet.name].pet;
|
|
player.slots[data.pet] = null;
|
|
socket.emit("game_response", "merge_complete");
|
|
resend(player, "reopen");
|
|
});
|
|
socket.on("activate", function (data) {
|
|
var player = players[socket.id];
|
|
// console.log(JSON.stringify(data));
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.slot) {
|
|
var item = player.slots[data.slot];
|
|
if (!item || in_arr(data.slot, trade_slots)) {
|
|
return;
|
|
}
|
|
if (item.name == "etherealamulet") {
|
|
if (player.last_ethereal && mssince(player.last_ethereal) < 120) {
|
|
return socket.emit("game_response", "not_ready");
|
|
}
|
|
player.last_ethereal = new Date();
|
|
player.s.ethereal = { ms: 5000 };
|
|
socket.emit("eval", { code: "skill_timeout('ethereal',120)" });
|
|
return resend(player, "u+cid");
|
|
} else if (item.name == "angelwings") {
|
|
if (player.tskin == "snow_angel") {
|
|
player.tskin = "";
|
|
} else if (item.level >= 8 && (player.type == "mage" || player.type == "priest")) {
|
|
player.tskin = "snow_angel";
|
|
} else {
|
|
return socket.emit("game_response", "nothing");
|
|
}
|
|
} else if (item.name == "tristone") {
|
|
var on = true;
|
|
if (player.tskin || player.tactivations == 100) {
|
|
player.tskin = "";
|
|
on = false;
|
|
} else if (item.level <= 1 && deduct_gender(player) == "female") {
|
|
player.tskin = random_one(["tf_green", "tf_pink"]);
|
|
} else if (item.level == 2 && deduct_gender(player) == "female") {
|
|
player.tskin = random_one(["tf_blue", "tf_purple"]);
|
|
} else if (deduct_gender(player) == "female") {
|
|
player.tskin = "tf_orange";
|
|
} else if (item.level <= 1) {
|
|
player.tskin = random_one(["tm_gray", "tm_brown", "tm_white"]);
|
|
} else if (item.level == 2) {
|
|
player.tskin = random_one(["tm_green", "tm_yellow", "tm_purple"]);
|
|
} else {
|
|
player.tskin = random_one(["tm_blue", "tm_red"]);
|
|
}
|
|
player.tactivations = (player.tactivations || 0) + 1;
|
|
} else if (item.name == "darktristone") {
|
|
var on = true;
|
|
if (player.tskin || player.tactivations == 100) {
|
|
player.tskin = "";
|
|
on = false;
|
|
} else if (item.level == 4 && deduct_gender(player) == "female") {
|
|
player.tskin = "mf_blue";
|
|
} else if (deduct_gender(player) == "female") {
|
|
player.tskin = "mf_yellow";
|
|
} else if (item.level == 4) {
|
|
player.tskin = "mm_blue";
|
|
} else {
|
|
player.tskin = "mm_yellow";
|
|
}
|
|
player.tactivations = (player.tactivations || 0) + 1;
|
|
} else {
|
|
return;
|
|
}
|
|
resend(player, "reopen+nc+inv+u+cid");
|
|
} else {
|
|
var item = player.items[data.num];
|
|
if (item) {
|
|
if (item.name == "frozenstone") {
|
|
consume_one(player, data.num);
|
|
}
|
|
if (item.name == "bkey") {
|
|
if (!player.user) {
|
|
return socket.emit("game_response", "only_in_bank");
|
|
}
|
|
if (player.user.unlocked && player.user.unlocked.bank_b) {
|
|
return socket.emit("game_response", "already_unlocked");
|
|
}
|
|
consume_one(player, data.num);
|
|
player.user.unlocked = player.user.unlocked || {};
|
|
player.user.unlocked.bank_b = new Date();
|
|
player.user.items8 = player.user.items8 || [];
|
|
socket.emit("game_response", "door_unlocked");
|
|
}
|
|
if (item.name == "ukey") {
|
|
if (!player.user) {
|
|
return socket.emit("game_response", "only_in_bank");
|
|
}
|
|
if (player.user.unlocked && player.user.unlocked.bank_u) {
|
|
return socket.emit("game_response", "already_unlocked");
|
|
}
|
|
consume_one(player, data.num);
|
|
player.user.unlocked = player.user.unlocked || {};
|
|
player.user.unlocked.bank_u = new Date();
|
|
player.user.items24 = player.user.items24 || [];
|
|
socket.emit("game_response", "door_unlocked");
|
|
}
|
|
if (item.name == "dkey") {
|
|
if (!player.user) {
|
|
return socket.emit("game_response", "only_in_bank");
|
|
}
|
|
var found = false;
|
|
for (var i = 0; i < 48; i++) {
|
|
var pack = "items" + i;
|
|
if (!bank_packs[pack] || player.user[pack]) {
|
|
continue;
|
|
}
|
|
found = true;
|
|
player.user[pack] = [];
|
|
player.cuser[pack] = [];
|
|
break;
|
|
}
|
|
if (!found) {
|
|
return;
|
|
}
|
|
consume_one(player, data.num);
|
|
socket.emit("game_response", "bank_pack_unlocked");
|
|
}
|
|
}
|
|
resend(player, "reopen+nc+inv");
|
|
}
|
|
});
|
|
socket.on("booster", function (data) {
|
|
var player = players[socket.id];
|
|
var item = player.items[data.num];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
server_log("booster " + data.num + " " + data.action);
|
|
if (!item || !booster_items.includes(item.name)) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (data.action == "activate" && !item.expires) {
|
|
item.expires = new Date();
|
|
item.expires.setDate(item.expires.getDate() + 30 + (item.level || 0) * 2);
|
|
//item.expires.setMinutes(item.expires.getMinutes()+2);
|
|
} else if (data.action == "shift") {
|
|
if (!booster_items.includes(data.to)) {
|
|
return fail_response("invalid");
|
|
}
|
|
player.xpm = player.goldm = player.luckm = 1;
|
|
item.name = data.to;
|
|
player.s.penalty_cd = { ms: min(((player.s.penalty_cd && player.s.penalty_cd.ms) || 0) + 240, 120000) };
|
|
}
|
|
// if(item.expires) item.p="legacy";
|
|
player.citems[data.num] = cache_item(player.items[data.num]);
|
|
resend(player, "reopen");
|
|
success_response({ name: player.items[data.num].name });
|
|
});
|
|
socket.on("convert", function (data) {
|
|
// There are a lot of routines that could give birth to an endgame bug
|
|
// This routine ended up being the one
|
|
// "stone" boosters were originally 1200 shells
|
|
// when they were discontinued, I let players exchange them for 3600 shells instead
|
|
// turns out 'buy_with_cash' didn't check whether an item has "ignore" or not
|
|
// so one could buy the stones for 1200 shells and sell them for 3600
|
|
// unlocking infinite gold and shells [17/01/18]
|
|
var player = players[socket.id];
|
|
var item = player.items[data.num];
|
|
var amount = 3600;
|
|
server_log("stone " + data.num + " " + data.action);
|
|
if (!item || !["stoneofxp", "stoneofgold", "stoneofluck"].includes(item.name)) {
|
|
return;
|
|
}
|
|
if (item.expires) {
|
|
amount = round(600 - (hsince(item.expires) * 100) / 24.0);
|
|
}
|
|
add_shells(player, amount, "convert");
|
|
player.items[data.num] = null;
|
|
player.citems[data.num] = cache_item(player.items[data.num]);
|
|
resend(player, "reopen+cid");
|
|
});
|
|
socket.on("emotion", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!data.name) {
|
|
data.name = random_one(Object.keys(player.p.emx));
|
|
}
|
|
if (player.last.emotion && mssince(player.last.emotion) < 2000) {
|
|
return socket.emit("game_response", "emotion_cooldown");
|
|
}
|
|
player.last.emotion = new Date();
|
|
if (!G.emotions[data.name] || !player.p.emx[data.name]) {
|
|
return socket.emit("game_response", "emotion_cant");
|
|
}
|
|
xy_emit(player, "emotion", { name: data.name, player: player.name });
|
|
});
|
|
socket.on("skill", function (data) {
|
|
const player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
server_log("skill " + JSON.stringify(data));
|
|
|
|
var cool = true;
|
|
var resolve = { response: "data", place: data.name, success: true };
|
|
var reject = null;
|
|
player.first = true;
|
|
G.skills.attack.cooldown = player.attack_ms;
|
|
|
|
if (is_disabled(player)) {
|
|
return fail_response("disabled", data.name);
|
|
}
|
|
|
|
if ((player.tskin == "konami" || is_silenced(player)) && data.name != "attack") {
|
|
return fail_response("skill_cant_incapacitated", data.name);
|
|
}
|
|
|
|
const gSkill = G.skills[data.name];
|
|
if (!gSkill) {
|
|
return fail_response("no_skill", data.name);
|
|
}
|
|
|
|
if (
|
|
gSkill.mp &&
|
|
player.mp < (gSkill.mp * (100 - (player.mp_reduction || 0))) / 100.0 &&
|
|
!(player.role == "gm" && data.name == "blink")
|
|
) {
|
|
return fail_response("no_mp", data.name);
|
|
}
|
|
|
|
if (gSkill.level && player.level < gSkill.level) {
|
|
return fail_response("no_level", data.name);
|
|
}
|
|
|
|
let cooldown;
|
|
let lastUse;
|
|
if (gSkill.share) {
|
|
// This skill shares a cooldown with another skill, use that skill's cooldown
|
|
cooldown = G.skills[gSkill.share].cooldown * (gSkill.cooldown_multiplier || 1);
|
|
lastUse = player.last[gSkill.share];
|
|
} else {
|
|
cooldown = gSkill.cooldown || gSkill.reuse_cooldown;
|
|
lastUse = player.last[data.name];
|
|
}
|
|
if (cooldown && lastUse && mssince(lastUse) < cooldown) {
|
|
return fail_response("cooldown", data.name, {
|
|
skill: data.name,
|
|
id: data.id,
|
|
ms: cooldown - mssince(lastUse),
|
|
});
|
|
}
|
|
|
|
if (gSkill.class && !in_arr(player.type, gSkill.class) && player.role != "gm") {
|
|
return fail_response("skill_cant_use", data.name);
|
|
}
|
|
|
|
if (gSkill.wtype && player.role != "gm") {
|
|
let canUse;
|
|
if (!player.slots.mainhand) {
|
|
canUse = false;
|
|
} else if (is_array(gSkill.wtype)) {
|
|
canUse = in_arr(G.items[player.slots.mainhand.name].wtype, gSkill.wtype);
|
|
} else {
|
|
canUse = G.items[player.slots.mainhand.name].wtype == gSkill.wtype;
|
|
}
|
|
if (!canUse) {
|
|
return fail_response("skill_cant_wtype", data.name);
|
|
}
|
|
}
|
|
|
|
const gMap = G.maps[player.map];
|
|
if (gSkill.hostile && gMap.safe) {
|
|
return fail_response("skill_cant_safe", data.name);
|
|
}
|
|
|
|
var target;
|
|
if (gSkill.target) {
|
|
const isMonster = "" + parseInt(data.id) === "" + data.id;
|
|
const isPlayer = !isMonster;
|
|
const canTargetMonster = gSkill.target == "monster" || gSkill.target === true;
|
|
const canTargetPlayer = gSkill.target == "player" || gSkill.target === true;
|
|
if ((isMonster && !canTargetMonster) || (isPlayer && !canTargetPlayer)) {
|
|
return fail_response("invalid_target", data.name, { id: data.id });
|
|
}
|
|
if (isPlayer && players[id_to_id[data.id]]) {
|
|
target = players[id_to_id[data.id]];
|
|
if (gSkill.hostile && target.name == player.name) {
|
|
// Prevent casting the skill on oneself
|
|
return fail_response("no_target", data.name);
|
|
}
|
|
} else if (isMonster && instances[player.in].monsters[data.id]) {
|
|
target = instances[player.in].monsters[data.id];
|
|
} else {
|
|
// TODO: Consider adding game_response, too
|
|
return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" });
|
|
}
|
|
if (is_invis(target) || (!gSkill.global && distance(target, player) > B.max_vision)) {
|
|
// TODO: Consider adding game_response, too
|
|
return socket.emit("disappear", { id: data.id, place: data.name, reason: "not_there" });
|
|
}
|
|
}
|
|
|
|
if (gSkill.requirements) {
|
|
for (const requirement in gSkill.requirements) {
|
|
if (!player[requirement] || player[requirement] < gSkill.requirements[requirement]) {
|
|
return fail_response("skill_cant_requirements", data.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gSkill.slot) {
|
|
let found = false;
|
|
let noCharge = false;
|
|
for (const [slotName, itemName] of gSkill.slot) {
|
|
const slotItem = player.slots[slotName];
|
|
if (!slotItem || slotItem.name !== itemName) {
|
|
continue;
|
|
}
|
|
found = true;
|
|
|
|
const gItem = G.items[itemName];
|
|
if (!gItem.charge) {
|
|
continue;
|
|
}
|
|
// This skill requires a charge, check the charge level
|
|
if ((slotItem.charges || 0) < gItem.charge) {
|
|
noCharge = true;
|
|
continue;
|
|
}
|
|
// Charge is sufficient, use this item for the skill
|
|
slotItem.charges -= gItem.charge;
|
|
player.cslots[slotName] = cache_item(slotItem);
|
|
noCharge = false;
|
|
break;
|
|
}
|
|
if (!found) {
|
|
return fail_response("skill_cant_slot", data.name);
|
|
}
|
|
if (noCharge) {
|
|
return fail_response("skill_cant_charges", data.name);
|
|
}
|
|
}
|
|
|
|
if (player.s.invincible) {
|
|
delete player.s.invincible;
|
|
player.to_resend = "u+cid";
|
|
}
|
|
|
|
// Range check
|
|
const isTargetTooFar = (target) => {
|
|
// Use the range of the skill, falling back to the player range if it isn't set
|
|
let range = gSkill.range || player.range;
|
|
if (gSkill.global) {
|
|
return false;
|
|
}
|
|
if (gSkill.range_multiplier) {
|
|
range *= gSkill.range_multiplier;
|
|
}
|
|
if (gSkill.range_bonus) {
|
|
range += gSkill.range_bonus;
|
|
}
|
|
|
|
if (data.name == "throw") {
|
|
range += player.level;
|
|
}
|
|
|
|
const dist = distance(player, target);
|
|
if (dist > range + player.xrange) {
|
|
return true;
|
|
}
|
|
if (dist > range) {
|
|
// xrange was used, reduce it by however much we used
|
|
player.xrange -= dist - range;
|
|
}
|
|
return false;
|
|
};
|
|
if (target && isTargetTooFar(target)) {
|
|
return fail_response("too_far", data.name, { dist: dist, id: target.id });
|
|
}
|
|
|
|
// Consume item check
|
|
if (gSkill.consume) {
|
|
let item;
|
|
if (data.num === undefined || data.num === null) {
|
|
// No item was provided, search for one
|
|
for (let i = player.isize - 1; i >= 0; i--) {
|
|
const potentialItem = player.items[i];
|
|
if (!potentialItem) {
|
|
continue;
|
|
}
|
|
if (potentialItem.name !== gSkill.consume) {
|
|
continue;
|
|
}
|
|
|
|
// We found the item
|
|
data.num = i;
|
|
item = potentialItem;
|
|
break;
|
|
}
|
|
} else {
|
|
item = player.items[data.num];
|
|
}
|
|
if (!item || item.name != gSkill.consume) {
|
|
return fail_response("skill_cant_item", data.name);
|
|
}
|
|
|
|
// Consume the item
|
|
consume_one(player, data.num);
|
|
player.to_resend = "u+cid+reopen";
|
|
}
|
|
|
|
if (
|
|
[
|
|
"attack",
|
|
"burst",
|
|
"heal",
|
|
"mentalburst",
|
|
"piercingshot",
|
|
"poisonarrow",
|
|
"purify",
|
|
"quickpunch",
|
|
"quickstab",
|
|
"smash",
|
|
"snowball",
|
|
"supershot",
|
|
"taunt",
|
|
"zapperzap",
|
|
].includes(data.name)
|
|
) {
|
|
const attack = commence_attack(player, target, data.name);
|
|
if (!attack.failed) {
|
|
resolve = attack;
|
|
} else {
|
|
reject = attack;
|
|
cool = false;
|
|
}
|
|
} else if (data.name == "selfheal") {
|
|
const attack = commence_attack(player, player, data.name);
|
|
if (!attack.failed) {
|
|
resolve = attack;
|
|
} else {
|
|
reject = attack;
|
|
cool = false;
|
|
}
|
|
} else if (data.name == "invis" && !player.s.invis) {
|
|
if (player.s.marked) {
|
|
return fail_response("skill_cant_use", data.name);
|
|
}
|
|
player.s.invis = { ms: 999999999999999 };
|
|
xy_emit(player, "disappear", { id: player.id, invis: true, reason: "invis" });
|
|
player.to_resend = " ";
|
|
} else if (data.name == "pickpocket") {
|
|
consume_mp(player, gSkill.mp);
|
|
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 (S.blessed_minutes) {
|
|
cdata.blessed_by = S.blessed_by;
|
|
cdata.blessed_minutes = S.blessed_minutes;
|
|
player.s.patronsgrace = { ms: G.conditions.patronsgrace.duration, f: S.blessed_by };
|
|
}
|
|
if (result.code) {
|
|
cdata.code = result.code;
|
|
cdata.code_slot = result.code_slot;
|
|
cdata.code_version = result.code_version;
|
|
}
|
|
cdata.entities = send_all_xy(player, { raw: true });
|
|
socket.emit("start", cdata);
|
|
total_players++;
|
|
}
|
|
},
|
|
function (err) {
|
|
server_log("start_character_failed: " + data.character + " reason: " + err, 1);
|
|
if (observers[socket.id]) {
|
|
observers[socket.id].auth_engaged = false;
|
|
}
|
|
},
|
|
);
|
|
});
|
|
socket.on("use", function (data) {
|
|
const player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.item == "hp" || data.item == "mp") {
|
|
if (player.last.potion) {
|
|
const ms = -mssince(player.last.potion);
|
|
if (ms > 0) {
|
|
return fail_response("not_ready", { ms: ms });
|
|
}
|
|
}
|
|
player.last.potion = future_ms(4000);
|
|
if (data.item == "hp") {
|
|
player.hp = Math.min(player.hp + 50, player.max_hp);
|
|
disappearing_text(socket, player, "+50", { color: "green", xy: 1, s: "hp", nohp: 1 });
|
|
}
|
|
if (data.item == "mp") {
|
|
player.mp = Math.min(player.mp + 100, player.max_mp);
|
|
disappearing_text(socket, player, "+100", { color: "#006AA9", xy: 1, s: "mp", nomp: 1 });
|
|
}
|
|
// calculate_player_stats(player); [22/11/16]
|
|
player.cid++;
|
|
player.u = true;
|
|
socket.emit("player", player_to_client(player));
|
|
socket.emit("eval", { code: "pot_timeout(4000)" });
|
|
}
|
|
success_response({});
|
|
});
|
|
socket.on("friend", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
server_log("friend: " + JSON.stringify(data));
|
|
if (data.event == "request") {
|
|
var friend = players[name_to_id[data.name || ""]];
|
|
if (!friend) {
|
|
return fail_response("friend_rleft");
|
|
}
|
|
if (in_arr(friend.owner, player.friends) || friend.owner == player.owner) {
|
|
return success_response("friend_already");
|
|
}
|
|
requests[player.name + "-" + friend.name] = { a: player.owner, b: friend.owner };
|
|
friend.socket.emit("friend", { event: "request", name: player.name });
|
|
return success_response("friend_rsent");
|
|
}
|
|
if (data.event == "accept") {
|
|
if (!requests[data.name + "-" + player.name]) {
|
|
return fail_response("friend_expired");
|
|
}
|
|
appengine_call(
|
|
"set_friends",
|
|
{ user1: requests[data.name + "-" + player.name].a, user2: requests[data.name + "-" + player.name].b },
|
|
function (result) {
|
|
var player = players[socket.id];
|
|
if (result.failed) {
|
|
if (player) {
|
|
socket.emit("game_response", { response: "friend_failed", reason: result.reason });
|
|
}
|
|
return;
|
|
}
|
|
//var friend=players[name_to_id[data.name||""]];
|
|
//if(player)
|
|
//if(friend) friend.emit("friend",{event:"accepted",name:player.name});
|
|
},
|
|
function () {
|
|
var player = players[socket.id];
|
|
if (player) {
|
|
socket.emit("game_response", { response: "friend_failed", reason: "coms failure" });
|
|
}
|
|
},
|
|
);
|
|
requests[data.name + "-" + player.name] = false;
|
|
}
|
|
if (data.event == "unfriend") {
|
|
appengine_call(
|
|
"not_friends",
|
|
{ user1: player.owner, user2: data.name },
|
|
function (result) {
|
|
var player = players[socket.id];
|
|
if (result.failed) {
|
|
if (player) {
|
|
socket.emit("game_response", { response: "unfriend_failed", reason: result.reason });
|
|
}
|
|
return;
|
|
}
|
|
},
|
|
function () {
|
|
var player = players[socket.id];
|
|
if (player) {
|
|
socket.emit("game_response", { response: "unfriend_failed", reason: "coms failure" });
|
|
}
|
|
},
|
|
);
|
|
}
|
|
success_response({ success: false, in_progress: true });
|
|
});
|
|
socket.on("duel", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.event == "challenge") {
|
|
var invited = players[name_to_id[data.id || data.name]];
|
|
if (!invited || invited.id == player.id) {
|
|
return socket.emit("game_log", "Invalid");
|
|
}
|
|
if (invited.duel || player.duel) {
|
|
return socket.emit("game_log", "Already dueling");
|
|
}
|
|
invited.socket.emit("duel", { event: "chellenge", name: player.name });
|
|
socket.emit("game_response", { response: "challenge_sent", name: invited.name });
|
|
invited.socket.emit("game_response", { response: "challenge_received", name: player.name });
|
|
challenges[player.name] = invited.name;
|
|
} else if (data.event == "accept") {
|
|
var challenger = players[name_to_id[data.id || data.name]];
|
|
if (!challenger || challenges[challenger.name] != player.name) {
|
|
return socket.emit("game_log", "Challenge expired");
|
|
}
|
|
if (challenger.duel || player.duel) {
|
|
return socket.emit("game_log", "Already dueling");
|
|
}
|
|
if (is_in_pvp(challenger) || is_in_pvp(player)) {
|
|
return socket.emit("game_log", "Can't start a duel if any of the parties are already in a pvp zone");
|
|
}
|
|
delete challenges[challenger.name];
|
|
challenger.socket.emit("game_response", { response: "challenge_accepted", name: player.name });
|
|
var name = randomStr(20);
|
|
var a = [];
|
|
var b = [];
|
|
instance = create_instance(name, "duelland");
|
|
instance.info = {
|
|
seconds: (is_sdk && 20) || 60,
|
|
active: false,
|
|
A: [player_to_summary(challenger)],
|
|
B: [player_to_summary(player)],
|
|
id: name,
|
|
};
|
|
instance.info.A[0].active = true;
|
|
instance.info.B[0].active = true;
|
|
clean_slate(challenger);
|
|
transport_player_to(challenger, name, 1);
|
|
if (challenger.party) {
|
|
parties[challenger.party].forEach(function (p) {
|
|
a.push(p);
|
|
});
|
|
} else {
|
|
a = [challenger.name];
|
|
}
|
|
clean_slate(player);
|
|
transport_player_to(player, name, 2);
|
|
if (player.party) {
|
|
parties[player.party].forEach(function (p) {
|
|
b.push(p);
|
|
});
|
|
} else {
|
|
b = [player.name];
|
|
}
|
|
if (!E.duels) {
|
|
E.duels = {};
|
|
}
|
|
var duel = {
|
|
challenger: challenger.name,
|
|
a: a,
|
|
vs: player.name,
|
|
b: b,
|
|
instance: name,
|
|
seconds: (is_sdk && 20) || 60,
|
|
active: false,
|
|
id: name,
|
|
};
|
|
challenger.team = "A";
|
|
challenger.duel = duel;
|
|
challenger.s.stunned = { ms: 120000 };
|
|
resend(challenger, "u+cid");
|
|
player.team = "B";
|
|
player.duel = duel;
|
|
player.s.stunned = { ms: 120000 };
|
|
resend(player, "u+cid");
|
|
E.duels[name] = duel;
|
|
broadcast_e();
|
|
a.forEach(function (p) {
|
|
if (p != challenger.name && get_player(p)) {
|
|
get_player(p).socket.emit("game_response", {
|
|
response: "duel_started",
|
|
challenger: challenger.name,
|
|
vs: player.name,
|
|
id: name,
|
|
});
|
|
}
|
|
});
|
|
b.forEach(function (p) {
|
|
if (p != player.name && get_player(p)) {
|
|
get_player(p).socket.emit("game_response", {
|
|
response: "duel_started",
|
|
challenger: challenger.name,
|
|
vs: player.name,
|
|
id: name,
|
|
});
|
|
}
|
|
});
|
|
} else if (data.event == "enter") {
|
|
if (is_in_pvp(player)) {
|
|
return socket.emit("game_log", "Can't join the duel from a pvp zone!");
|
|
}
|
|
if (player.duel) {
|
|
return socket.emit("game_log", "Already in a duel!");
|
|
}
|
|
if (!E.duels[data.id]) {
|
|
return socket.emit("game_log", "Duel expired");
|
|
}
|
|
if (E.duels[data.id].active) {
|
|
return socket.emit("game_log", "Duel already started");
|
|
}
|
|
if (!(E.duels[data.id].a.includes(player.name) || E.duels[data.id].b.includes(player.name))) {
|
|
return socket.emit("game_log", "Not your duel");
|
|
}
|
|
clean_slate(player);
|
|
|
|
if (E.duels[data.id].a.includes(player.name)) {
|
|
transport_player_to(player, data.id, 1);
|
|
player.team = "A";
|
|
} else {
|
|
transport_player_to(player, data.id, 2);
|
|
player.team = "B";
|
|
}
|
|
|
|
player.duel = E.duels[data.id];
|
|
if (player.duel.a.includes(player.name)) {
|
|
instances[data.id].info.A.push(player_to_summary(player));
|
|
instances[data.id].info.A[instances[data.id].info.A.length - 1].active = true;
|
|
} else {
|
|
instances[data.id].info.B.push(player_to_summary(player));
|
|
instances[data.id].info.B[instances[data.id].info.B.length - 1].active = true;
|
|
}
|
|
player.s.stunned = { ms: 120000 };
|
|
|
|
resend(player, "u+cid");
|
|
instance_emit(data.id, "game_chat", { message: player.name + " joined the duel!" });
|
|
}
|
|
});
|
|
socket.on("party", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.event == "invite") {
|
|
// if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can send invites"); return; }
|
|
if (player.party && (parties[player.party].length >= limits.party_max || player.party_length >= limits.party)) {
|
|
return fail_response("party_full");
|
|
}
|
|
var invited = players[name_to_id[data.id || data.name]];
|
|
if (!invited || invited.id == player.id) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (player.party && player.party == invited.party) {
|
|
return success_response("already_in_party");
|
|
}
|
|
// if(invited.party) { socket.emit("game_log",invited.name+" is already partying"); return; }
|
|
invited.socket.emit("invite", { name: player.name });
|
|
socket.emit("game_log", "Invited " + invited.name + " to party");
|
|
if (!invitations[player.name]) {
|
|
invitations[player.name] = {};
|
|
}
|
|
invitations[player.name][invited.id] = 1;
|
|
}
|
|
if (data.event == "request") {
|
|
// if(player.party) { return; }
|
|
var invited = players[name_to_id[data.id || data.name]];
|
|
if (!invited || invited.id == player.id) {
|
|
return fail_response("invalid");
|
|
}
|
|
if (player.party && player.party == invited.party) {
|
|
return success_response("already_in_party");
|
|
}
|
|
// if(!invited.party) { socket.emit("game_log",invited.name+" isn't partying"); return; }
|
|
invited.socket.emit("request", { name: player.name });
|
|
socket.emit("game_log", "Requested to join " + invited.name + "'s party");
|
|
if (!requests[player.name]) {
|
|
requests[player.name] = {};
|
|
}
|
|
requests[player.name][invited.id] = 1;
|
|
}
|
|
if (data.event == "accept") {
|
|
var inviter = players[name_to_id[data.name]];
|
|
// if(player.party) { socket.emit("party_update",{list:parties[player.party],party:party_to_client(player.party)}); socket.emit("game_log","Already partying"); return; }
|
|
// if(!inviter || (inviter.party && inviter.party!=inviter.name)) { socket.emit("game_log","Party was disbanded"); return; }
|
|
if (!inviter) {
|
|
return fail_response("player_gone", { name: data.name });
|
|
}
|
|
if (
|
|
inviter.party &&
|
|
(parties[inviter.party].length >= limits.party_max || inviter.party_length >= limits.party)
|
|
) {
|
|
return fail_response("party_full");
|
|
}
|
|
if (!invitations[inviter.name] || !invitations[inviter.name][player.id]) {
|
|
return fail_response("invitation_expired");
|
|
}
|
|
if (player.party && player.party == inviter.party) {
|
|
return success_response("already_in_party");
|
|
}
|
|
if (player.party) {
|
|
leave_party(player.party, player);
|
|
socket.emit("party_update", {});
|
|
socket.emit("game_log", "Left your current party");
|
|
}
|
|
invitations[inviter.name][player.id] = 0;
|
|
if (!inviter.party) {
|
|
inviter.party = inviter.name;
|
|
parties[inviter.name] = [inviter.name];
|
|
resend(inviter, "nc+u+cid");
|
|
delete requests[inviter.name];
|
|
if (!players[name_to_id[data.name]]) {
|
|
return fail_response("player_gone", { name: data.name });
|
|
} // these repetitions are all because socket.emit's can cause in-line disconnects and disband parties right after creation [07/08/20]
|
|
}
|
|
player.party = inviter.party;
|
|
parties[inviter.party].push(player.name);
|
|
party_emit(player.party, "party_update", {
|
|
list: parties[inviter.party],
|
|
party: party_to_client(inviter.party),
|
|
message:
|
|
player.name +
|
|
" joined the party" +
|
|
((inviter.party != inviter.name && " with " + inviter.name + "'s invite") || ""),
|
|
});
|
|
resend(player, "nc+u+cid");
|
|
delete requests[player.name];
|
|
delete requests[inviter.name]; // optional
|
|
}
|
|
if (data.event == "raccept") {
|
|
var requester = players[name_to_id[data.name]];
|
|
if (!requester) {
|
|
return fail_response("player_gone", { name: data.name });
|
|
}
|
|
// if(requester.party) { socket.emit("game_log","Already partying"); return; }
|
|
if (player.party && (parties[player.party].length >= limits.party_max || player.party_length >= limits.party)) {
|
|
return fail_response("party_full");
|
|
}
|
|
if (!requests[requester.name] || !requests[requester.name][player.id]) {
|
|
return fail_response("request_expired");
|
|
}
|
|
if (player.party && player.party == requester.party) {
|
|
return success_response("already_in_party");
|
|
}
|
|
if (requester.party) {
|
|
leave_party(requester.party, requester);
|
|
requester.socket.emit("party_update", {});
|
|
requester.socket.emit("game_log", "Left the party");
|
|
if (!players[name_to_id[data.name]]) {
|
|
return fail_response("player_gone", { name: data.name });
|
|
}
|
|
}
|
|
requests[requester.name][player.id] = 0;
|
|
if (!player.party) {
|
|
player.party = player.name;
|
|
parties[player.name] = [player.name];
|
|
resend(player, "nc+u+cid");
|
|
delete requests[player.name];
|
|
}
|
|
requester.party = player.party;
|
|
parties[player.party].push(requester.name);
|
|
party_emit(requester.party, "party_update", {
|
|
list: parties[player.party],
|
|
party: party_to_client(player.party),
|
|
message: requester.name + " joined the party",
|
|
});
|
|
resend(requester, "nc+u+cid");
|
|
delete requests[requester.name];
|
|
delete requests[player.name]; // optional
|
|
}
|
|
if (data.event == "leave") {
|
|
if (!player.party) {
|
|
socket.emit("party_update", {});
|
|
return success_response({});
|
|
}
|
|
leave_party(player.party, player);
|
|
socket.emit("party_update", {});
|
|
socket.emit("game_log", "Left the party");
|
|
resend(player, "nc+u+cid");
|
|
}
|
|
if (data.event == "kick") {
|
|
if (!player.party) {
|
|
return success_response({});
|
|
}
|
|
// if(player.party && player.party!=player.name) { socket.emit("game_log","Only the party leader can kick someone"); return; }
|
|
if (!in_arr(data.name, parties[player.party])) {
|
|
socket.emit("party_update", { list: parties[player.party], party: party_to_client(player.party) });
|
|
return success_response({});
|
|
}
|
|
if (parties[player.party].indexOf(player.name) > parties[player.party].indexOf(data.name)) {
|
|
return fail_response("cant_kick");
|
|
}
|
|
var kicked = players[name_to_id[data.name]];
|
|
if (!kicked) {
|
|
return fail_response("player_gone");
|
|
}
|
|
leave_party(player.party, kicked);
|
|
if (player.party) {
|
|
party_emit(player.party, "game_log", player.name + " kicked " + kicked.name);
|
|
}
|
|
kicked.socket.emit("party_update", {});
|
|
kicked.socket.emit("game_log", "You've been removed from the party");
|
|
resend(kicked, "nc+u+cid");
|
|
}
|
|
success_response({});
|
|
});
|
|
socket.on("magiport", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (magiportations[data.name] && magiportations[data.name][player.name] && get_player(data.name)) {
|
|
delete magiportations[data.name][player.name];
|
|
if (instances[get_player(data.name).in].mount != instances[player.in].mount) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (!magiport_someone(player, get_player(data.name))) {
|
|
return fail_response("invalid");
|
|
}
|
|
return success_response({});
|
|
} else {
|
|
player.socket.emit("game_response", { response: "magiport_gone", name: data.name });
|
|
return fail_response("inviter_gone");
|
|
}
|
|
});
|
|
socket.on("trade", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (data.event == "show" && player.p.trades != 1) {
|
|
player.p.trades = 1;
|
|
reslot_player(player);
|
|
resend(player, "nc+u+cid");
|
|
} else if (data.event == "hide" && player.p.trades != null) {
|
|
player.p.trades = null;
|
|
reslot_player(player);
|
|
resend(player, "nc+u+cid");
|
|
}
|
|
});
|
|
socket.on("signup", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!signups[player.name]) {
|
|
disappearing_text(false_socket, npcs.bean, "+1", { color: "#4D9A59", xy: 1 });
|
|
}
|
|
signups[player.name] = true;
|
|
socket.emit("game_response", { response: "signed_up" });
|
|
});
|
|
socket.on("join", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (player.type == "merchant") {
|
|
return fail_response("no_merchants");
|
|
}
|
|
if (player.s.hopsickness) {
|
|
return fail_response("cant_when_sick");
|
|
}
|
|
if (player.user) {
|
|
return fail_response("cant_in_bank");
|
|
}
|
|
if (data.name == "goobrawl" && events.goobrawl) {
|
|
if (player.map != "goobrawl") {
|
|
transport_player_to(player, "goobrawl");
|
|
}
|
|
} else if (data.name == "crabxx" && events.crabxx) {
|
|
if (simple_distance(player, { map: "main", x: -1000, y: 1700 }) > 200) {
|
|
transport_player_to(player, "main", [-1000, 1700, 0, 40]);
|
|
}
|
|
} else if (data.name == "franky" && events.franky) {
|
|
if (simple_distance(player, { map: "level2w", x: -300, y: 150 }) > 200) {
|
|
transport_player_to(player, "level2w", [-300, 150, 0, 40]);
|
|
}
|
|
} else if (data.name == "icegolem" && events.icegolem) {
|
|
if (simple_distance(player, { map: "winterland", x: 820, y: 425 }) > 100) {
|
|
transport_player_to(player, "winterland", [820, 425, 0, 10]);
|
|
}
|
|
} else if (data.name == "abtesting" && E.abtesting) {
|
|
if (player.map != "abtesting" || !player.team) {
|
|
if (
|
|
ssince(timers.abtesting_start) > 120 &&
|
|
(!player.p.abtesting || player.p.abtesting[0] != E.abtesting.id)
|
|
) {
|
|
return fail_response("join_too_late");
|
|
}
|
|
|
|
if (player.p.abtesting && player.p.abtesting[0] == E.abtesting.id) {
|
|
player.team = player.p.abtesting[1];
|
|
} else {
|
|
player.team = random_one(["A", "B"]);
|
|
}
|
|
|
|
player.p.abtesting = [E.abtesting.id, player.team];
|
|
|
|
player.team = player.p.abtesting[1]; // transport does a restore ...
|
|
save_state(player);
|
|
|
|
if (player.team == "A") {
|
|
transport_player_to(player, "abtesting", 2);
|
|
} else if (player.team == "B") {
|
|
transport_player_to(player, "abtesting", 3);
|
|
}
|
|
|
|
resend(player, "cid");
|
|
}
|
|
} else {
|
|
return fail_response("cant_join");
|
|
}
|
|
success_response();
|
|
});
|
|
socket.on("stop", function (data) {
|
|
var player = players[socket.id];
|
|
var change = false;
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!data || !data.action) {
|
|
if (player.s.invis) {
|
|
step_out_of_invis(player);
|
|
change = true;
|
|
}
|
|
if (Object.keys(player.c).length) {
|
|
player.c = {};
|
|
change = true;
|
|
}
|
|
} else if (data.action == "invis") {
|
|
if (player.s.invis) {
|
|
step_out_of_invis(player);
|
|
change = true;
|
|
}
|
|
} else if (data.action == "channeling") {
|
|
if (Object.keys(player.c).length) {
|
|
player.c = {};
|
|
change = true;
|
|
}
|
|
} else if (data.action == "teleport" || data.action == "town") {
|
|
if (player.c.town) {
|
|
delete player.c.town;
|
|
change = true;
|
|
}
|
|
} else if (data.action == "revival") {
|
|
if (player.c.revival) {
|
|
delete player.c.revival;
|
|
change = true;
|
|
}
|
|
}
|
|
if (change) {
|
|
resend(player, "u+cid");
|
|
}
|
|
success_response();
|
|
});
|
|
socket.on("tarot", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
var npc = G.maps[player.map].ref.twitch;
|
|
if (!npc || simple_distance(npc, player) > 500) {
|
|
return socket.emit("game_response", "distance");
|
|
}
|
|
for (var name in player.s) {
|
|
if (name.startsWith("tarot")) {
|
|
return socket.emit("game_response", "tarot_exists");
|
|
}
|
|
}
|
|
});
|
|
socket.on("bet", function (data) {
|
|
if (!instances.tavern) {
|
|
return;
|
|
}
|
|
var player = players[socket.id];
|
|
if (!player || player.user || (player.map != "tavern" && !is_sdk)) {
|
|
return;
|
|
}
|
|
if (player.s.xshotted) {
|
|
return socket.emit("game_response", "bet_xshot");
|
|
}
|
|
if (data.type == "roulette") {
|
|
if (!is_sdk) {
|
|
return;
|
|
} // Old routine [23/08/18]
|
|
if (!data.odds) {
|
|
data.odds = "black";
|
|
}
|
|
data.odds = "" + data.odds;
|
|
data.gold = max(1, parseInt(data.gold) || 1);
|
|
if (!tavern.roulette.odds[data.odds]) {
|
|
return;
|
|
}
|
|
if (tavern.roulette.state != "bets") {
|
|
socket.emit("game_log", "Not taking bets yet!");
|
|
return;
|
|
}
|
|
if (Object.keys(player.bets).length >= 5) {
|
|
socket.emit("game_log", "You already have 5 active bets");
|
|
return;
|
|
}
|
|
if (player.gold < data.gold) {
|
|
socket.emit("game_log", "Not enough gold");
|
|
return;
|
|
}
|
|
|
|
socket.emit("game_log", { message: "Bet accepted on " + data.odds, color: "white" });
|
|
socket.emit("game_log", { message: to_pretty_num(data.gold) + " gold", color: "gray" });
|
|
|
|
var rid = randomStr(10);
|
|
player.gold -= data.gold;
|
|
player.bets[rid] = {
|
|
id: rid,
|
|
type: "roulette",
|
|
odds: data.odds,
|
|
gold: data.gold,
|
|
pid: socket.id,
|
|
state: "bet",
|
|
};
|
|
tavern.roulette.players[player.socket.id] = true;
|
|
io.to("roulette").emit("bet", { name: player.name, type: "roulette", odds: data.odds, gold: data.gold });
|
|
socket.emit("tavern", player.bets[rid]);
|
|
resend(player, "reopen+nc");
|
|
}
|
|
if (data.type == "dice") {
|
|
// server_log(JSON.stringify(data));
|
|
var num = min(99.99, max(parseFloat(data.num) || 0, 0.01));
|
|
var gold = max(10000, min(parseInt(data.gold) || 0, 100000000000));
|
|
var odds = 100.0 / num;
|
|
var dir = "down";
|
|
if (data.dir == "up") {
|
|
odds = 100.0 / (100 - num);
|
|
dir = "up";
|
|
}
|
|
odds = min(odds, 10000);
|
|
odds = parseFloat(floor_f2(odds));
|
|
var win = gold * odds;
|
|
var raw = win;
|
|
var edge = ceil(((gold * odds - gold) * house_edge()) / 100.0);
|
|
win = parseInt(win);
|
|
edge = parseInt(edge);
|
|
if (tavern.dice.state == "roll") {
|
|
return socket.emit("game_response", "tavern_too_late");
|
|
}
|
|
if (tavern.dice.state != "bets") {
|
|
return socket.emit("game_response", "tavern_not_yet");
|
|
}
|
|
if (gold > player.gold) {
|
|
return socket.emit("game_response", "gold_not_enough");
|
|
}
|
|
if (win - edge - gold > (S.gold - house_debt()) * 0.4) {
|
|
return socket.emit("game_response", "tavern_gold_not_enough");
|
|
}
|
|
if (Object.keys(player.bets).length) {
|
|
return socket.emit("game_response", "tavern_dice_exist");
|
|
}
|
|
// if(Object.keys(player.bets).length>=5) return socket.emit("game_response","tavern_too_many_bets");
|
|
// socket.emit("game_log",{"message":"Bet accepted on "+num.toFixed(2)+" "+dir.toUpperCase(),"color":"white"});
|
|
socket.emit("game_log", { message: "Num: " + num.toFixed(2) + " " + dir.toUpperCase(), color: "white" });
|
|
socket.emit("game_log", { message: "Bet: " + to_pretty_num(gold) + " gold", color: "gray" });
|
|
|
|
var rid = randomStr(10);
|
|
player.gold -= gold;
|
|
player.bets[rid] = {
|
|
id: rid,
|
|
type: "dice",
|
|
num: num,
|
|
gold: gold,
|
|
dir: dir,
|
|
pid: socket.id,
|
|
state: "bet",
|
|
win: win,
|
|
edge: edge,
|
|
odds: odds,
|
|
};
|
|
tavern.dice.players[player.socket.id] = true;
|
|
instance_emit(tavern, "tavern", {
|
|
event: "bet",
|
|
name: player.name,
|
|
type: "dice",
|
|
num: num,
|
|
gold: gold,
|
|
dir: dir,
|
|
});
|
|
resend(player, "reopen+nc");
|
|
}
|
|
if (data.type == "slots") {
|
|
var gold = 1000000;
|
|
if (gold > player.gold) {
|
|
return socket.emit("game_response", "gold_not_enough");
|
|
}
|
|
player.gold -= gold;
|
|
S.gold += gold;
|
|
player.q.slots = { ms: 3000 };
|
|
xy_emit(player, "ui", { type: "slots", player: player.name });
|
|
socket.emit("game_response", { response: "gold_use", gold: gold, game: data.type });
|
|
resend(player, "u+cid+reopen+nc");
|
|
}
|
|
});
|
|
socket.on("tavern", function (data) {
|
|
if (data.event == "info") {
|
|
socket.emit("tavern", { event: "info", edge: house_edge(), max: parseInt((S.gold - house_debt()) * 0.4) });
|
|
}
|
|
});
|
|
socket.on("play", function (data) {});
|
|
socket.on("pet", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player) {
|
|
return;
|
|
}
|
|
if (!player.monster) {
|
|
player.monster = new_monster(player.in, {
|
|
type: player.pet,
|
|
stype: "pet",
|
|
x: player.x,
|
|
y: player.y,
|
|
owner: player.name,
|
|
name: "Skimpy",
|
|
});
|
|
}
|
|
});
|
|
socket.on("whistle", function (data) {
|
|
if (!player.monster) {
|
|
return;
|
|
}
|
|
xy_emit(player.monster, "disappear", { id: player.monster.id });
|
|
player.monster.x = player.x;
|
|
player.monster.y = player.y;
|
|
player.monster.map = player.map;
|
|
player.monster.in = player.in;
|
|
player.monster.u = true;
|
|
player.monster.cid++;
|
|
});
|
|
socket.on("list_pvp", function (data) {
|
|
var plist = [];
|
|
for (var j = 1; j < 201; j++) {
|
|
if (pend - j < 0) {
|
|
break;
|
|
}
|
|
plist.push(pwns[(pend - j) % 200]);
|
|
}
|
|
socket.emit("pvp_list", { code: data && data.code, list: plist });
|
|
});
|
|
socket.on("players", function (data) {
|
|
var player = players[socket.id];
|
|
var sdata = [];
|
|
if (!player) {
|
|
return;
|
|
} // || is_pvp
|
|
for (var id in players) {
|
|
var current = players[id];
|
|
var mapn = current.map;
|
|
current.age = parseInt(ceil(hsince(new Date(current.created)) / 24.0));
|
|
if (is_pvp) {
|
|
mapn = "Unknown";
|
|
sdata.push({
|
|
name: "Hidden",
|
|
map: mapn,
|
|
age: current.age,
|
|
level: current.level,
|
|
type: current.type,
|
|
afk: (current.afk && 1) || 0,
|
|
party: (current.party && "Hidden") || "",
|
|
kills: current.kills,
|
|
});
|
|
} else if (!is_in_pvp(current)) {
|
|
sdata.push({
|
|
name: current.name,
|
|
map: mapn,
|
|
age: current.age,
|
|
level: current.level,
|
|
type: current.type,
|
|
afk: (current.afk && 1) || 0,
|
|
party: current.party || "",
|
|
});
|
|
}
|
|
}
|
|
socket.emit("players", sdata);
|
|
});
|
|
socket.on("pets", function (data) {
|
|
var player = players[socket.id];
|
|
var sdata = [];
|
|
if (!player) {
|
|
return;
|
|
} // || is_pvp
|
|
for (var id in player.p.pets || {}) {
|
|
sdata.push(player.p.pets[id]);
|
|
}
|
|
socket.emit("players", sdata);
|
|
});
|
|
socket.on("harakiri", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player || player.rip) {
|
|
return;
|
|
}
|
|
defeat_player(player);
|
|
if (!player.rip) {
|
|
rip(player);
|
|
}
|
|
disappearing_text(player.socket, player, "SEPPUKU", { xy: 1, size: "huge", color: "#6F76A6" });
|
|
resend(player, "u+cid");
|
|
});
|
|
socket.on("deepsea", function (data) {
|
|
return;
|
|
var player = players[socket.id];
|
|
if (!player || player.rip || player.tskin == "deepsea") {
|
|
return;
|
|
}
|
|
player.tskin = "deepsea";
|
|
disappearing_text(player.socket, player, "ROARRRRRRR", { xy: 1, size: "huge", color: "#60A975" });
|
|
resend(player, "u+cid");
|
|
});
|
|
socket.on("blend", function (data) {
|
|
var player = players[socket.id];
|
|
var min = 99999;
|
|
var x = null;
|
|
if (!player || player.rip) {
|
|
return;
|
|
}
|
|
for (var id in instances[player.in].monsters || {}) {
|
|
var m = instances[player.in].monsters[id];
|
|
var c = distance(m, player, true);
|
|
if (c < min) {
|
|
min = c;
|
|
x = m;
|
|
}
|
|
}
|
|
if (x) {
|
|
var skin = G.monsters[x.type].skin || x.type;
|
|
if (player.tskin != skin) {
|
|
player.tskin = skin;
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
});
|
|
socket.on("skin", function (data) {
|
|
var player = players[socket.id];
|
|
if (player.role != "gm") {
|
|
return;
|
|
}
|
|
player.tskin = data.name;
|
|
resend(player, "u+cid");
|
|
});
|
|
socket.on("legacify", function (data) {
|
|
var player = players[socket.id];
|
|
if (!player || player.rip) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < 42; i++) {
|
|
if (0 && player.items[i] && !player.items[i].p && ["fury", "starkillers"].includes(player.items[i].name)) {
|
|
player.items[i].p = "legacy";
|
|
}
|
|
}
|
|
resend(player, "reopen");
|
|
});
|
|
socket.on("requested_ack", function (data) {
|
|
// send "requesting_ack", to verify someone is actually connected [03/08/16]
|
|
server_log("requested_ack" + JSON.stringify(data), 1);
|
|
});
|
|
socket.on("disconnect", function () {
|
|
//#IMPORTANT: disconnect exceptions are fatal [07/08/16]
|
|
// console.log("disconnect!");
|
|
var player = players[socket.id];
|
|
var observer = observers[socket.id];
|
|
try {
|
|
delete sockets[socket.id];
|
|
} catch (e) {}
|
|
if (player) {
|
|
player.dc = true;
|
|
try {
|
|
defeat_player(player);
|
|
} catch (e) {
|
|
log_trace("#X DCERRORPVP", e);
|
|
}
|
|
// if(!server.live) { server_log('Ignoring the "disconnect" of '+player.name,1); return; }
|
|
|
|
try {
|
|
if (player.moving && distance(player, { x: player.going_x, y: player.going_y }) < 800) {
|
|
player.x = player.going_x;
|
|
player.y = player.going_y;
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X DCERRORM", e);
|
|
}
|
|
// to not get stuck on out-of-bounds corners
|
|
|
|
try {
|
|
if (player.party) {
|
|
leave_party(player.party, player);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X DCERROR1", e);
|
|
}
|
|
|
|
try {
|
|
xy_emit(player, "disappear", { id: player.id, reason: "disconnect" });
|
|
} catch (e) {
|
|
log_trace("#X DCERROR2", e);
|
|
}
|
|
|
|
server_log("Disconnected Player: " + socket.id + " " + player.name + " Calls: " + socket.total_calls, 1);
|
|
|
|
try {
|
|
for (var bid in player.bets) {
|
|
player.gold += player.bets[bid].gold;
|
|
}
|
|
player.bets = {};
|
|
} catch (e) {
|
|
log_trace("#X DCERRORBETS", e);
|
|
}
|
|
|
|
try {
|
|
if (player.monster) {
|
|
remove_monster(player.monster);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X DCERRORPETS", e);
|
|
}
|
|
|
|
try {
|
|
delete players[socket.id];
|
|
delete instances[player.in].players[player.id];
|
|
if (instances[player.in].solo == player.id) {
|
|
destroy_instance(player.in);
|
|
}
|
|
pmap_remove(player);
|
|
} catch (e) {
|
|
log_trace("#X DCERROR3 ", e);
|
|
}
|
|
|
|
try {
|
|
restore_state(player, true);
|
|
} catch (e) {
|
|
log_trace("#X DCERRORrestore", e);
|
|
}
|
|
|
|
try {
|
|
if (gameplay != "hardcore" && gameplay != "test") {
|
|
dc_players[player.real_id] = player;
|
|
sync_loop();
|
|
} else {
|
|
save_player(player);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X DCERROR", e);
|
|
}
|
|
}
|
|
if (observer) {
|
|
server_log("Disconnected Observer: " + socket.id + " Calls: " + socket.total_calls);
|
|
try {
|
|
delete_observer(socket);
|
|
} catch (e) {
|
|
log_trace("#X DCERRORO ", e);
|
|
}
|
|
}
|
|
});
|
|
socket.on("shutdown", function (data) {
|
|
if (data.pass != variables.access_master) {
|
|
return;
|
|
}
|
|
if (data.reason) {
|
|
broadcast("disconnect_reason", data.reason);
|
|
}
|
|
setTimeout(shutdown_routine, 10000);
|
|
});
|
|
socket.on("notice", function (data) {
|
|
if (data.pass != variables.access_master) {
|
|
return;
|
|
}
|
|
broadcast("notice", { message: data.message });
|
|
});
|
|
socket.on("render", function (data) {
|
|
if (data.pass != variables.access_master) {
|
|
return;
|
|
}
|
|
var player = players[socket.id];
|
|
var output = "";
|
|
var json_output = undefined;
|
|
var window = null;
|
|
var after = "";
|
|
try {
|
|
eval(data.code);
|
|
} catch (e) {
|
|
output = "Exception: " + e;
|
|
}
|
|
if (window) {
|
|
socket.emit("simple_eval", {
|
|
window: window,
|
|
after: after,
|
|
code: "for(var id in data.window) window[id]=data.window[id]; eval(data.after);",
|
|
});
|
|
} else if (json_output !== undefined) {
|
|
socket.emit("simple_eval", { output: output, json_output: json_output, code: "show_json(data.json_output)" });
|
|
} else {
|
|
socket.emit("simple_eval", {
|
|
output: output,
|
|
json_output: json_output,
|
|
code: 'show_modal("<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((spi) => {
|
|
const condition = spi[0]; // interval or "hp:0.75"
|
|
const name = spi[1]; // monster type
|
|
const count = spi[2] || 1; // default to 1
|
|
|
|
// --- Timed spawns (existing) ---
|
|
if (typeof condition === "number") {
|
|
if (!monster.last[name] || mssince(monster.last[name]) > condition) {
|
|
for (let i = 0; i < count; i++) {
|
|
const pname = random_one(Object.keys(monster.points));
|
|
const player = get_player(pname);
|
|
if (!player || player.npc || distance(monster, player) > 400) return;
|
|
if (!is_same(player, get_player(monster.target), true)) return;
|
|
|
|
monster.last[name] = new Date();
|
|
const spot = safe_xy_nearby(
|
|
player.map,
|
|
player.x + Math.random() * 20 - 10,
|
|
player.y + Math.random() * 20 - 10,
|
|
);
|
|
if (!spot) return;
|
|
|
|
new_monster(instance.name, {
|
|
type: name,
|
|
stype: "spawn",
|
|
x: spot.x,
|
|
y: spot.y,
|
|
target: player.name,
|
|
master: monster.id,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- HP threshold spawns (new) ---
|
|
if (typeof condition === "string" && condition.startsWith("hp:")) {
|
|
const threshold = parseFloat(condition.split(":")[1]); // e.g. 0.75
|
|
const currentHpRatio = monster.hp / monster.max_hp;
|
|
const key = name + "_hp_" + threshold;
|
|
|
|
if (currentHpRatio <= threshold && (monster.last[key] == undefined || monster.last[key] < count)) {
|
|
const pname = random_one(Object.keys(monster.points));
|
|
const player = get_player(pname);
|
|
if (player && !player.npc && distance(monster, player) < 400) {
|
|
if (is_same(player, get_player(monster.target), true)) {
|
|
const spot = safe_xy_nearby(
|
|
player.map,
|
|
player.x + Math.random() * 20 - 10,
|
|
player.y + Math.random() * 20 - 10,
|
|
);
|
|
if (spot) {
|
|
new_monster(instance.name, {
|
|
type: name,
|
|
stype: "spawn",
|
|
x: spot.x,
|
|
target: player.name,
|
|
master: monster.id,
|
|
});
|
|
monster.last[key] = 1 + (monster.last[key] || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function attack_target_or_move() {
|
|
var player = players[name_to_id[monster.target]];
|
|
if (player && ssince(monster.last.attacked) > 20 && Math.random() > monster.rage * 0.99) {
|
|
stop_pursuit(monster, { force: true, cause: "bored" });
|
|
return;
|
|
}
|
|
if (focus && distance(focus, monster) > 40 && !monster.moving) {
|
|
if (mode.all_smart) {
|
|
if (!monster.worker) {
|
|
monster.working = true;
|
|
workers[wlast++ % workers.length].postMessage({
|
|
type: "fast_astar",
|
|
in: monster.in,
|
|
id: monster.id,
|
|
map: monster.map,
|
|
sx: monster.x,
|
|
sy: monster.y,
|
|
tx: focus.x,
|
|
ty: focus.y,
|
|
});
|
|
}
|
|
} else if (!G.monsters[monster.type].stationary) {
|
|
monster.ogoing_x = monster.going_x;
|
|
monster.ogoing_y = monster.going_y;
|
|
monster.going_x = monster.x + (focus.x - monster.x) / 2;
|
|
monster.going_y = monster.y + (focus.y - monster.y) / 2;
|
|
if (mode.path_checks && !can_move(monster)) {
|
|
monster.going_x = monster.ogoing_x;
|
|
monster.going_y = monster.ogoing_y;
|
|
} else {
|
|
start_moving_element(monster);
|
|
}
|
|
}
|
|
}
|
|
if (player && player.in == monster.in && !player.rip && !is_invis(player)) {
|
|
if (
|
|
distance(player, monster, true) >
|
|
min(monster.range / 1.6, 240) + min(100, monster.attack / 5.0) + 160 + ((mode.all_smart && 320) || 1) &&
|
|
!monster.walk_once
|
|
) {
|
|
stop_pursuit(monster, { cause: "exceeds_range" });
|
|
} else if (can_attack(monster, player)) {
|
|
var attack = commence_attack(monster, player, "attack");
|
|
if (attack && attack.events && attack.events.length) {
|
|
events.push(...attack.events);
|
|
}
|
|
} else if (
|
|
distance(monster, player, true) > 12 &&
|
|
!mode.range_test &&
|
|
!(mode.all_smart && monster.moving) &&
|
|
!focus &&
|
|
!G.monsters[monster.type].stationary
|
|
) {
|
|
// console.log(monster.height);
|
|
if (mode.all_smart) {
|
|
if (!monster.worker) {
|
|
monster.working = true;
|
|
workers[wlast++ % workers.length].postMessage({
|
|
type: "fast_astar",
|
|
in: monster.in,
|
|
id: monster.id,
|
|
map: monster.map,
|
|
sx: monster.x,
|
|
sy: monster.y,
|
|
tx: player.x,
|
|
ty: player.y,
|
|
});
|
|
}
|
|
} else {
|
|
monster.ogoing_x = monster.going_x;
|
|
monster.ogoing_y = monster.going_y;
|
|
monster.going_x = monster.x + (player.x - monster.x) / 2;
|
|
monster.going_y = monster.y + (player.y - monster.y) / 2;
|
|
if (mode.path_checks && !can_move(monster)) {
|
|
monster.going_x = monster.ogoing_x;
|
|
monster.going_y = monster.ogoing_y;
|
|
if (monster.attack < 120 || distance(monster, player, true) > monster.range) {
|
|
stop_pursuit(monster, { cause: "cant_move" });
|
|
}
|
|
} else {
|
|
// console.log("Moving to "+monster.going_x+" "+monster.going_y);
|
|
start_moving_element(monster);
|
|
}
|
|
}
|
|
}
|
|
} else if (monster.target) {
|
|
stop_pursuit(monster, { cause: "player_gone" });
|
|
}
|
|
if (monster.walk_once) {
|
|
monster.walk_once = false;
|
|
}
|
|
}
|
|
if (monster.moving) {
|
|
var ms = mssince(monster.last.move);
|
|
ms = min(ms, 2000); // to prevent monsters from jumping off the map when the machine sleeps
|
|
monster.x += (monster.vx * ms) / 1000.0;
|
|
monster.y += (monster.vy * ms) / 1000.0;
|
|
stop_logic(monster);
|
|
monster.last.move = new Date();
|
|
xy_u_logic(monster);
|
|
|
|
if (monster.moving && monster.attack > 100 && monster.target) {
|
|
attack_target_or_move();
|
|
}
|
|
} else if (monster.s.sleeping || monster.working) {
|
|
} else if (can_walk(monster)) {
|
|
// for the .s.stunned check
|
|
if (monster.s.magiport) {
|
|
} else if (monster.target || focus) {
|
|
attack_target_or_move();
|
|
} else if (!mode.upush_test) {
|
|
if (monster.map_def.position && !mode.all_roam) {
|
|
var position = monster.map_def.position;
|
|
var radius = monster.map_def.radius;
|
|
var dx = Math.random() * radius - radius / 2;
|
|
var dy = Math.random() * radius - radius / 2;
|
|
if (abs(dx) + abs(dy) < 80) {
|
|
// optimization to prevent short walks
|
|
if (dx < 0 && dx > -60) {
|
|
dx = -60;
|
|
}
|
|
if (dx > 0 && dx < 60) {
|
|
dx = 60;
|
|
}
|
|
if (dy < 0 && dy > -60) {
|
|
dy = -60;
|
|
}
|
|
if (dy > 0 && dy < 60) {
|
|
dy = 60;
|
|
}
|
|
}
|
|
monster.going_x = monster.x + dx;
|
|
monster.going_y = monster.y + dy;
|
|
if (monster.going_x > position[0] + radius) {
|
|
monster.going_x = position[0] + radius;
|
|
}
|
|
if (monster.going_y > position[1] + radius) {
|
|
monster.going_y = position[1] + radius;
|
|
}
|
|
if (monster.going_x < position[0] - radius) {
|
|
monster.going_x = position[0] - radius;
|
|
}
|
|
if (monster.going_y < position[1] - radius) {
|
|
monster.going_y = position[1] - radius;
|
|
}
|
|
start_moving_element(monster);
|
|
} else if (
|
|
monster.map_def.roam ||
|
|
G.monsters[monster.type].roam ||
|
|
mode.all_roam ||
|
|
(monster.map_def.polygon && !monster.irregular)
|
|
) {
|
|
perfc.roams += 1;
|
|
var map_def = monster.map_def;
|
|
var tries = 1;
|
|
if (!monster.map_def.roam && !mode.all_roam && monster.map_def.polygon) {
|
|
tries = 12;
|
|
}
|
|
if (!monster.rmove) {
|
|
monster.rmove = parseInt(Math.random() * 100);
|
|
}
|
|
monster.dmove = monster.dmove || 0;
|
|
for (var t = 0; t < tries; t++) {
|
|
var moves = [
|
|
[1, 0],
|
|
[0.8, 0.8],
|
|
[0, 1],
|
|
[-0.8, 0.8],
|
|
[-1, 0],
|
|
[-0.8, -0.8],
|
|
[0, -1],
|
|
[0.8, -0.8],
|
|
];
|
|
var multipliers = [500, 200, 100, 50, 10];
|
|
if (Math.random() < 0.1) {
|
|
monster.rmove = parseInt(Math.random() * moves.length);
|
|
}
|
|
var move = moves[monster.rmove % moves.length];
|
|
var d = multipliers[monster.dmove % multipliers.length];
|
|
monster.going_x = monster.x + move[0] * d;
|
|
monster.going_y = monster.y + move[1] * d;
|
|
if (tries > 1 && monster.map_def.polygon) {
|
|
if (is_point_inside([monster.going_x, monster.going_y], monster.map_def.polygon)) {
|
|
break;
|
|
} else {
|
|
monster.rmove++;
|
|
monster.dmove++;
|
|
}
|
|
}
|
|
}
|
|
// console.log(monster.rmove+","+monster.dmove+" "+monster.going_x+","+monster.going_y);
|
|
if (can_move(monster)) {
|
|
start_moving_element(monster);
|
|
monster.dmove = 0;
|
|
} else {
|
|
monster.rmove++;
|
|
monster.dmove++;
|
|
}
|
|
} else if (
|
|
monster.map_def.stype != "pet" &&
|
|
monster.map_def.stype != "spawn" &&
|
|
monster.map_def.stype != "trap"
|
|
) {
|
|
var map_def = monster.map_def;
|
|
var to_move = true;
|
|
if (map_def.polygon) {
|
|
var p = random_point(map_def.polygon, monster.base);
|
|
monster.going_x = p[0];
|
|
monster.going_y = p[1];
|
|
} else {
|
|
monster.going_x = map_def.boundary[0] + Math.random() * (map_def.boundary[2] - map_def.boundary[0]);
|
|
monster.going_y = map_def.boundary[1] + Math.random() * (map_def.boundary[3] - map_def.boundary[1]);
|
|
}
|
|
if (monster.irregular == 3) {
|
|
// new [01/03/19]
|
|
monster.m++;
|
|
setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500);
|
|
remove_monster(monster, { nospawn: true, method: "disappear" });
|
|
} else if (monster.irregular == 2) {
|
|
server_log("Irregular2 move for " + monster.id);
|
|
if (monster.in != monster.oin) {
|
|
port_monster(monster, { map: monster.oin, x: monster.going_x, y: monster.going_y, in: monster.oin });
|
|
} else {
|
|
recalculate_move(monster);
|
|
}
|
|
monster.irregular = 1;
|
|
} else if (monster.irregular == 1) {
|
|
if (!can_move(monster)) {
|
|
monster.m++;
|
|
server_log("Irregular1 respawn: " + monster.id);
|
|
setTimeout(new_monster_f(monster.oin, monster.map_def, { last_state: monster }), 500);
|
|
remove_monster(monster, { nospawn: true, method: "disappear" });
|
|
} else {
|
|
server_log("No longer irregular: " + monster.id);
|
|
delete monster["irregular"];
|
|
}
|
|
}
|
|
if (monster.going_x != monster.x || monster.going_y != monster.y) {
|
|
// so Automatron's don't move
|
|
start_moving_element(monster);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!monster.dead && (monster.u || events.length)) {
|
|
monster.u = false;
|
|
to_push.push({ id: id, entity: monster, data: monster_to_client(monster, events) });
|
|
monster_map[id] = to_push[to_push.length - 1];
|
|
}
|
|
}
|
|
|
|
for (var id in instance.players) {
|
|
var player = instance.players[id];
|
|
if (!player) {
|
|
continue;
|
|
}
|
|
for (var name in player.s) {
|
|
var def = G.conditions[name];
|
|
var ref = player.s[name];
|
|
var value = player.s[name].ms;
|
|
player.s[name].ms -= ms;
|
|
if (def && def.interval) {
|
|
if (!player.s[name].last || mssince(player.s[name].last) >= def.interval) {
|
|
player.s[name].last = new Date();
|
|
if (name == "eburn") {
|
|
disappearing_text(player.socket, player, "-50", { color: "red", xy: 1 });
|
|
player.hp = max(1, player.hp - 50);
|
|
}
|
|
if (name == "eheal") {
|
|
disappearing_text(player.socket, player, "+50", { color: "heal", xy: 1 });
|
|
player.hp = min(player.max_hp, player.hp + 50);
|
|
}
|
|
if (name == "burned") {
|
|
var damage = ceil(ref.intensity / 5);
|
|
disappearing_text(player.socket, player, "-" + damage, { color: "red", xy: 1 });
|
|
player.hp = max(0, player.hp - damage);
|
|
xy_emit(player, "hit", {
|
|
source: "burn",
|
|
hid: ref.fid,
|
|
id: player.name,
|
|
damage: damage,
|
|
kill: player.hp <= 0,
|
|
});
|
|
player_rip_logic(player);
|
|
}
|
|
resend(player, "u+cid+nc");
|
|
}
|
|
}
|
|
if (in_arr(name, ["damage_received"])) {
|
|
player.s[name].amount = max(0, (player.s[name].amount * (4000 - ms)) / 4000.0);
|
|
}
|
|
if (player.s[name].ms <= 0) {
|
|
delete player.s[name];
|
|
if (name == "blink") {
|
|
if (player.s.dampened) {
|
|
xy_emit(player, "ui", { type: "dampened", name: player.name });
|
|
} else {
|
|
decay_s(player, 30000);
|
|
transport_player_to(player, ref.in, [ref.x, ref.y, ref.d], "blink");
|
|
}
|
|
}
|
|
if (name == "magiport") {
|
|
var dampened = false;
|
|
for (var id in instances[ref.in].monsters) {
|
|
var m = instances[ref.in].monsters[id];
|
|
if (m.type == "fieldgen0" && point_distance(ref.x, ref.y, m.x, m.y) < 300) {
|
|
xy_emit(player, "ui", { type: "dampened", name: player.name });
|
|
dampened = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!dampened) {
|
|
var skip = false;
|
|
if (ref.map != player.map) {
|
|
skip = true;
|
|
}
|
|
decay_s(player, 30000);
|
|
transport_player_to(player, ref.in, [ref.x, ref.y], "magiport");
|
|
if (skip) {
|
|
resend(player, "u+cid");
|
|
return; // player isn't in this instance any more
|
|
}
|
|
}
|
|
}
|
|
if (def) {
|
|
player.socket.emit("game_response", { response: "ex_condition", name: name });
|
|
}
|
|
if (def && def.ui) {
|
|
resend(player, "u+cid");
|
|
} // +cid to update the UI's [23/10/18]
|
|
else {
|
|
resend(player, "u");
|
|
} // #TODO: don't need to resend actually, maybe reconsider [27/06/18]
|
|
}
|
|
}
|
|
for (var name in player.q) {
|
|
var value = player.q[name].ms;
|
|
var ref = player.q[name];
|
|
player.q[name].ms -= ms;
|
|
if (name == "upgrade") {
|
|
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
var def = player.items[ref.num].p;
|
|
var change = false;
|
|
if (value < ref.len * 0.8 && def.nums[0] === undefined) {
|
|
def.nums[0] = parseInt(player.p.u_roll * 10000) % 10;
|
|
change = true;
|
|
}
|
|
if (value < ref.len * 0.64 && def.nums[1] === undefined) {
|
|
def.nums[1] = parseInt(player.p.u_roll * 1000) % 10;
|
|
change = true;
|
|
}
|
|
if (value < ref.len * 0.4 && def.nums[2] === undefined) {
|
|
def.nums[2] = parseInt(player.p.u_roll * 100) % 10;
|
|
change = true;
|
|
}
|
|
if (value < min(3000, ref.len * 0.3) && def.nums[3] === undefined) {
|
|
def.nums[3] = parseInt(player.p.u_roll * 10);
|
|
change = true;
|
|
}
|
|
if (value < min(2200, ref.len * 0.22) && player.p.u_item && !player.p.u_fail && !def.success) {
|
|
def.success = true;
|
|
change = true;
|
|
}
|
|
if (value < min(2200, ref.len * 0.22) && (player.p.u_itemx || player.p.u_fail) && !def.failure) {
|
|
def.failure = true;
|
|
change = true;
|
|
}
|
|
if (change) {
|
|
player.socket.emit("q_data", { q: player.q, num: ref.num, p: def });
|
|
}
|
|
}
|
|
}
|
|
if (name == "compound") {
|
|
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
var def = player.items[ref.num].p;
|
|
var change = false;
|
|
if (value < 8000 && def.nums[0] === undefined) {
|
|
def.nums[0] = parseInt(player.p.c_roll * 10000) % 10;
|
|
change = true;
|
|
}
|
|
if (value < 6400 && def.nums[1] === undefined) {
|
|
def.nums[1] = parseInt(player.p.c_roll * 1000) % 10;
|
|
change = true;
|
|
}
|
|
if (value < 5000 && def.nums[2] === undefined) {
|
|
def.nums[2] = parseInt(player.p.c_roll * 100) % 10;
|
|
change = true;
|
|
}
|
|
if (value < 3000 && def.nums[3] === undefined) {
|
|
def.nums[3] = parseInt(player.p.c_roll * 10);
|
|
change = true;
|
|
}
|
|
if (value < 2200 && player.p.c_item && !def.success) {
|
|
def.success = true;
|
|
change = true;
|
|
}
|
|
if (value < 2200 && player.p.c_itemx && !def.failure) {
|
|
def.failure = true;
|
|
change = true;
|
|
}
|
|
if (change) {
|
|
player.socket.emit("q_data", { q: player.q, num: ref.num, p: def });
|
|
}
|
|
}
|
|
}
|
|
if (player.q[name].ms <= 0) {
|
|
delete player.q[name];
|
|
if (name == "exchange") {
|
|
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
player.citems[ref.num] = player.items[ref.num] = null;
|
|
}
|
|
exchange(player, ref.id, { v: ref.v });
|
|
if (ref.qs) {
|
|
player.socket.emit("game_response", { response: ref.qs + "_success", suffix: ref.s || "" });
|
|
} else {
|
|
xy_emit(G.maps.main.exchange, "upgrade", { type: "exchange", success: 1 });
|
|
}
|
|
resend(player, "reopen+u+cid");
|
|
}
|
|
if (name == "compound") {
|
|
if (player.p.c_item) {
|
|
var item = player.p.c_item;
|
|
var def = G.items[item.name];
|
|
player.hitchhikers.push([
|
|
"game_response",
|
|
{
|
|
response: "compound_success",
|
|
stale: ref.stale,
|
|
level: item.level,
|
|
num: ref.num,
|
|
up: item.extra || undefined,
|
|
},
|
|
]);
|
|
if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 1800000 && !player.stealth) {
|
|
broadcast("server_message", {
|
|
message: player.name + " received " + item_to_phrase(item),
|
|
color: colors.server_success,
|
|
item: cache_item(item),
|
|
type: "server_csuccess",
|
|
name: player.name,
|
|
});
|
|
}
|
|
xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 1 });
|
|
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
player.items[ref.num] = item;
|
|
player.citems[ref.num] = cache_item(player.items[ref.num]);
|
|
}
|
|
achievement_logic_compound_success(player, item);
|
|
} else {
|
|
var item = player.p.c_itemx;
|
|
var def = G.items[item.name];
|
|
player.hitchhikers.push([
|
|
"game_response",
|
|
{ response: "compound_fail", level: item.level, num: ref.num, stale: ref.stale },
|
|
]);
|
|
if (calculate_item_value(item) + (def.edge || 0) * 2000000 > 920000 && !player.stealth) {
|
|
broadcast("server_message", {
|
|
message: player.name + " lost " + item_to_phrase(item) + "'s",
|
|
color: colors.server_failure,
|
|
item: cache_item(item),
|
|
type: "server_cfail",
|
|
name: player.name,
|
|
});
|
|
}
|
|
xy_emit(G.maps.main.compound, "upgrade", { type: "compound", success: 0 });
|
|
if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
player.citems[ref.num] = player.items[ref.num] = null;
|
|
player.esize++;
|
|
}
|
|
}
|
|
delete player.p.c_item;
|
|
delete player.p.c_itemx;
|
|
delete player.p.c_roll;
|
|
resend(player, "reopen+u+cid+nc+inv");
|
|
}
|
|
if (name == "upgrade") {
|
|
var success = false;
|
|
var announce = false;
|
|
var new_level = player.p.u_level + 1;
|
|
var item = player.p.u_item || player.p.u_itemx;
|
|
var p = player.items[ref.num] && player.items[ref.num].p;
|
|
if (player.p.u_item && player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
success = true;
|
|
player.items[ref.num] = player.p.u_item;
|
|
player.citems[ref.num] = cache_item(player.items[ref.num]);
|
|
} else if (player.items[ref.num] && player.items[ref.num].name == "placeholder") {
|
|
player.citems[ref.num] = player.items[ref.num] = null;
|
|
player.esize++;
|
|
}
|
|
if (player.p.u_type == "offering") {
|
|
if (success) {
|
|
player.hitchhikers.push(["game_response", { response: "upgrade_offering_success", stale: ref.stale }]);
|
|
}
|
|
} else if (player.p.u_type == "stat") {
|
|
if (success) {
|
|
player.hitchhikers.push([
|
|
"game_response",
|
|
{
|
|
response: "upgrade_success_stat",
|
|
stale: ref.stale,
|
|
stat_type: p && G.items[p.scroll].stat,
|
|
num: ref.num,
|
|
},
|
|
]);
|
|
}
|
|
} else if (!ref.silent) {
|
|
announce = true;
|
|
}
|
|
|
|
if (success && !player.p.u_fail) {
|
|
player.hitchhikers.push([
|
|
"game_response",
|
|
{ response: "upgrade_success", level: new_level, num: ref.num, stale: ref.stale },
|
|
]);
|
|
if (announce && calculate_item_value(item) > 4800000 && !player.stealth) {
|
|
broadcast("server_message", {
|
|
message: player.name + " received " + item_to_phrase(item),
|
|
color: colors.server_success,
|
|
item: cache_item(item),
|
|
type: "server_usuccess",
|
|
name: player.name,
|
|
});
|
|
}
|
|
xy_emit((instances.main && G.maps.main.upgrade) || player, "upgrade", { type: "upgrade", success: 1 });
|
|
achievement_logic_upgrade_success(player, item);
|
|
} else {
|
|
player.hitchhikers.push([
|
|
"game_response",
|
|
{ response: "upgrade_fail", level: new_level, num: ref.num, stale: ref.stale },
|
|
]);
|
|
if (announce && calculate_item_value(item) > 4800000 && !player.stealth) {
|
|
broadcast("server_message", {
|
|
message: player.name + " lost " + item_to_phrase(item),
|
|
color: colors.server_failure,
|
|
item: cache_item(item),
|
|
type: "server_ufail",
|
|
name: player.name,
|
|
});
|
|
}
|
|
xy_emit((instances.main && G.maps.main.upgrade) || player, "upgrade", { type: "upgrade", success: 0 });
|
|
}
|
|
delete player.p.u_item;
|
|
delete player.p.u_type;
|
|
delete player.p.u_itemx;
|
|
delete player.p.u_roll;
|
|
delete player.p.u_fail;
|
|
delete player.p.u_level;
|
|
resend(player, "reopen+u+cid+nc+inv");
|
|
}
|
|
if (name == "slots") {
|
|
if (Math.random() < ((S.gold > 500000000 && D.odds.slots_good) || D.odds.slots)) {
|
|
var gold = 500000000;
|
|
player.gold += gold;
|
|
S.gold -= gold;
|
|
broadcast("server_message", {
|
|
message: player.name + " received " + to_pretty_num(gold) + " gold",
|
|
color: "gold",
|
|
});
|
|
player.socket.emit("game_response", "slots_success");
|
|
player.socket.emit("game_log", { message: "Received " + to_pretty_num(gold) + " gold", color: "gold" });
|
|
// resend(player,"u+cid");
|
|
} else {
|
|
player.socket.emit("game_response", "slots_fail");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (var name in player.c) {
|
|
player.c[name].ms -= ms;
|
|
if (player.c[name].ms <= 0) {
|
|
var ref = player.c[name];
|
|
delete player.c[name];
|
|
if (name == "town") {
|
|
decay_s(player, 4000);
|
|
player.last.town = new Date();
|
|
transport_player_to(player, player.in, undefined, 1);
|
|
resend(player);
|
|
}
|
|
if (name == "pickpocket") {
|
|
var target = get_player(ref.target);
|
|
if (!target) {
|
|
player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "player_gone" });
|
|
} else if (distance(player, target) > 20) {
|
|
player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "distance" });
|
|
} else {
|
|
var num = floor(Math.random() * 42);
|
|
if (target.items[num] && target.items[num].v) {
|
|
var item = target.items[num];
|
|
if (item.q && item.q > 1) {
|
|
target.items[num].q -= 1;
|
|
target.citems[num].q -= 1;
|
|
item = create_new_item(item.name);
|
|
} else {
|
|
target.citems[num] = target.items[num] = null;
|
|
}
|
|
item.v = new Date();
|
|
add_item(player, item);
|
|
player.socket.emit("game_response", { response: "picked", slot: "mainhand", cevent: true });
|
|
consume_skill(player, "pickpocket", true);
|
|
resend(player, "reopen");
|
|
resend(target, "reopen");
|
|
target.socket.emit("game_response", { response: "got_picked", cevent: true });
|
|
} else {
|
|
player.socket.emit("game_response", { response: "pick_failed", cevent: true, reason: "misfortune" });
|
|
}
|
|
}
|
|
}
|
|
if (name == "fishing") {
|
|
if (Math.random() < 0.1) {
|
|
if (player.esize) {
|
|
exchange(player, ref.drop, { phrase: "Fished" });
|
|
}
|
|
consume_skill(player, "fishing", true);
|
|
if (player.slots.mainhand) {
|
|
var prop = calculate_item_properties(player.slots.mainhand);
|
|
if (prop.breaks && Math.random() < max(0, prop.breaks / 100.0)) {
|
|
player.cid++;
|
|
player.u = true;
|
|
player.cslots.mainhand = player.slots.mainhand = null;
|
|
player.socket.emit("game_log", "Your rod broke down ...");
|
|
player.socket.emit("game_response", { response: "data", cevent: "item_break", slot: "mainhand" });
|
|
}
|
|
}
|
|
player.socket.emit("game_response", { response: "data", cevent: "fishing_success", slot: "mainhand" });
|
|
resend(player, "reopen");
|
|
} else {
|
|
player.socket.emit("ui", { name: player.name, type: "fishing_none", cevent: true });
|
|
}
|
|
}
|
|
if (name == "mining") {
|
|
if (Math.random() < 0.2) {
|
|
if (player.esize) {
|
|
exchange(player, ref.drop, { phrase: "Mined" });
|
|
}
|
|
consume_skill(player, "mining", true);
|
|
if (player.slots.mainhand) {
|
|
var prop = calculate_item_properties(player.slots.mainhand);
|
|
if (prop.breaks && Math.random() < max(0, prop.breaks / 100.0)) {
|
|
player.cid++;
|
|
player.u = true;
|
|
player.cslots.mainhand = player.slots.mainhand = null;
|
|
player.socket.emit("game_log", "Your pickaxe broke down ...");
|
|
}
|
|
}
|
|
resend(player, "reopen");
|
|
} else {
|
|
player.socket.emit("ui", { type: "mining_none" });
|
|
}
|
|
}
|
|
if (name == "revival") {
|
|
player.rip = false;
|
|
invincible_logic(player);
|
|
if (player.party) {
|
|
send_party_update(player.party);
|
|
}
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
}
|
|
if (player.slots.elixir && player.slots.elixir.expires && now_date > player.slots.elixir.expires) {
|
|
try {
|
|
var def = G.items[player.slots.elixir.name];
|
|
player.socket.emit("game_log", def.name + " wore off ...");
|
|
if (def.withdrawal) {
|
|
add_condition(player, def.withdrawal);
|
|
}
|
|
} catch (e) {}
|
|
player.slots.elixir = null;
|
|
player.cslots.elixir = null;
|
|
resend(player, "reopen+u+cid");
|
|
}
|
|
if (player.moving) {
|
|
var ms = mssince(player.last.move);
|
|
player.x += (player.vx * ms) / 1000.0;
|
|
player.y += (player.vy * ms) / 1000.0;
|
|
player.red_zone *= 0.99;
|
|
if (smap_data[player.map] != -1 && !player.npc && mode.red_zone && !player.s.dash) {
|
|
var current = smap_data[player.map][phash(player)];
|
|
if (current === undefined) {
|
|
current = 8;
|
|
}
|
|
current = max(0, current - 1); // 1 is the new 0 [01/08/18]
|
|
player.red_zone += current;
|
|
// console.log(player.red_zone);
|
|
if (player.red_zone > smap_edge) {
|
|
player.red_zone = 0;
|
|
player.hp -= parseInt(player.hp / 2 + 1000);
|
|
player.socket.emit("game_log", "Received a movement penalty");
|
|
player.socket.emit("game_log", "This might have happened if your network is too slow");
|
|
appengine_log("violation", "red_zone: " + player.name + " afk: " + player.afk + " code: " + player.code);
|
|
transport_player_to(player, player.map);
|
|
defeat_player(player);
|
|
if (player.hp <= 0) {
|
|
rip(player);
|
|
}
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
stop_logic(player);
|
|
// if(!player.moving) player.check_x=player.x,player.check_y=player.y,player.checked_xy=false;
|
|
player.last.move = new Date();
|
|
xy_u_logic(player);
|
|
xy_upush_logic(player);
|
|
if (!player.npc) {
|
|
pmap_move(player);
|
|
}
|
|
} else {
|
|
player.red_zone *= 0.97;
|
|
}
|
|
if (mode.aggro && !player.rip && !is_invinc(player) && !player.npc) {
|
|
//#GTODO: calculate an angle + compare against monster's .angle before attacking
|
|
var l = get_nearby_ghash(aggressives, player, 32);
|
|
l.forEach(function (monster) {
|
|
if (is_in_front(monster, player) && can_attack(monster, player)) {
|
|
if (Math.random() < player.aggro_diff) {
|
|
return;
|
|
}
|
|
if (monster.rage && Math.random() < monster.rage - player.aggro_diff) {
|
|
target_player(monster, player);
|
|
}
|
|
var attack = commence_attack(monster, player, "attack");
|
|
if (attack && attack.events && attack.events.length) {
|
|
if (monster_map[monster.id]) {
|
|
if (!monster_map[monster.id].data.events) {
|
|
monster_map[monster.id].data.events = attack.events;
|
|
}
|
|
monster_map[monster.id].data.events.push(...attack.events);
|
|
} else {
|
|
to_push.push({ id: monster.id, entity: monster, data: monster_to_client(monster, attack.events) });
|
|
monster_map[monster.id] = to_push[to_push.length - 1];
|
|
}
|
|
}
|
|
// disappearing_text(player.socket,monster,"ATTACK!",{color:"#E082B1",xy:1});
|
|
}
|
|
});
|
|
}
|
|
if (player.u) {
|
|
player.u = false;
|
|
to_push.push({ id: id, entity: player, data: player_to_client(player, 1) });
|
|
}
|
|
if (player.violations >= 3) {
|
|
player.socket.disconnect();
|
|
}
|
|
}
|
|
|
|
(G.maps[instance.map].traps || []).forEach(function (trap) {
|
|
if (trap.type == "spikes") {
|
|
for (var id in pmap_get({ x: trap.position[0], y: trap.position[1], in: instance.name })) {
|
|
var player = players[id_to_id[id]];
|
|
if (player && !player.npc) {
|
|
disappearing_text(player.socket, player, "-50", { color: "red", xy: 1 });
|
|
player.hp = max(0, player.hp - 50);
|
|
player_rip_logic(player);
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
} else if (trap.type == "debuff") {
|
|
for (var id in instance.players) {
|
|
var player = instance.players[id];
|
|
if (is_point_inside([player.x, player.y], trap.polygon)) {
|
|
// player.s["debuffaura"]={"ms":200,"name":"Debuff","skin":"citizens","citizens":true,"speed":-40};
|
|
add_condition(player, "slowness", { duration: 200 });
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (var id in instance.players) {
|
|
send_xy_updates(instance.players[id], to_push);
|
|
}
|
|
for (var id in instance.observers) {
|
|
send_xy_updates(instance.observers[id], to_push);
|
|
}
|
|
}
|
|
|
|
function count_unique_users() {
|
|
unique_players = 0;
|
|
var marked = {};
|
|
for (var id in players) {
|
|
var ip = get_ip(players[id]);
|
|
if (!marked[ip]) {
|
|
marked[ip] = 1;
|
|
unique_players++;
|
|
}
|
|
}
|
|
if (is_sdk) {
|
|
unique_players = 9;
|
|
}
|
|
for (name in instances) {
|
|
var instance = instances[name];
|
|
if (!instance.pvp) {
|
|
continue;
|
|
}
|
|
var initial = instance.allow;
|
|
var npc = npcs.pvp;
|
|
if (
|
|
(is_sdk && Object.keys(players).length >= 2) ||
|
|
unique_players >= B.arena_limit ||
|
|
Object.keys(instance.players).length
|
|
) {
|
|
// direction logic at game.js/update_sprite
|
|
if (instance.allow) {
|
|
continue;
|
|
}
|
|
instance.allow = true;
|
|
if (!npc) {
|
|
continue;
|
|
}
|
|
npc.going_x = npc.positions[1][0];
|
|
npc.going_y = npc.positions[1][1];
|
|
npc.allow = true;
|
|
npc.u = true;
|
|
start_moving_element(npc);
|
|
} else {
|
|
if (!instance.allow) {
|
|
continue;
|
|
}
|
|
instance.allow = false;
|
|
if (!npc) {
|
|
continue;
|
|
}
|
|
npc.going_x = npc.positions[0][0];
|
|
npc.going_y = npc.positions[0][1];
|
|
npc.allow = false;
|
|
npc.u = true;
|
|
start_moving_element(npc);
|
|
}
|
|
}
|
|
}
|
|
|
|
var last_iloop = new Date();
|
|
function instance_loop() {
|
|
var ms_since = 32;
|
|
try {
|
|
var now_date = new Date();
|
|
|
|
for (name in instances) {
|
|
var instance = instances[name];
|
|
if (mssince(instance.last_update) > 75) {
|
|
update_instance(instance);
|
|
}
|
|
}
|
|
|
|
ms_since = mssince(now_date);
|
|
if (ms_since > B.instance_loop_log_edge) {
|
|
server_log("Instance loop took " + mssince(now_date) + "ms", 1);
|
|
}
|
|
perfc.instance_loop[ms_since] = (perfc.instance_loop[ms_since] || 0) + 1;
|
|
perfc.instance_loops += 1;
|
|
var mss_since = mssince(last_iloop);
|
|
perfc.instance_delay[mss_since] = (perfc.instance_delay[mss_since] || 0) + 1;
|
|
if (mss_since / 1000 > 15) {
|
|
console.log("Sleep detected: " + mss_since / 1000);
|
|
}
|
|
last_iloop = new Date();
|
|
} catch (e) {
|
|
log_trace("#X Instance loop error", e);
|
|
}
|
|
setTimeout(instance_loop, max(75, min(1000, ms_since * 2 + 2)));
|
|
}
|
|
|
|
function npc_loop() {
|
|
// now mainly just NPC's, back in the day pretty much everything [11/08/22]
|
|
if (!server.live) {
|
|
return setTimeout(npc_loop, 10);
|
|
}
|
|
var ms_since = 32;
|
|
try {
|
|
var now_date = new Date();
|
|
|
|
for (var id in npcs) {
|
|
var npc = npcs[id];
|
|
var delay = -npc.delay * npc.d_multiplier;
|
|
if (npc.rip) {
|
|
continue;
|
|
}
|
|
var def = G.npcs[npc.ntype] || {};
|
|
if (
|
|
def &&
|
|
instances[npc.in] &&
|
|
instances[npc.in].players &&
|
|
def.aura &&
|
|
(!npc.last_aura || mssince(npc.last_aura) > 2000)
|
|
) {
|
|
npc.last_aura = new Date();
|
|
for (var pid in instances[npc.in].players) {
|
|
var player = instances[npc.in].players[pid];
|
|
// console.log(player);
|
|
if (!player.npc && distance(player, npc) < 320) {
|
|
player.s[npc.ntype + "aura"] = { ms: 6000, name: "Citizen's Aura", skin: "citizens", citizens: true };
|
|
for (var p in def.aura) {
|
|
player.s[npc.ntype + "aura"][p] = def.aura[p];
|
|
}
|
|
resend(player, "u+cid");
|
|
}
|
|
}
|
|
}
|
|
if (def.attack && ssince(npc.last.attack) > 1.5) {
|
|
for (var id in instances[npc.in].monsters) {
|
|
var monster = instances[npc.in].monsters[id];
|
|
if (distance(npc, monster) < npc.range) {
|
|
// && monster.hp>5000
|
|
npc.last.attack = new Date();
|
|
commence_attack(npc, monster, "attack");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (def.seek) {
|
|
var target = npc.focus && get_player(npc.focus);
|
|
if (target && def.transport && target.map != npc.map && target.in == target.map) {
|
|
var spot = safe_xy_nearby(target.map, target.x - 8, target.y - 6);
|
|
if (spot) {
|
|
transport_player_to(npc, get_player(npc.focus).map, [spot.x, spot.y]);
|
|
}
|
|
}
|
|
var target = null;
|
|
var old_focus = npc.focus;
|
|
var min_val = 99999;
|
|
var max_val = -99999;
|
|
|
|
for (var pid in instances[npc.in].players) {
|
|
var player = instances[npc.in].players[pid];
|
|
if (def.seek == "low_hp" && player.hp < player.max_hp * 0.275 && player.hp < min_val) {
|
|
min_val = player.hp;
|
|
target = player;
|
|
}
|
|
if (def.seek == "cuteness" && player.cuteness && player.cuteness > max_val) {
|
|
max_val = player.cuteness;
|
|
target = player;
|
|
}
|
|
if (
|
|
def.seek == "thrill" &&
|
|
player.thrilling &&
|
|
player.thrilling > now_date &&
|
|
(!target || player.thrilling > target.thrilling)
|
|
) {
|
|
target = player;
|
|
}
|
|
if (def.seek == "gold" && player.gold > 1000000000 * ((!is_pvp && 10) || 1) && player.gold > max_val) {
|
|
max_val = player.gold;
|
|
target = player;
|
|
}
|
|
if (player.type == "merchant") {
|
|
continue;
|
|
}
|
|
if (
|
|
def.seek == "dragondagger" &&
|
|
player.slots.mainhand &&
|
|
player.slots.mainhand.name == "dragondagger" &&
|
|
player.level < min_val
|
|
) {
|
|
min_val = player.level;
|
|
target = player;
|
|
}
|
|
}
|
|
|
|
npc.focus = target && target.name;
|
|
if (old_focus != npc.focus) {
|
|
npc.u = true;
|
|
npc.cid++;
|
|
}
|
|
}
|
|
|
|
if (!npc.movable) {
|
|
continue;
|
|
}
|
|
var moves = [
|
|
[1, 0],
|
|
[0, 1],
|
|
[-1, 0],
|
|
[0, -1],
|
|
[0.8, 0.8],
|
|
[-0.8, -0.8],
|
|
[0.8, -0.8],
|
|
[-0.8, 0.8],
|
|
];
|
|
var multiplier = npc.steps;
|
|
shuffle(moves);
|
|
if (def.aura && G.maps[npc.map].ref.transporter && distance(npc, G.maps[npc.map].ref.transporter) < 2000) {
|
|
delay = -2400;
|
|
multiplier *= 4;
|
|
} else if (def.aura) {
|
|
delay *= 3;
|
|
}
|
|
if (!npc.citizen || npc.moving || npc.last.move > future_ms(delay) || instances[npc.in].paused) {
|
|
continue;
|
|
}
|
|
if (Math.random() < 0.3) {
|
|
multiplier *= 2;
|
|
} else if (Math.random() < 0.3) {
|
|
multiplier *= 4;
|
|
}
|
|
|
|
if (def.heal) {
|
|
for (var id in instances[npc.in].players) {
|
|
var player = instances[npc.in].players[id];
|
|
if (distance(player, npc) < 320 && player.hp < player.max_hp) {
|
|
commence_attack(npc, player, "partyheal");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (npc.focus) {
|
|
var target = get_player(npc.focus);
|
|
if (!target || distance(target, npc) < 30) {
|
|
continue;
|
|
}
|
|
npc.going_x = (npc.x + target.x) / 2;
|
|
npc.going_y = (npc.y + target.y) / 2;
|
|
if (!can_move(npc)) {
|
|
npc.going_x = npc.x + moves[0][0] * multiplier;
|
|
npc.going_y = npc.y + moves[0][1] * multiplier;
|
|
}
|
|
} else {
|
|
npc.going_x = npc.x + moves[0][0] * multiplier;
|
|
npc.going_y = npc.y + moves[0][1] * multiplier;
|
|
}
|
|
npc.d_multiplier = 1 + 2 * Math.random();
|
|
|
|
if (
|
|
npc.boundary &&
|
|
(npc.going_x < npc.boundary[0] ||
|
|
npc.going_x > npc.boundary[2] ||
|
|
npc.going_y < npc.boundary[1] ||
|
|
npc.going_y > npc.boundary[3])
|
|
) {
|
|
} else if (can_move(npc)) {
|
|
npc.u = true;
|
|
start_moving_element(npc);
|
|
// server_log("Moving to "+npc.going_x+","+npc.going_y);
|
|
} else {
|
|
npc.going_x = npc.x;
|
|
npc.going_y = npc.y;
|
|
npc.last.move = new Date();
|
|
}
|
|
}
|
|
ms_since = mssince(now_date);
|
|
} catch (e) {
|
|
log_trace("#X NPC loop error", e);
|
|
}
|
|
setTimeout(npc_loop, max(28, min(1000, ms_since * 2 + 2))); // originally 24
|
|
}
|
|
|
|
function bless_loop() {
|
|
if (S.blessed_minutes) S.blessed_minutes--;
|
|
if (!S.blessed_minutes) {
|
|
delete E.blessed_minutes;
|
|
delete E.blessed_by;
|
|
return;
|
|
}
|
|
E.blessed_minutes = S.blessed_minutes;
|
|
E.blessed_by = S.blessed_by;
|
|
for (var id in players) {
|
|
var player = players[id],
|
|
rs = false;
|
|
if (!player.s.patronsgrace) rs = true;
|
|
player.s.patronsgrace = { ms: G.conditions.patronsgrace.duration, f: S.blessed_by };
|
|
if (rs) resend(player, "u+cid");
|
|
}
|
|
}
|
|
|
|
setInterval(bless_loop, 60 * 1000);
|
|
|
|
function game_loop() {
|
|
// back in the day pretty much everything was in here [11/08/22]
|
|
if (!server.live) {
|
|
return setTimeout(game_loop, 10);
|
|
}
|
|
var ms_since = 32;
|
|
try {
|
|
var now_date = new Date();
|
|
// for(name in instances)
|
|
// {
|
|
// var instance=instances[name];
|
|
// if(mssince(instance.last_update)>75) update_instance(instance);
|
|
// }
|
|
for (var id in server.s) {
|
|
server.s[id].ms -= 10;
|
|
if (server.s[id].ms < 0) {
|
|
delete server.s[id];
|
|
}
|
|
}
|
|
|
|
ms_since = mssince(now_date);
|
|
if (ms_since > B.game_loop_log_edge) {
|
|
server_log("Main loop took " + mssince(now_date) + "ms", 1);
|
|
}
|
|
perfc.game_loop[ms_since] = (perfc.game_loop[ms_since] || 0) + 1;
|
|
perfc.game_loops += 1;
|
|
} catch (e) {
|
|
log_trace("#X Main loop error", e);
|
|
}
|
|
setTimeout(game_loop, max(28, min(1000, ms_since * 2 + 2))); // originally 24
|
|
}
|
|
|
|
var lrid = 0;
|
|
function aura_loop() {
|
|
try {
|
|
lrid++;
|
|
for (var id in players) {
|
|
var player = players[id];
|
|
if (!player || !player.aura || player.rid % 5 != lrid % 5) {
|
|
continue;
|
|
}
|
|
for (var aid in player.aura) {
|
|
for (var pid in instances[player.in].players) {
|
|
if (!is_same(instances[player.in].players[pid], player, 3)) {
|
|
continue;
|
|
}
|
|
if (distance(instances[player.in].players[pid], player) > 200) {
|
|
continue;
|
|
}
|
|
instances[player.in].players[pid].s[aid] = { ms: G.conditions[aid].duration || 30000, f: player.name };
|
|
if (G.conditions[aid].attr0) {
|
|
instances[player.in].players[pid].s[aid][G.conditions[aid].attr0] = player.aura[aid].attr0;
|
|
}
|
|
resend(instances[player.in].players[pid], "u+cid");
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X aura loop error", e);
|
|
}
|
|
setTimeout(aura_loop, 1000);
|
|
}
|
|
|
|
setTimeout(game_loop, 10);
|
|
setTimeout(npc_loop, 10);
|
|
setTimeout(instance_loop, 10);
|
|
setTimeout(aura_loop, 10);
|
|
setInterval(count_unique_users, 6000);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in observers) {
|
|
var observer = observers[id];
|
|
if (observer.player && get_player(observer.player.name)) {
|
|
var player = get_player(observer.player.name);
|
|
if (simple_distance(observer, player) > 200) {
|
|
transport_observer_to(
|
|
observer,
|
|
player.in,
|
|
player.map,
|
|
player.x,
|
|
player.y + ((observer.socket.desktop && 120) || 0),
|
|
);
|
|
}
|
|
} else {
|
|
if (simple_distance(observer, { map: observer_map, in: observer_map, x: observer_x, y: observer_y }) > 200) {
|
|
transport_observer_to(
|
|
observer,
|
|
observer_map,
|
|
observer_map,
|
|
observer_x,
|
|
observer_y + ((observer.socket.desktop && 120) || 0),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X observer loop2 error", e);
|
|
}
|
|
}, 13200);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
if (is_pvp) {
|
|
return;
|
|
}
|
|
var names = [];
|
|
var player;
|
|
total_merchants = 0;
|
|
for (var id in players) {
|
|
player = players[id];
|
|
if (
|
|
!player.npc &&
|
|
player.last.attack &&
|
|
player.map == player.in &&
|
|
ssince(player.last.attack) < 3 &&
|
|
!G.maps[player.map].pvp
|
|
) {
|
|
names.push(player.name);
|
|
}
|
|
if (player.p && player.p.stand) {
|
|
total_merchants += 1;
|
|
}
|
|
}
|
|
if (names.length) {
|
|
player = get_player(names[floor(Math.random() * names.length)]);
|
|
observer_map = player.map;
|
|
observer_x = parseInt(player.x);
|
|
observer_y = parseInt(player.y);
|
|
server_log("Set the observer location: " + [observer_map, observer_x, observer_y]);
|
|
} else {
|
|
observer_map = merchant_map;
|
|
observer_x = merchant_x;
|
|
observer_y = merchant_y;
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X observer loop error", e);
|
|
}
|
|
}, 4000);
|
|
|
|
setTimeout(
|
|
function () {
|
|
setInterval(
|
|
function () {
|
|
for (var id in players) {
|
|
var player = players[id];
|
|
if (player.type == "merchant" && player.p.stand) {
|
|
var xp = 1000;
|
|
if (player.level <= 40) {
|
|
xp = G.levels[player.level] / 100;
|
|
} else if (player.level <= 60) {
|
|
xp = G.levels[player.level] / 200;
|
|
} else if (player.level <= 70) {
|
|
xp = G.levels[player.level] / 400;
|
|
} else {
|
|
xp = G.levels[70] / 400;
|
|
}
|
|
xp = parseInt(Math.round(xp));
|
|
player.xp += xp;
|
|
player.socket.emit("game_log", "Gained " + to_pretty_num(xp) + " marketing XP");
|
|
player.socket.emit("disappearing_text", {
|
|
message: "+" + xp,
|
|
x: player.x,
|
|
y: player.y - 32,
|
|
args: { color: "gray", size: "large" },
|
|
});
|
|
resend(player, "reopen+u+cid");
|
|
}
|
|
}
|
|
},
|
|
3 * 60 * 60 * 1000,
|
|
); //3
|
|
},
|
|
1 * 60 * 60 * 1000,
|
|
); //1
|
|
|
|
setInterval(function () {
|
|
try {
|
|
var edge = future_s(-120);
|
|
for (var id in players) {
|
|
if (0 && players[id].last_ipass < edge) {
|
|
players[id].ban = "ipass";
|
|
players[id].socket.emit("disconnect_reason", "Failed to check in. Your network might be too slow.");
|
|
players[id].socket.disconnect();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X ipass loop error", e);
|
|
}
|
|
}, 132000);
|
|
|
|
setTimeout(function () {
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in instances) {
|
|
var instance = instances[id];
|
|
if (
|
|
B.pause_instances &&
|
|
!instance.paused &&
|
|
!instance.operators &&
|
|
Object.keys(instance.players).length <= instance.npcs &&
|
|
!Object.keys(instance.observers).length &&
|
|
ssince(instance.last_player) > 50
|
|
) {
|
|
pause_instance(instance);
|
|
}
|
|
if (Object.keys(instance.players).length > instance.npcs || Object.keys(instance.observers).length) {
|
|
instance.last_player = new Date();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X pause loop error", e);
|
|
}
|
|
}, 2000);
|
|
}, 60000); // delay for a minute, so citizens move around
|
|
|
|
setInterval(function () {
|
|
try {
|
|
for (var oname in parties) {
|
|
send_party_update(oname);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X party loop error", e);
|
|
}
|
|
}, 60000);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in players) {
|
|
var player = players[id];
|
|
players[id].pdps *= 0.8;
|
|
player.p.minutes++;
|
|
trade_slots.forEach(function (slot) {
|
|
if (player.slots[slot] && player.slots[slot].giveaway) {
|
|
player.slots[slot].giveaway--;
|
|
if (!player.slots[slot].giveaway) {
|
|
var list = [];
|
|
player.slots[slot].list.forEach(function (name) {
|
|
var player = get_player(name);
|
|
if (player && player.esize) {
|
|
list.push(player);
|
|
} else if (!mode.prevent_external) {
|
|
list.push({ name });
|
|
}
|
|
});
|
|
if (!list.length) {
|
|
player.slots[slot].giveaway = 5;
|
|
} else {
|
|
if (!mode.prevent_external) {
|
|
var winner = random_one(list);
|
|
var item = player.slots[slot];
|
|
player.slots[slot] = null;
|
|
delete item.list;
|
|
delete item.giveaway;
|
|
delete item.registry;
|
|
item.gf = player.name;
|
|
item.src = "gva";
|
|
var mitem = JSON.stringify(item);
|
|
add_to_trade_history(player, "giveaway", winner.name, cache_item(item, true));
|
|
xy_emit(player, "game_log", {
|
|
message: winner.name + " won " + player.name + "'s giveaway of " + G.items[item.name].name + "!",
|
|
color: "#42A0DC",
|
|
confetti: winner.name,
|
|
});
|
|
appengine_call(
|
|
"send_mail",
|
|
{
|
|
fro: player.name,
|
|
to: winner.name,
|
|
subject: "You've won a giveaway!",
|
|
message:
|
|
"Congratulations, you won " +
|
|
player.name +
|
|
"'s giveaway. Participants were: " +
|
|
list.map((e) => e.name).join(", "),
|
|
rid: randomStr(50),
|
|
retries: 5,
|
|
item: mitem,
|
|
},
|
|
function (result) {},
|
|
function () {
|
|
console.log("#M unsent giveaway, lost item: " + mitem);
|
|
},
|
|
);
|
|
} else {
|
|
var winner = random_one(list);
|
|
var item = player.slots[slot];
|
|
player.slots[slot] = null;
|
|
delete item.list;
|
|
delete item.giveaway;
|
|
delete item.registry;
|
|
item.gf = player.name;
|
|
item.src = "gva";
|
|
add_item(winner, item);
|
|
add_to_trade_history(player, "giveaway", winner.name, cache_item(item, true));
|
|
xy_emit(player, "game_log", {
|
|
message: winner.name + " won " + player.name + "'s giveaway of " + G.items[item.name].name + "!",
|
|
color: "#42A0DC",
|
|
confetti: winner.name,
|
|
});
|
|
resend(winner, "reopen");
|
|
}
|
|
}
|
|
}
|
|
player.cslots[slot] = cache_item(player.slots[slot], true);
|
|
resend(player, "u+cid" + ((!player.slots[slot] && "+reopen") || ""));
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X decay loop error", e);
|
|
}
|
|
}, 60000);
|
|
|
|
var a_score = {}; // announcement score
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in a_score) {
|
|
a_score[id] *= 0.99;
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X ascore loop error", e);
|
|
}
|
|
}, 10000);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in players) {
|
|
var player = players[id];
|
|
player.xrange = min(25, player.xrange + 5);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X xrange loop error", e);
|
|
}
|
|
}, 1000);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
var c = new Date();
|
|
for (var id in players) {
|
|
var player = players[id];
|
|
if (player.auth_id && player.p.first === undefined && !player.first_u_call) {
|
|
function first_call(player) {
|
|
player.first_u_call = appengine_call(
|
|
"is_first",
|
|
{ auth: player.auth, auth_id: player.auth_id, character: player.real_id, suffix: "/" + player.id },
|
|
function (result) {
|
|
if (result.first) {
|
|
player.p.first = true;
|
|
} else {
|
|
player.p.first = false;
|
|
}
|
|
},
|
|
function () {
|
|
delete player.first_u_call;
|
|
},
|
|
);
|
|
}
|
|
first_call(player);
|
|
}
|
|
if (player.p.first && !player.p.dt.first) {
|
|
realm_broadcast("server_message", { message: player.name + " joined Adventure Land!", color: "#24A2FA" });
|
|
player.p.dt.first = future_h(24 * 7);
|
|
} else if (player.p.dt.first && player.p.dt.first > c) {
|
|
player.paura = player.paura || {};
|
|
player.paura["newcomersblessing"] = { attr0: 0, attr1: 0 };
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X first_character loop error", e);
|
|
}
|
|
}, 4000);
|
|
|
|
function projectiles_loop() {
|
|
var now = new Date();
|
|
for (var id in projectiles) {
|
|
try {
|
|
if (projectiles[id].eta <= now) {
|
|
var projectile = projectiles[id];
|
|
delete projectiles[id];
|
|
complete_attack(projectile.attacker, projectile.target, projectile);
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X projectile loop error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
setInterval(projectiles_loop, 7);
|
|
|
|
var accel = 1;
|
|
if (mode.fast_mlevels) {
|
|
accel = 100;
|
|
}
|
|
setInterval(function () {
|
|
try {
|
|
for (var id in instances) {
|
|
for (var mid in instances[id].monsters || []) {
|
|
var monster = instances[id].monsters[mid];
|
|
if (
|
|
G.monsters[monster.type].cute ||
|
|
G.monsters[monster.type].peaceful ||
|
|
G.monsters[monster.type].stationary ||
|
|
monster.target
|
|
) {
|
|
continue;
|
|
}
|
|
var exp = Math.pow(2, (monster.level - 1) * 0.3);
|
|
var mult = 1;
|
|
if (monster["1hp"]) {
|
|
mult = 200;
|
|
} else if (G.monsters[monster.type].special) {
|
|
mult = 20;
|
|
}
|
|
if (
|
|
mssince(monster.last_level) > max((180000 * exp * mult) / accel, (monster.max_hp * mult * 30 * exp) / accel)
|
|
) {
|
|
if (monster.temp) {
|
|
remove_monster(monster);
|
|
} else {
|
|
level_monster(monster);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log_trace("#X mlevel loop error", e);
|
|
}
|
|
}, 16000 / accel);
|
|
|
|
setInterval(function () {
|
|
try {
|
|
for (var 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); });
|