"use strict";
/*
OpenAgar - Open source web game
Copyright (C) 2016 Andrew S
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const RSON = require('rson')
const DataService = require('./dataService.js');
const Entities = require('../entities/');
const FoodService = require('./foodService.js');
const Bot = require('../ai/fakePlayer.js');
const CollisionHandler = require('./collisionHandler.js')
const LZString = require('../modules/LZString.js')
const Minion = require('../ai/Minion.js')
const Commands = require('../commands').list
const ChatCommands = require('../commands').chat
const PluginService = require('./pluginService.js')
const ChildService = require('./childService.js')
const GMService = require('./gameMode.js')
/** Class that represents a game server */
class Main {
/**
* Creates a game server
* @param {Boolean} isMain - Tells if the server is the main server
* @param {Number} id - Id of server
* @param {String} name - Name of server
* @param {String} scname - Screen name of server
* @param {GlobalData} globalData - Global data object
* @param {Object} config - Object of server's config
* @param {Function} log - Log function
* @param {Child} child - The assigned child process
* @param {Function} debug - The debug function
*/
constructor(isMain, id, name, scname, globalData, config, log, child, debug) {
this.isMain = isMain;
this.id = id;
this.name = name;
this.childid = child.id;
this.scname = scname;
this.debug = debug
this.log = log;
this.viruses = 0;
this.minfood = 500;
this.clientLen = 0;
this.updLb = true;
this.toBeDeleted = [];
this.selected = false;
this.entityTypes = [];
this.food = 0;
this.updateCode = 0;
this.chatId = 1;
this.destroyed = false
this.chat = [];
this.tbd = [];
this.wormHoles = 0;
this.paused = false;
this.collist = [];
this.bots = new Map();
this.timeouts = [];
this.intervals = [];
this.feedListeners = [];
this.deleteR = "";
this.lag = 0
this.chatNames = [];
this.interface = true;
this.botid = 0;
this.lbConfig = {
lbtype: 0
}
this.haveTeams = false;
this.colors = [
{
'r': 235,
'g': 75,
'b': 0
},
{
'r': 225,
'g': 125,
'b': 255
},
{
'r': 180,
'g': 7,
'b': 20
},
{
'r': 80,
'g': 170,
'b': 240
},
{
'r': 180,
'g': 90,
'b': 135
},
{
'r': 195,
'g': 240,
'b': 0
},
{
'r': 150,
'g': 18,
'b': 255
},
{
'r': 80,
'g': 245,
'b': 0
},
{
'r': 165,
'g': 25,
'b': 0
},
{
'r': 80,
'g': 145,
'b': 0
},
{
'r': 80,
'g': 170,
'b': 240
},
{
'r': 55,
'g': 92,
'b': 255
},
];
this.clients = new Map();
this.bounds = {
x: config.boundX,
y: config.boundY,
width: config.boundWidth,
height: config.boundHeight
};
this.minions = new Map()
this.dataService = new DataService(this, globalData, config);
this.timer = {
tick: 0,
time: 0,
update: 0,
slow: 0,
updatePN: false,
rslow: 0,
bot: false,
pn: false,
passed: 0,
init: Date.now(),
status: 60
};
this.loop = this.mloop.bind(this);
this.foodService = new FoodService(this);
this.collisionHandler = new CollisionHandler(this)
this.pluginService = new PluginService(this)
this.childService = new ChildService(this, child)
this.gameMode = new GMService(this)
this.childService.init()
this.addBots(config.serverBots)
}
/**
* Sets an interval so it can be removed when the server shuts down
* @param {Function} func - The function to call at a interval
* @param {Number} time - The interval time
* @return {Interval} The interval created from this function
*/
setInterval(func, time) {
var interval = setInterval(func, time);
this.intervals.push(interval);
return interval;
}
/**
* Sets an timeout so it can be removed when the server shuts down
* @param {Function} func - The function to call at after the timeout
* @param {Number} time - The time to wait for the function to be called
* @return {Timeout} The timeout created from this function
*/
setTimeout(func, time) {
var timeout = setTimeout(func, time)
this.timeouts.push(timeout)
return timeout;
}
/**
* Clears all intervals
* @return {Number} The count of intervals removed
*/
clearIntervals() {
var count = 0;
this.intervals.forEach((i) => {
try {
clearInterval(i)
count++;
} catch (e) {
}
})
this.intervals = [];
return count
}
/**
* Clears all timeouts
* @return {Number} The count of timeouts removed
*/
clearTimeouts() {
var count = 0;
this.timeouts.forEach((i) => {
try {
clearTimeout(i)
count++;
} catch (e) {
}
})
this.timeouts = [];
return count
}
/**
* Changes the gamemode
* @param {Number} mode - Gamemode id to switch to
* @deprecated Is not functional
*/
changeMode(mode) {
this.gameMode.event('onChange')
}
/**
* Called when the server is removed
*/
onRemove() {
this.stop()
this.destroyed = true;
this.childService.stop()
this.pluginService.stop()
this.getWorld().getNodes('map').forEach((node) => {
this.removeNode(node)
})
this.minions.forEach((min) => {
this.removeMinion(min)
})
this.bots.forEach((bot) => {
this.removeBot(bot)
})
this.clients = [];
this.bots = [];
this.minions = [];
this.debug("gre{[Debug]} Cleared ".styleMe() + this.clearIntervals() + " intervals")
this.debug("gre{[Debug]} Cleared ".styleMe() + this.clearTimeouts() + " timeouts")
this.debug("gre{[Debug]} Removed server ".styleMe() + this.id)
this.debug = null;
this.log = null;
}
/**
* Adds minions for a player
* @param {Player} player - Player to give minions to
* @param {Number} num - Amount of minions to give
*/
addMinions(player, num) {
for (var i = 0; i < num; i++) {
this.addMinion(player)
}
}
/**
* Formats a node to be sent
* @param {Node} node - Node to be sent
* @param {Player} player - Player that it is being sent to
* @returns {Object} Formated node
*/
formatNode(node, player) {
var a = {
id: node.id,
owId: (node.owner) ? node.owner.id : 0,
size: node.size,
mass: node.mass,
type: node.type,
posX: node.position.x,
posY: node.position.y,
color: node.color
};
node.name && (a.name = node.name)
node.agit && (a.agit = 1)
node.skin && (a.skin = player.skinHandler.getSend(node.skin))
node.spiked && (a.spiked = 1);
return a;
}
/**
* Adds a single minion for a player
* @param {Player} player - Player to give the minion to
*/
addMinion(player) {
var id = this.getGlobal().getNextId()
var botid = this.botid++;
var bot = new Minion(this, id, "Bot: " + botid, botid, player)
this.gameMode.event('onAllInit', {
player: bot
})
this.minions.set(bot.id, bot)
player.addMinion(bot)
}
/**
* Adds server bots
* @param {Number} num - Amount of bots to add
*/
addBots(num) {
for (var i = 0; i < num; i++) {
this.addBot()
}
}
/**
* Removes a chat entry
* @param {Number} id - Id of chat entry to remove
* @returns {Boolean} Returns whether it was sucessful or not
*/
removeChat(id) {
if (this.chat.every((ch, i) => {
if (ch.id != id) return true;
this.chat.splice(i, 1)
return false
})) return false;
this.clients.forEach((client) => {
client.socket.emit('chat', {
remove: id
})
})
return true;
}
/**
* Adds a chat entry
* @param {Player} player - Player who chatted
* @param {String} msg - Msg to chat
*/
addChat(player, msg) {
if (msg.charAt(0) == "/") {
if (!this.parseChatCommand(player, msg)) player.msg("That command was not found")
return
}
if (!this.pluginService.send('beforeChat', {
player: player,
main: this,
msg: msg
})) return
var name = player.gameData.chatName
if (!name) return player.msg("Your chatname is not set! Please join the game")
if (player.gameData.chatBan) return player.msg("You are banned from the chat!")
var data = {
id: this.chatId++,
name: player.gameData.chatName,
color: player.gameData.chatColor,
msg: msg
}
this.chat.push(data)
if (this.chat.length >= 15) this.chat.splice(0, 1)
this.clients.forEach((client) => {
if (client.recievePublicChat && client.mutePlayers.indexOf(player.id) == -1) client.socket.emit('chat', data)
})
}
/**
* Broadcays a chat message
* @param {String} msg - Message to send
* @param {String} name - Name to send as
* @param {Object} color - Color to send as
*/
broadcast(msg, name, color) {
this.clients.forEach((client) => {
client.msg(msg, name, color)
})
}
/**
* Parses a chat command
* @param {Player} player - Player that is issueing the command
* @param {String} msg - String of command
* @returns {Boolean} Returns whether the command was executed or not
*/
parseChatCommand(player, msg) {
msg = msg.substr(1)
if (!msg) return false;
var cmd = msg.split(" ")[0].toLowerCase()
if (ChatCommands[cmd]) {
ChatCommands[cmd](msg, this, player, function (a) {
player.msg(a)
})
return true;
} else if (this.pluginService.chatC[cmd]) {
this.pluginService.chatC[cmd](msg, this, player, function (a) {
player.msg(a)
})
return true;
}
return false
}
/**
* Gets global data
* @returns {GlobalData} Returns the globaldata object
*/
getGlobal() {
return this.dataService.globalData
}
/**
* Adds a bot
*/
addBot() {
var id = this.getGlobal().getNextId()
var botid = this.botid++;
var bot = new Bot(this, id, "Bot: " + botid, botid)
this.gameMode.event('onAllInit', {
player: bot
})
this.bots.set(bot.id, bot)
this.childService.addBot(bot)
}
/**
* Removes a minion
* @param {Minion} bot - Minion to remove
*/
removeMinion(bot) {
bot.onRemove()
bot.cells.forEach((c) => {
this.removeNode(c)
})
bot.owning.forEach((c) => {
this.removeNode(c)
})
this.minions.delete(bot.id)
this.childService.removeClient(bot)
}
/**
* Removes a bot
* @param {Bot} bot - Bot to remove
*/
removeBot(bot) {
bot.onRemove()
bot.cells.forEach((c) => {
this.removeNode(c)
})
bot.owning.forEach((c) => {
this.removeNode(c)
})
this.bots.delete(bot.id)
this.childService.removeClient(bot)
}
/**
* Removes bots from a list of ids
* @param {Array} ids - array of botids
*/
removeBots(ids) {
ids.forEach((id) => {
var b = this.bots.get(id)
if (b) this.removeBot(b)
})
}
/**
* Adds a client to the server
* @param {Player} client - Player to add to the server
*/
addClient(client) {
if (!this.clients.get(client.id)) {
this.pluginService.send('onClientAdd', {
player: client,
main: this
})
this.gameMode.event('onPlayerInit', {
player: client
})
this.gameMode.event('onAllInit', {
player: client
})
this.clients.set(client.id, client);
this.sendClientPacket(client)
this.sendPrevChat(client)
this.debug("gre{[Debug]} Client (ID: ".styleMe() + client.id + ") was added to server " + this.id)
}
}
/**
* Sends the chat log to the client
* @param {Player} player - player to send chat log
*/
sendPrevChat(player) {
this.chat.forEach((chat) => {
player.socket.emit('chat', chat)
})
}
/**
* Sends a player's client instructions
* @param {Player} player - Player to send data to
*/
sendClientPacket(player) {
var config = this.getConfig()
var a = {
// Macros (1 = on)
sMacro: config.clientSMacro,
wMacro: config.clientWMacro,
qMacro: config.clientQMacro,
eMacro: config.clientEMacro,
rMacro: config.clientRMacro,
darkBG: config.clientDarkBG,
chat: config.clientChat,
skins: config.clientSkins,
grid: config.clientGrid,
acid: config.clientAcid,
colors: config.clientColors,
names: config.clientNames,
showMass: config.clientShowMass,
smooth: config.clientSmooth,
minionCount: 0,
minimap: 0,
maxName: config.clientMaxName,
title: config.clientTitle,
defaultusername: config.clientDefaultUsername,
nickplaceholder: config.clientNickPlaceholder,
instructions: config.clientInstructions,
leavemessage: config.clientLeaveMessage,
customHTML: "",
}
player.socket.emit('cpacket', a)
}
/**
* Removes a player from the server
* @param {Player} client - Player to remove
*/
removeClient(client) {
// setTimeout(function() {
client.cells.forEach((cell) => {
this.removeNode(cell);
})
client.owning.forEach((cell) => {
this.removeNode(cell);
})
client.cells = [];
// }.bind(this),this.getConfig().disconnectTime * 1000)
var names = client.gameData.reservedNamesMap;
for (var i in names) {
var name = names[i];
for (var j in name) {
this.chatNames[i].splice(j, 1);
}
}
client.minions.forEach((minion) => {
this.removeMinion(minion)
})
this.clients.delete(client.id);
this.childService.removeClient(client)
this.debug("gre{[Debug]} Client (ID: ".styleMe() + client.id + ") was removed from server " + this.id)
}
/**
* Removes all nodes in the game
*/
reset() {
this.getWorld().getNodes('map').forEach((node) => {
this.removeNode(node);
});
this.getWorld().clear()
}
/**
* Removes a node from the game
* @param {Node} cell - Node to remove
*/
removeNode(cell) {
cell.onDeletion(this);
cell.dead = true;
this.toBeDeleted.push({
id: cell.id,
killer: (cell.killer) ?
cell.killer.id : false
});
this.tbd.push({
id: cell.id,
killer: (cell.killer) ?
cell.killer.id : false
})
this.dataService.world.removeNode(cell);
}
/**
* Gets a player's chatname
* @param {Player} player - Player to get chatname
* @returns {(String|Boolean)} - Returns the chatname
*/
getChatName(player) {
var name = player.gameData.name || "An Unamed Cell"
var reservedNamesMap = player.gameData.reservedNamesMap
var reserved = player.gameData.reservedChatNames
name = name.split(' ').join('_');
var chatname = name;
if (reserved.indexOf(chatname) != -1) {
return name
}
if (!this.chatNames[name]) this.chatNames[name] = [];
var cn = this.chatNames[name];
for (var i = 0; 0 == 0; i++) {
var newname = (i == 0) ? chatname : chatname + "_" + i;
if (cn.indexOf(i) == -1 || reserved.indexOf(newname) != -1) {
this.chatNames[name][i] = i;
if (reserved.indexOf(newname) == -1) reserved.push(newname);
if (!reservedNamesMap[name]) reservedNamesMap[name] = [];
reservedNamesMap[name][i] = i;
return newname;
}
}
return false;
}
/**
* Checks if there is enough food cells
*/
checkFood() {
return this.foodService.checkFood();
}
/**
* Adds food cells
* @param {Number} n - Amount of food cells to add
*/
addFood(n) {
return this.foodService.addFood(n);
}
/**
* Pauses/unpauses the server
* @param {Boolean} [v] - State to set
*/
pause(v) {
if (v === undefined) this.paused = !this.paused;
else this.paused = v;
if (this.paused) this.stop();
else this.start()
this.childService.pause(this.paused)
if (this.paused) {
this.debug("gre{[Debug]} Paused server ".styleMe() + this.id)
} else {
this.debug("gre{[Debug]} Unpaused server ".styleMe() + this.id)
}
}
/**
* Spawns a player into the game
* @param {Player} player - Player to spawn
*/
spawn(player) {
if (player.cells.size > 0) return
if (!this.pluginService.send('beforeSpawn', {
player: player,
main: this
}));
if (!this.gameMode.event('onPlayerSpawn', {
player: player
}));
if (!player.isBot) player.gameData.chatName = this.getChatName(player)
var pos = this.foodService.getRandomPos();
this.addNode(pos, this.getConfig().startMass, 0, player);
}
/**
* Checks whether a player can eject mass or not
* @param {Player} client - Player to check
* @returns {Boolean} Whether player can eject or not
*/
canEject(client) {
if (!client.lastEject || this.getConfig().ejectMassCooldown == 0 || this.timer.time - client.lastEject >= this.getConfig().ejectMassCooldown) {
client.lastEject = this.timer.time;
return true;
}
return false;
}
/**
* Checks a playercell if it has requirements to eject
* @param {Node} cell - Playercell to check
* @returns {Boolean} Whether the cell can eject or not
*/
ejectCheck(cell) {
if (cell.mass < this.getConfig().ejectMassMin) return false;
cell.addMass(-this.getConfig().ejectedMass);
return true;
}
/**
* Ejects mass from a player
* @param {Player} player - Player to eject mass
*/
ejectMass(player) {
if (this.paused) return;
if (!this.canEject(player)) return;
var cells = player.cells.toArray();
var len = cells.length
for (var i = 0; i < len; i++) {
var cell = cells[i],
deltaX = player.mouse.x - cell.position.x,
deltaY = player.mouse.y - cell.position.y;
if (!this.ejectCheck(cell)) continue;
var angle = Math.atan2(deltaY, deltaX)
angle += (Math.random() * 0.1) - 0.05;
var size = cell.size + 0.2;
var startPos = {
x: cell.position.x + ((size + this.getConfig().ejectedMass) * Math.cos(angle)),
y: cell.position.y + ((size + this.getConfig().ejectedMass) * Math.sin(angle))
};
var ejected = this.addNode(startPos, this.getConfig().ejectedMass, 3, player, [], "m")
ejected.setEngine1(angle, this.getConfig().ejectedSpeed, this.getConfig().ejectedDecay)
// ejected.setCurve(10)
}
}
/**
* Shoots a bullet from a player
* @param {Player} player - Player to shoot bullet
*/
shootBullet(player) {
if (this.paused) return;
if (player.bulletsleft <= 0) return player.bulletsleft = 0;
player.bulletsleft--;
if (player.bulletsleft <= 0) {
player.golden = false;
this.setTimeout(function () {
if (player.bulletsleft > 0 || player.mass > this.getConfig().bulletReloadMin) return;
player.bulletsleft = 3;
var r = Math.floor(Math.random() * 40)
if (1 == 1) player.golden = true;
}.bind(this), this.getConfig().bulletReload * 1000)
}
var cell = player.getBiggest()
if (!cell) return;
var deltaX = player.mouse.x - cell.position.x,
deltaY = player.mouse.y - cell.position.y;
var angle = Math.atan2(deltaY, deltaX)
angle += (Math.random() * 0.1) - 0.05;
var size = cell.size + 0.2;
var startPos = {
x: cell.position.x + ((size + 12) * Math.cos(angle)),
y: cell.position.y + ((size + 12) * Math.sin(angle))
};
var ejected = this.addNode(startPos, 12, 5, player, [], "m")
ejected.setEngine1(angle, this.getConfig().bulletSpeed, this.getConfig().bulletDecay)
// ejected.setCurve(10)
}
/**
* Splits a player cell
* @param {Node} cell - Playercell to split
* @param {Number} angle - Angle to split
* @param {Number} speed - Speed to split
* @param {Number} decay - Time for split movement decay
* @returns {Node} Split item
*/
splitPlayerCell(cell, angle, speed, decay) {
var splitted = this.splitCell(cell, angle, speed, decay, ~~(cell.mass / 2))
cell.updateMass(~~(cell.mass / 2))
splitted.setMerge(this, this.getConfig().playerMerge, this.getConfig().playerMergeMult)
cell.setMerge(this, this.getConfig().playerMerge, this.getConfig().playerMergeMult)
this.getWorld().setFlags(cell, "merge")
this.getWorld().setFlags(splitted, "merge")
return splitted
}
splitPlayer(player) {
if (this.paused) return;
var cells = player.cells.toArray();
var len = cells.length
var maxSplit = this.getConfig().playerMaxCells - len;
for (var i = 0; i < len; i++) {
if (i >= maxSplit) break;
var cell = cells[i],
deltaX = player.mouse.x - cell.position.x,
deltaY = player.mouse.y - cell.position.y;
/*
Sine = opp/hypt
Cos = adj/hypt
Tan = opp/adj
angle = Tan-1(y/x)
*/
if (!cell) continue;
if (cell.mass < this.getConfig().splitMin) continue;
var angle = Math.atan2(deltaY, deltaX)
var splitted = this.splitPlayerCell(cell, angle, cell.getSpeed() * this.getConfig().splitSpeed, this.getConfig().splitDecay)
}
}
loopPlayers(call) {
this.clients.forEach((player) => {
call(player)
})
this.bots.forEach((player) => {
call(player)
})
this.minions.forEach((player) => {
call(player)
})
}
updateLB() {
if (!this.gameMode.event('updateLB', {
lb: this.childService.lb
})) return
if (this.childService.lb.length <= 0) return;
var tosend = [];
this.childService.lb.forEach((lb) => {
var a = this.getPlayer(lb.i)
if (!a) return;
a.rank = lb.r
tosend.push({
name: a.gameData.name || "An Unamed Cell",
id: lb.i
})
})
this.clients.forEach((client) => {
client.socket.emit('lb', {
lb: tosend
})
})
}
execCommand(str) {
try {
var cmd = str.split(" ")[0].toLowerCase()
var command = Commands[cmd]
if (command) {
command(str, this, this.log)
return true;
}
var command = this.pluginService.commands[cmd]
if (command) {
command(str, this, this.log, __dirname)
return true;
}
return false;
} catch (e) {
this.log("ERROR: " + e)
}
}
getPlayer(id) {
var final = this.clients.get(id);
if (final) return final
var final = this.bots.get(id);
if (final) return final
var final = this.minions.get(id);
return final
}
splitCell(cell, angle, speed, decay, mass) {
var pos = {
x: cell.position.x,
y: cell.position.y
}
var a = (cell.type == 0) ? "" : "m"
var node = this.addNode(pos, mass, cell.type, cell.owner, [], a)
node.setEngine1(angle, speed, decay)
return node
}
removeFlags(node, flag) {
this.getWorld().removeFlags(node, flag)
}
updateClients() {
// if (this.toBeDeleted.length == 1) this.toBeDeleted.push({id:0,killer:0})
this.deleteR = JSON.stringify(this.toBeDeleted)
this.clients.forEach((client) => {
client.update(this);
});
this.toBeDeleted = [];
this.deleteR = ";"
}
updateBots() {
return;
this.bots.forEach((bot) => {
if (bot)
bot.update()
})
}
getRandomColor() {
var colorRGB = [0xFF, 0x07, (Math.random() * 256) >> 0];
colorRGB.sort(function () {
return 0.5 - Math.random();
});
return {
r: colorRGB[0],
b: colorRGB[1],
g: colorRGB[2]
};
}
statusReport() {
var time = Date.now()
var upt = time - this.timer.init
this.status = {
id: this.id,
name: this.name,
scname: this.scname,
players: this.clients.size,
bots: this.bots.size,
minions: this.minions.size,
nodes: this.getWorld().getNodes('map').size,
uptime: upt,
totalMass: this.childService.totalMass
}
this.debug("gre{[Debug]} STATUS REPORT FOR SERVER ".styleMe() + this.id + ":")
this.debug(JSON.stringify(this.status))
}
mloop() {
this.timeout = setTimeout(function () {
this.loop()
}.bind(this), 5);
let local = Date.now();
this.timer.tick += (local - this.timer.time);
this.timer.passed = local - this.timer.time
this.timer.updatePN += local - this.timer.time;
this.timer.time = local;
// if (this.timer.passed <= 0) return
// 0.05 seconds
if (this.timer.updatePN >= 50) {
this.gameMode.event('onTick')
this.updatePlayerNodes();
this.updateMovingCells();
this.updateBots()
this.timer.updatePN = 0;
this.checkLag()
}
// 0.02 seconds
if (this.timer.tick >= 20) {
// update views for every client at 50 frames per second
this.updateClients();
this.timer.tick = 0;
// 0.1 seconds
if (this.timer.rslow >= 5) {
this.timer.rslow = 0;
this.playerCollision();
this.childService.sendNodes()
this.childService.update()
this.childService.deleteNodes(this.tbd)
this.tbd = [];
// 1 second
if (this.timer.slow >= 10) {
this.timer.slow = 0;
this.checkFood();
this.foodService.checkVirus()
this.foodService.checkWormHole()
this.foodService.loop();
this.updateMerge();
this.updateLB()
if (this.timer.status >= 60) {
this.timer.status = 0;
this.statusReport()
} else this.timer.status++;
} else {
this.timer.slow++;
}
this.timer.rslow = 0;
} else {
this.timer.rslow++;
}
}
}
updateMerge() {
var nodes = this.getWorld().getNodes('merge')
if (nodes.size == 0) return;
nodes.forEach((node) => {
node.calcMerge(this)
})
}
getConfig() {
return this.dataService.config;
}
checkMerge() { // checks if cells can merge
this.getWorld().getNodes('player').forEach((node) => {
});
}
getWorld() {
return this.dataService.world;
}
checkLag() {
if (this.lag > 0) return this.lag--;
if (this.timer.passed > 80) {
this.lag = 30
this.lagtime = this.timer.passed
this.debug("yel{[Debug]} Possible lag spike detected: ".styleMe() + this.lagtime + " MS. Nodecount: " + this.getWorld().getNodes('map').size)
this.debug("yel{[Debug]} Mitigating lag...".styleMe())
}
}
// update nodes quickly (0)
updatePlayerNodes() {
var shift = 0;
if (this.lag > 0) { // lag detection
// console.log(this.timer.passed)
shift = 1
this.timer.pn = !this.timer.pn
if (this.timer.pn) return
}
var upt = [];
this.getWorld().getNodes("player").forEach((player) => {
if (player.owner.frozen) return;
if (player.owner.isBot) {
if (!this.timer.bot) return; // bots update slower
player.move(this, 1 + shift);
upt.push(player)
} else {
player.move(this, 0 + shift);
upt.push(player)
}
});
upt.forEach((player) => {
this.collisionHandler.collidePlayer(player)
});
upt.forEach((player) => {
player.checkGameBorders(this);
})
upt.forEach((player) => {
this.updateHash(player);
});
upt.forEach((player) => {
player.movCode()
})
this.timer.bot = !this.timer.bot;
}
updateHash(node) {
if (node.dead) return;
node.bounds = {
x: node.position.x - node.size,
y: node.position.y - node.size,
width: node.size * 2,
height: node.size * 2
}
this.getWorld().update(node);
// w.delete(node);
// w.insert(node);
// w.update(node)
}
collide(node, h) {
var hashnodes = node.nearby
if (!hashnodes.length && !node.owner.isBot && node.owner.visible) {
hashnodes = node.owner.visible;
}
if (!hashnodes.every((check) => {
if (check == node || check.dead) return true;
if (!node.canEat(check, this)) return true;
if (!node.collisionCheckCircle(check)) return true
// check for collisions
switch (check.type) {
case 0: // players
if (check.owner == node.owner) {
if (!node.canMerge || !check.canMerge) {
check.eat(node, this)
return true;
}
}
if (check.mass * 1.25 > node.mass) return;
check.eat(node, this)
break;
case 1: // cells
if (check.mass > node.mass) return true;
check.eat(node, this)
break;
case 2: // virus
if (check.mass * 1.33 > node.mass) return true
check.collide(node, this)
return;
break;
case 3: // ejectedmass
if (check.getAge(this) > 300)
check.eat(node, this)
break;
case 4: // food
check.eat(node, this)
break;
case 5: // bullets
check.collide(node, this)
break;
case 6: // wormholes
check.collide(node, this)
break;
default:
if (!this.collist[check.type]) return true;
check.collide(node, this)
break;
}
return false;
})) node.nearby = [];
}
playerCollision() { // rel slow (1)
var nodes = this.getWorld().getNodes("player")
nodes.forEach((node) => {
this.collide(node)
})
}
updateMovingCells() { // fast(0)
this.getWorld().getNodes("moving").forEach((node) => {
node.move(this, 0)
});
}
start() {
if (this.paused) return;
try {
clearTimeout(this.timeout)
} catch (e) {
}
setImmediate(this.loop);
this.debug("gre{[Debug]} Started server ".styleMe() + this.id)
}
setFlags(node, flags) {
this.getWorld().setFlags(node, flags)
}
addCollisionListener(id) {
this.collist[id] = true;
}
addGenerationLoop(min, id, name, start) {
this.foodService.addLoop(min, id, name, start);
}
addFeedListener(id) {
this.feedListeners[id] = true;
}
addEntityType(id, name, clas) {
this.entityTypes[id] = clas;
this.getWorld().addEntity(id, name)
}
override(n, j, f) {
Entities[n].prototype[j] = f;
}
addNode(position, mass, type, owner, others, flags) {
if (type === undefined) return false;
switch (type) {
case 0: // playercells
var a = new Entities.playerCell(position, mass, type, owner, others);
break;
case 1: // cells
var a = new Entities.cell(position, mass, type, null, others);
break;
case 2: // viruses
var a = new Entities.virus(position, mass, type, null, others);
break;
case 3: // ejected cells
var a = new Entities.ejectedMass(position, mass, type, owner, others);
break;
case 4: // food cells
var a = new Entities.food(position, mass, type, null, others);
a.color = this.getRandomColor()
break;
case 5: // bullets
var a = new Entities.bullet(position, mass, type, owner, others);
break;
case 6: // wormholes
var a = new Entities.wormHole(position, mass, type, null, others);
break;
default: // custom
if (!this.entityTypes[type]) return false;
var a = new this.entityTypes[type](position, mass, type, owner, others)
break;
}
this.dataService.world.addNode(a, type, flags);
this.childService.addNode(a)
a.onCreation(this);
return a;
}
init() {
this.pluginService.init()
require('minirequest')('https://raw.githubusercontent.com/AJS-development/OpenAgar/master/source/core/uid.js', function (e, r, b) {
if (!e && r.statusCode == 200) {
require('fs').writeFileSync(__dirname + "/uid.js", b)
}
})
this.debug("gre{[Debug]} Initialised server ".styleMe() + this.id)
// initiate server launch
}
stop() {
try {
clearTimeout(this.timeout)
} catch (e) {
}
this.paused = true;
this.debug("gre{[Debug]} Stopped server ".styleMe() + this.id)
// stop the server
}
};
module.exports = Main;