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