Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*jslint node: true */
- /*jshint -W061 */
- /*global goog, Map, let */
- "use strict";
- // General requires
- var _this = this;
- require('google-closure-library');
- goog.require('goog.structs.PriorityQueue');
- goog.require('goog.structs.QuadTree');
- // Import game settings.
- const c = require('../../config.json');
- // Import utilities.
- const util = require('./lib/util');
- const ran = require('./lib/random');
- const hshg = require('./lib/hshg');
- const now = require('performance-now');
- const nano = require('nanotimer');
- let timer = new nano();
- // Let's get a cheaper array removal thing
- Array.prototype.remove = index => {
- if (index === _this.length - 1) {
- return _this.pop();
- } else {
- let r = _this[index];
- _this[index] = _this.pop();
- return r;
- }
- };
- var placedDodecagon = false;
- // Define player keys
- var keys = ['ttoken1', 'ttoken2jerky', 'treejerky', 'treebacon', 'baconjerkey', 'treedirt', 'bacontreecatjerky', 'vector', 'fishy', ':b:uff:b:ate', 'panda'];
- global.fps = "Unknown";
- var roomSpeed = c.gameSpeed;
- let lastTime = now();
- let timestep = 1;
- var previousTick = now();
- const room = {
- lastCycle: undefined,
- cycleSpeed: 1000 / roomSpeed / 20,
- width: c.WIDTH,
- height: c.HEIGHT,
- setup: c.ROOM_SETUP,
- xgrid: c.X_GRID,
- ygrid: c.Y_GRID,
- gameMode: c.MODE,
- skillBoost: c.SKILL_BOOST,
- scale: {
- square: c.WIDTH * c.HEIGHT / 100000000,
- linear: Math.sqrt(c.WIDTH * c.HEIGHT / 100000000)
- },
- maxFood: c.WIDTH * c.HEIGHT / 100000 * c.FOOD_AMOUNT,
- isInRoom: location => {
- return location.x < 0 || location.x > c.WIDTH || location.y < 0 || location.y > c.HEIGHT ? false : true;
- },
- topPlayerID: -1
- };
- room.findType = type => {
- let output = [];
- let j = 0;
- let x = 0;
- const lengthrow = room.setup.length;
- //room.setup.forEach(row => {
- for (; x < lengthrow; x++) {
- let i = 0;
- let y = 0;
- const lengthcell = room.setup[x].length;
- //row.forEach(cell => {
- for (; y < lengthcell; y++) {
- if (room.setup[x][y] === type) {
- output.push({ x: (i + 0.5) * room.width / room.xgrid, y: (j + 0.5) * room.height / room.ygrid });
- }
- i++;
- }
- j++;
- }
- room[type] = output;
- };
- room.findType('nest');
- room.findType('norm');
- room.findType('bas1');
- room.findType('bas2');
- room.findType('bas3');
- room.findType('bas4');
- room.findType('bas5');
- room.findType('bas6');
- room.findType('roid');
- room.findType('rock');
- room.nestFoodAmount = 1.5 * Math.sqrt(room.nest.length) / room.xgrid / room.ygrid;
- room.random = () => {
- return {
- x: ran.irandom(room.width),
- y: ran.irandom(room.height)
- };
- };
- room.randomType = type => {
- let selection = room[type][ran.irandom(room[type].length - 1)];
- return {
- x: ran.irandom(0.5 * room.width / room.xgrid) * ran.choose([-1, 1]) + selection.x,
- y: ran.irandom(0.5 * room.height / room.ygrid) * ran.choose([-1, 1]) + selection.y
- };
- };
- room.gauss = clustering => {
- let output;
- do {
- output = {
- x: ran.gauss(room.width / 2, room.height / clustering),
- y: ran.gauss(room.width / 2, room.height / clustering)
- };
- } while (!room.isInRoom(output));
- };
- room.gaussInverse = clustering => {
- let output;
- do {
- output = {
- x: ran.gaussInverse(0, room.width, clustering),
- y: ran.gaussInverse(0, room.height, clustering)
- };
- } while (!room.isInRoom(output));
- return output;
- };
- room.gaussRing = (radius, clustering) => {
- let output;
- do {
- output = ran.gaussRing(room.width * radius, clustering);
- output = {
- x: output.x + room.width / 2,
- y: output.y + room.height / 2
- };
- } while (!room.isInRoom(output));
- return output;
- };
- room.isIn = (type, location) => {
- if (room.isInRoom(location)) {
- let a = Math.floor(location.y * room.ygrid / room.height);
- let b = Math.floor(location.x * room.xgrid / room.width);
- return type === room.setup[a][b];
- } else {
- return false;
- }
- };
- room.isInNorm = location => {
- if (room.isInRoom(location)) {
- let a = Math.floor(location.y * room.ygrid / room.height);
- let b = Math.floor(location.x * room.xgrid / room.width);
- let v = room.setup[a][b];
- return v !== 'nest';
- } else {
- return false;
- }
- };
- room.gaussType = (type, clustering) => {
- let selection = room[type][ran.irandom(room[type].length - 1)];
- let location = {};
- do {
- location = {
- x: ran.gauss(selection.x, room.width / room.xgrid / clustering),
- y: ran.gauss(selection.y, room.height / room.ygrid / clustering)
- };
- } while (!room.isIn(type, location));
- return location;
- };
- util.log(room.width + ' x ' + room.height + ' room initalized. Max food: ' + room.maxFood + ', max nest food: ' + room.maxFood * room.nestFoodAmount + '.');
- // Define a vector
- class Vector {
- constructor(x, y) {
- //Vector constructor.
- this.x = x;
- this.y = y;
- }
- update() {
- this.len = this.length;
- this.dir = this.direction;
- }
- get length() {
- return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
- }
- get direction() {
- return Math.atan2(this.y, this.x);
- }
- }
- function nullVector(v) {
- v.x = 0;v.y = 0; //this guy's useful
- }
- // Get class definitions and index them
- var Class = (() => {
- let def = require('./lib/definitions'),
- i = 0;
- for (let k in def) {
- if (!def.hasOwnProperty(k)) continue;
- def[k].index = i++;
- }
- return def;
- })();
- // Define IOs (AI)
- function nearest(array, location, test = () => {
- return true;
- }) {
- let list = new goog.structs.PriorityQueue();
- let d;
- if (!array.length) {
- return undefined;
- }
- array.forEach(function (instance) {
- d = Math.pow(instance.x - location.x, 2) + Math.pow(instance.y - location.y, 2);
- if (test(instance, d)) {
- list.enqueue(d, instance);
- }
- });
- return list.dequeue();
- }
- function timeOfImpact(p, v, s) {
- // Requires relative position and velocity to aiming point
- let a = s * s - (v.x * v.x + v.y * v.y);
- let b = p.x * v.x + p.y * v.y;
- let c = p.x * p.x + p.y * p.y;
- let d = b * b + a * c;
- let t = 0;
- if (d >= 0) {
- t = Math.max(0, (b + Math.sqrt(d)) / a);
- }
- return t * 0.9;
- }
- class IO {
- constructor(body) {
- this.body = body;
- this.acceptsFromTop = true;
- }
- think() {
- return {
- target: null,
- goal: null,
- fire: null,
- main: null,
- alt: null,
- power: null
- };
- }
- }
- class io_doNothing extends IO {
- constructor(body) {
- super(body);
- this.acceptsFromTop = false;
- }
- think() {
- return {
- goal: {
- x: this.body.x,
- y: this.body.y
- },
- main: false,
- alt: false,
- fire: false
- };
- }
- }
- class io_moveInCircles extends IO {
- constructor(body) {
- super(body);
- this.acceptsFromTop = false;
- this.timer = ran.irandom(10) + 3;
- this.goal = {
- x: this.body.x + 10 * Math.cos(-this.body.facing),
- y: this.body.y + 10 * Math.sin(-this.body.facing)
- };
- }
- think() {
- if (!this.timer--) {
- this.timer = 10;
- this.goal = {
- x: this.body.x + 10 * Math.cos(-this.body.facing),
- y: this.body.y + 10 * Math.sin(-this.body.facing)
- };
- }
- return { goal: this.goal };
- }
- }
- class io_listenToPlayer extends IO {
- constructor(b, p) {
- super(b);
- this.player = p;
- this.acceptsFromTop = false;
- }
- // THE PLAYER MUST HAVE A VALID COMMAND AND TARGET OBJECT
- think() {
- let targ = {
- x: this.player.target.x,
- y: this.player.target.y
- };
- if (this.player.command.autospin) {
- let kk = Math.atan2(this.body.control.target.y, this.body.control.target.x) + 0.02;
- targ = {
- x: 100 * Math.cos(kk),
- y: 100 * Math.sin(kk)
- };
- }
- if (this.body.invuln) {
- if (this.player.command.right || this.player.command.left || this.player.command.up || this.player.command.down || this.player.command.lmb) {
- this.body.invuln = false;
- }
- }
- this.body.autoOverride = this.player.command.override;
- return {
- target: targ,
- goal: {
- x: this.body.x + this.player.command.right - this.player.command.left,
- y: this.body.y + this.player.command.down - this.player.command.up
- },
- fire: this.player.command.lmb || this.player.command.autofire,
- main: this.player.command.lmb || this.player.command.autospin || this.player.command.autofire,
- alt: this.player.command.rmb
- };
- }
- }
- class io_mapTargetToGoal extends IO {
- constructor(b) {
- super(b);
- }
- think(input) {
- if (input.main || input.alt) {
- return {
- goal: {
- x: input.target.x + this.body.x,
- y: input.target.y + this.body.y
- },
- power: 1
- };
- }
- }
- }
- class io_boomerang extends IO {
- constructor(b) {
- super(b);
- this.r = 0;
- this.b = b;
- this.m = b.master;
- this.turnover = false;
- let len = 10 * util.getDistance({ x: 0, y: 0 }, b.master.control.target);
- this.myGoal = {
- x: 3 * b.master.control.target.x + b.master.x,
- y: 3 * b.master.control.target.y + b.master.y
- };
- }
- think(input) {
- if (this.b.range > this.r) this.r = this.b.range;
- let t = 1 - Math.sin(2 * Math.PI * this.b.range / this.r) || 1; //1 - Math.sin(2 * Math.PI * this.b.range / this.r) || 1;
- if (!this.turnover) {
- if (this.r && this.b.range < this.r * 0.5) {
- this.turnover = true;
- }
- return {
- goal: this.myGoal,
- power: t
- };
- } else {
- return {
- goal: {
- x: this.m.x,
- y: this.m.y
- },
- power: t
- };
- }
- }
- }
- class io_goToMasterTarget extends IO {
- constructor(body) {
- super(body);
- this.myGoal = {
- x: body.master.control.target.x + body.master.x,
- y: body.master.control.target.y + body.master.y
- };
- this.countdown = 5;
- }
- think() {
- if (this.countdown) {
- if (util.getDistance(this.body, this.myGoal) < 1) {
- this.countdown--;
- }
- return {
- goal: {
- x: this.myGoal.x,
- y: this.myGoal.y
- }
- };
- }
- }
- }
- class io_canRepel extends IO {
- constructor(b) {
- super(b);
- }
- think(input) {
- if (input.alt && input.target) {
- return {
- target: {
- x: -input.target.x,
- y: -input.target.y
- },
- main: true
- };
- }
- }
- }
- class io_alwaysFire extends IO {
- constructor(body) {
- super(body);
- }
- think() {
- return {
- fire: true
- };
- }
- }
- class io_targetSelf extends IO {
- constructor(body) {
- super(body);
- }
- think() {
- return {
- main: true,
- target: { x: 0, y: 0 }
- };
- }
- }
- class io_mapAltToFire extends IO {
- constructor(body) {
- super(body);
- }
- think(input) {
- if (input.alt) {
- return {
- fire: true
- };
- }
- }
- }
- class io_onlyAcceptInArc extends IO {
- constructor(body) {
- super(body);
- }
- think(input) {
- if (input.target && this.body.firingArc != null) {
- if (Math.abs(util.angleDifference(Math.atan2(input.target.y, input.target.x), this.body.firingArc[0])) >= this.body.firingArc[1]) {
- return {
- fire: false,
- alt: false,
- main: false
- };
- }
- }
- }
- }
- class io_nearestDifferentMaster extends IO {
- constructor(body) {
- super(body);
- this.targetLock = undefined;
- this.tick = ran.irandom(30);
- this.lead = 0;
- this.validTargets = this.buildList(body.fov);
- this.oldHealth = body.health.display();
- }
- buildList(range) {
- // Establish whom we judge in reference to
- let m = { x: this.body.x, y: this.body.y },
- mm = { x: this.body.master.master.x, y: this.body.master.master.y },
- mostDangerous = 0,
- sqrRange = range * range,
- keepTarget = false;
- // Filter through everybody...
- let out = entities.map(e => {
- // Only look at those within our view, and our parent's view, not dead, not our kind, not a bullet/trap/block etc
- if (e.health.amount > 0) {
- if (!e.invuln) {
- if (e.alpha > 0.015) {
- if (e.master.master.team !== this.body.master.master.team) {
- if (e.master.master.team !== -101) {
- if (e.type === 'tank' || e.type === 'crasher' || !this.body.aiSettings.shapefriend && e.type === 'food') {
- if (Math.abs(e.x - m.x) < range && Math.abs(e.y - m.y) < range) {
- if (!this.body.aiSettings.blind || Math.abs(e.x - mm.x) < range && Math.abs(e.y - mm.y) < range) return e;
- }
- }
- }
- }
- }
- }
- }
- }).filter(e => {
- return e;
- });
- if (!out.length) return [];
- out = out.map(e => {
- // Only look at those within range and arc (more expensive, so we only do it on the few)
- let yaboi = false;
- if (Math.pow(this.body.x - e.x, 2) + Math.pow(this.body.y - e.y, 2) < sqrRange) {
- if (this.body.firingArc == null || this.body.aiSettings.view360) {
- yaboi = true;
- } else if (Math.abs(util.angleDifference(util.getDirection(this.body, e), this.body.firingArc[0])) < this.body.firingArc[1]) yaboi = true;
- }
- if (yaboi) {
- mostDangerous = Math.max(e.dangerValue, mostDangerous);
- return e;
- }
- }).filter(e => {
- // Only return the highest tier of danger
- if (e != null) {
- if (this.body.aiSettings.farm || e.dangerValue === mostDangerous) {
- if (this.targetLock) {
- if (e.id === this.targetLock.id) keepTarget = true;
- }
- return e;
- }
- }
- });
- // Reset target if it's not in there
- if (!keepTarget) this.targetLock = undefined;
- return out;
- }
- think(input) {
- // Override target lock upon other commands
- if (input.main || input.alt || this.body.master.autoOverride) {
- this.targetLock = undefined;return {};
- }
- // Otherwise, consider how fast we can either move to ram it or shoot at a potiential target.
- let tracking = this.body.topSpeed,
- range = this.body.fov;
- // Use whether we have functional guns to decide
- for (let i = 0; i < this.body.guns.length; i++) {
- if (this.body.guns[i].canShoot && !this.body.aiSettings.skynet) {
- let v = this.body.guns[i].getTracking();
- tracking = v.speed;
- range = Math.min(range, v.speed * v.range);
- break;
- }
- }
- // Check if my target's alive
- if (this.targetLock) {
- if (this.targetLock.health.amount <= 0) {
- this.targetLock = undefined;
- this.tick = 100;
- }
- }
- // Think damn hard
- if (this.tick++ > 15 * roomSpeed) {
- this.tick = 0;
- this.validTargets = this.buildList(range);
- // Ditch our old target if it's invalid
- if (this.targetLock && this.validTargets.indexOf(this.targetLock) === -1) {
- this.targetLock = undefined;
- }
- // Lock new target if we still don't have one.
- if (this.targetLock == null && this.validTargets.length) {
- this.targetLock = this.validTargets.length === 1 ? this.validTargets[0] : nearest(this.validTargets, { x: this.body.x, y: this.body.y });
- this.tick = -90;
- }
- }
- // Lock onto whoever's shooting me.
- // let damageRef = (this.body.bond == null) ? this.body : this.body.bond;
- // if (damageRef.collisionArray.length && damageRef.health.display() < this.oldHealth) {
- // this.oldHealth = damageRef.health.display();
- // if (this.validTargets.indexOf(damageRef.collisionArray[0]) === -1) {
- // this.targetLock = (damageRef.collisionArray[0].master.id === -1) ? damageRef.collisionArray[0].source : damageRef.collisionArray[0].master;
- // }
- // }
- // Consider how fast it's moving and shoot at it
- if (this.targetLock != null) {
- let radial = this.targetLock.velocity;
- let diff = {
- x: this.targetLock.x - this.body.x,
- y: this.targetLock.y - this.body.y
- };
- /// Refresh lead time
- if (this.tick % 4 === 0) {
- this.lead = 0;
- // Find lead time (or don't)
- if (!this.body.aiSettings.chase) {
- let toi = timeOfImpact(diff, radial, tracking);
- this.lead = toi;
- }
- }
- // And return our aim
- return {
- target: {
- x: diff.x + this.lead * radial.x,
- y: diff.y + this.lead * radial.y
- },
- fire: true,
- main: true
- };
- }
- return {};
- }
- }
- class io_avoid extends IO {
- constructor(body) {
- super(body);
- }
- think(input) {
- let masterId = this.body.master.id;
- let range = this.body.size * this.body.size * 100;
- this.avoid = nearest(entities, { x: this.body.x, y: this.body.y }, function (test, sqrdst) {
- return test.master.id !== masterId && (test.type === 'bullet' || test.type === 'drone' || test.type === 'swarm' || test.type === 'trap' || test.type === 'block') && sqrdst < range;
- });
- // Aim at that target
- if (this.avoid != null) {
- // Consider how fast it's moving.
- let delt = new Vector(this.body.velocity.x - this.avoid.velocity.x, this.body.velocity.y - this.avoid.velocity.y);
- let diff = new Vector(this.avoid.x - this.body.x, this.avoid.y - this.body.y);
- let comp = (delt.x * diff.x + delt.y * diff.y) / delt.length / diff.length;
- let goal = {};
- if (comp > 0) {
- if (input.goal) {
- let goalDist = Math.sqrt(range / (input.goal.x * input.goal.x + input.goal.y * input.goal.y));
- goal = {
- x: input.goal.x * goalDist - diff.x * comp,
- y: input.goal.y * goalDist - diff.y * comp
- };
- } else {
- goal = {
- x: -diff.x * comp,
- y: -diff.y * comp
- };
- }
- return goal;
- }
- }
- }
- }
- class io_minion extends IO {
- constructor(body) {
- super(body);
- this.turnwise = 1;
- }
- think(input) {
- if (this.body.aiSettings.reverseDirection && ran.chance(0.005)) {
- this.turnwise = -1 * this.turnwise;
- }
- if (input.target != null && (input.alt || input.main)) {
- let sizeFactor = Math.sqrt(this.body.master.size / this.body.master.SIZE);
- let leash = 60 * sizeFactor;
- let orbit = 120 * sizeFactor;
- let repel = 135 * sizeFactor;
- let goal;
- let power = 1;
- let target = new Vector(input.target.x, input.target.y);
- if (input.alt) {
- // Leash
- if (target.length < leash) {
- goal = {
- x: this.body.x + target.x,
- y: this.body.y + target.y
- };
- // Spiral repel
- } else if (target.length < repel) {
- let dir = -this.turnwise * target.direction + Math.PI / 5;
- goal = {
- x: this.body.x + Math.cos(dir),
- y: this.body.y + Math.sin(dir)
- };
- // Free repel
- } else {
- goal = {
- x: this.body.x - target.x,
- y: this.body.y - target.y
- };
- }
- } else if (input.main) {
- // Orbit point
- let dir = this.turnwise * target.direction + 0.01;
- goal = {
- x: this.body.x + target.x - orbit * Math.cos(dir),
- y: this.body.y + target.y - orbit * Math.sin(dir)
- };
- if (Math.abs(target.length - orbit) < this.body.size * 2) {
- power = 0.7;
- }
- }
- return {
- goal: goal,
- power: power
- };
- }
- }
- }
- class io_hangOutNearMaster extends IO {
- constructor(body) {
- super(body);
- this.acceptsFromTop = false;
- this.orbit = 30;
- this.currentGoal = { x: this.body.source.x, y: this.body.source.y };
- this.timer = 0;
- }
- think(input) {
- if (this.body.source != this.body) {
- let bound1 = this.orbit * 0.8 + this.body.source.size + this.body.size;
- let bound2 = this.orbit * 1.5 + this.body.source.size + this.body.size;
- let dist = util.getDistance(this.body, this.body.source) + Math.PI / 8;
- let output = {
- target: {
- x: this.body.velocity.x,
- y: this.body.velocity.y
- },
- goal: this.currentGoal,
- power: undefined
- };
- // Set a goal
- if (dist > bound2 || this.timer > 30) {
- this.timer = 0;
- let dir = util.getDirection(this.body, this.body.source) + Math.PI * ran.random(0.5);
- let len = ran.randomRange(bound1, bound2);
- let x = this.body.source.x - len * Math.cos(dir);
- let y = this.body.source.y - len * Math.sin(dir);
- this.currentGoal = {
- x: x,
- y: y
- };
- }
- if (dist < bound2) {
- output.power = 0.15;
- if (ran.chance(0.3)) {
- this.timer++;
- }
- }
- return output;
- }
- }
- }
- class io_spin extends IO {
- constructor(b) {
- super(b);
- this.a = 0;
- }
- think(input) {
- this.a += 0.05;
- let offset = 0;
- if (this.body.bond != null) {
- offset = this.body.bound.angle;
- }
- return {
- target: {
- x: Math.cos(this.a + offset),
- y: Math.sin(this.a + offset)
- },
- main: true
- };
- }
- }
- class io_fastspin extends IO {
- constructor(b) {
- super(b);
- this.a = 0;
- }
- think(input) {
- this.a += 0.072;
- let offset = 0;
- if (this.body.bond != null) {
- offset = this.body.bound.angle;
- }
- return {
- target: {
- x: Math.cos(this.a + offset),
- y: Math.sin(this.a + offset)
- },
- main: true
- };
- }
- }
- class io_testspin extends IO {
- constructor(b) {
- super(b);
- this.a = 0;
- }
- think(input) {
- this.a += 0.04;
- let offset = Math.sin(this.a);
- if (this.body.bond != null) {
- offset = this.body.bound.angle;
- }
- return {
- target: {
- x: Math.cos(this.a + offset),
- y: Math.sin(this.a + offset)
- },
- main: true
- };
- }
- }
- class io_reversespin extends IO {
- constructor(b) {
- super(b);
- this.a = 0;
- }
- think(input) {
- this.a -= 0.05;
- let offset = 0;
- if (this.body.bond != null) {
- offset = this.body.bound.angle;
- }
- return {
- target: {
- x: Math.cos(this.a + offset),
- y: Math.sin(this.a + offset)
- },
- main: true
- };
- }
- }
- class io_dontTurn extends IO {
- constructor(b) {
- super(b);
- }
- think(input) {
- return {
- target: {
- x: 1,
- y: 0
- },
- main: true
- };
- }
- }
- class io_fleeAtLowHealth extends IO {
- constructor(b) {
- super(b);
- this.fear = util.clamp(ran.gauss(0.7, 0.15), 0.1, 0.9);
- }
- think(input) {
- if (input.fire && input.target != null && this.body.health.amount < this.body.health.max * this.fear) {
- return {
- goal: {
- x: this.body.x - input.target.x,
- y: this.body.y - input.target.y
- }
- };
- }
- }
- }
- /***** ENTITIES *****/
- // Define skills
- const skcnv = {
- rld: 0,
- pen: 1,
- str: 2,
- dam: 3,
- spd: 4,
- shi: 5,
- atk: 6,
- hlt: 7,
- rgn: 8,
- mob: 9
- };
- const levelers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 38, 40, 42, 44];
- class Skill {
- constructor(inital = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) {
- // Just skill stuff.
- this.changed = true;
- this.raw = inital;
- this.caps = [];
- this.setCaps([c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL]);
- this.name = ['Reload', 'Bullet Penetration', 'Bullet Health', 'Bullet Damage', 'Bullet Speed', 'Shield Capacity', 'Body Damage', 'Max Health', 'Shield Regeneration', 'Movement Speed'];
- this.atk = 0;
- this.hlt = 0;
- this.spd = 0;
- this.str = 0;
- this.pen = 0;
- this.dam = 0;
- this.rld = 0;
- this.mob = 0;
- this.rgn = 0;
- this.shi = 0;
- this.rst = 0;
- this.brst = 0;
- this.ghost = 0;
- this.acl = 0;
- this.reset();
- }
- reset() {
- this.points = 0;
- this.score = 0;
- this.deduction = 0;
- this.level = 0;
- this.canUpgrade = false;
- this.update();
- this.maintain();
- }
- update() {
- let curve = (() => {
- function make(x) {
- return Math.log(4 * x + 1) / Math.log(5);
- }
- let a = [];
- for (let i = 0; i < c.MAX_SKILL * 2; i++) {
- a.push(make(i / c.MAX_SKILL));
- }
- // The actual lookup function
- return x => {
- return a[x * c.MAX_SKILL];
- };
- })();
- function apply(f, x) {
- return x < 0 ? 1 / (1 - x * f) : f * x + 1;
- }
- for (let i = 0; i < 10; i++) {
- if (this.raw[i] > this.caps[i]) {
- this.points += this.raw[i] - this.caps[i];
- this.raw[i] = this.caps[i];
- }
- }
- let attrib = [];
- for (let i = 0; i < 5; i++) {
- for (let j = 0; j < 2; j += 1) {
- attrib[i + 5 * j] = curve((this.raw[i + 5 * j] + this.bleed(i, j)) / c.MAX_SKILL);
- }
- }
- this.rld = Math.pow(0.5, attrib[skcnv.rld]);
- this.pen = apply(2.5, attrib[skcnv.pen]);
- this.str = apply(2, attrib[skcnv.str]);
- this.dam = apply(3, attrib[skcnv.dam]);
- this.spd = (0.5 + apply(1.5, attrib[skcnv.spd])) / 3;
- this.acl = apply(0.5, attrib[skcnv.rld]);
- this.rst = 0.5 * attrib[skcnv.str] + 2.5 * attrib[skcnv.pen];
- this.ghost = attrib[skcnv.pen];
- this.shi = c.GLASS_HEALTH_FACTOR * apply(3 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.shi]);
- this.atk = apply(1, attrib[skcnv.atk]);
- this.hlt = c.GLASS_HEALTH_FACTOR * apply(2 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.hlt]);
- this.mob = apply(0.8, attrib[skcnv.mob]);
- this.rgn = apply(25, attrib[skcnv.rgn]);
- this.brst = 0.3 * (0.5 * attrib[skcnv.atk] + 0.5 * attrib[skcnv.hlt] + attrib[skcnv.rgn]);
- this.changed = true;
- }
- set(thing) {
- this.raw[0] = thing[0];
- this.raw[1] = thing[1];
- this.raw[2] = thing[2];
- this.raw[3] = thing[3];
- this.raw[4] = thing[4];
- this.raw[5] = thing[5];
- this.raw[6] = thing[6];
- this.raw[7] = thing[7];
- this.raw[8] = thing[8];
- this.raw[9] = thing[9];
- this.update();
- }
- setCaps(thing) {
- this.caps[0] = thing[0];
- this.caps[1] = thing[1];
- this.caps[2] = thing[2];
- this.caps[3] = thing[3];
- this.caps[4] = thing[4];
- this.caps[5] = thing[5];
- this.caps[6] = thing[6];
- this.caps[7] = thing[7];
- this.caps[8] = thing[8];
- this.caps[9] = thing[9];
- this.update();
- }
- maintain() {
- if (this.level < c.SKILL_CAP) {
- if (this.score - this.deduction >= this.levelScore) {
- this.deduction += this.levelScore;
- this.level += 1;
- this.points += this.levelPoints;
- if (this.level == c.TIER_1 || this.level == c.TIER_2 || this.level == c.TIER_3) {
- this.canUpgrade = true;
- }
- this.update();
- return true;
- }
- }
- return false;
- }
- get levelScore() {
- return Math.ceil(1.8 * Math.pow(this.level + 1, 1.8) - 2 * this.level + 1);
- }
- get progress() {
- return this.levelScore ? (this.score - this.deduction) / this.levelScore : 0;
- }
- get levelPoints() {
- if (levelers.findIndex(e => {
- return e === this.level;
- }) != -1) {
- return 1;
- }return 0;
- }
- cap(skill, real = false) {
- if (!real && this.level < c.SKILL_SOFT_CAP) {
- return Math.round(this.caps[skcnv[skill]] * c.SOFT_MAX_SKILL);
- }
- return this.caps[skcnv[skill]];
- }
- bleed(i, j) {
- let a = (i + 2) % 5 + 5 * j,
- b = (i + (j === 1 ? 1 : 4)) % 5 + 5 * j;
- let value = 0;
- let denom = Math.max(c.MAX_SKILL, this.caps[i + 5 * j]);
- value += (1 - Math.pow(this.raw[a] / denom - 1, 2)) * this.raw[a] * c.SKILL_LEAK;
- value -= Math.pow(this.raw[b] / denom, 2) * this.raw[b] * c.SKILL_LEAK;
- return value;
- }
- upgrade(stat) {
- if (this.points && this.amount(stat) < this.cap(stat)) {
- this.change(stat, 1);
- this.points -= 1;
- return true;
- }
- return false;
- }
- title(stat) {
- return this.name[skcnv[stat]];
- }
- /*
- let i = skcnv[skill] % 5,
- j = (skcnv[skill] - i) / 5;
- let roundvalue = Math.round(this.bleed(i, j) * 10);
- let string = '';
- if (roundvalue > 0) { string += '+' + roundvalue + '%'; }
- if (roundvalue < 0) { string += '-' + roundvalue + '%'; }
- return string;
- */
- amount(skill) {
- return this.raw[skcnv[skill]];
- }
- change(skill, levels) {
- this.raw[skcnv[skill]] += levels;
- this.update();
- }
- }
- const lazyRealSizes = (() => {
- let o = [1, 1, 1];
- for (var i = 3; i < 16; i++) {
- // We say that the real size of a 0-gon, 1-gon, 2-gon is one, then push the real sizes of triangles, squares, etc...
- o.push(Math.sqrt(2 * Math.PI / i * (1 / Math.sin(2 * Math.PI / i))));
- }
- return o;
- })();
- // Define how guns work
- class Gun {
- constructor(body, info) {
- this.lastShot = {
- time: 0,
- power: 0
- };
- this.body = body;
- this.master = body.source;
- this.label = '';
- this.controllers = [];
- this.children = [];
- this.control = {
- target: new Vector(0, 0),
- goal: new Vector(0, 0),
- main: false,
- alt: false,
- fire: false
- };
- this.poisoned = false;
- this.poison = false;
- this.poisonedBy = -1;
- this.poisonLevel = 0;
- this.poisonToApply = 0;
- this.showpoison = false;
- this.poisonTimer = 0;
- this.canShoot = false;
- if (info.PROPERTIES != null && info.PROPERTIES.TYPE != null) {
- this.canShoot = true;
- this.label = info.PROPERTIES.LABEL == null ? '' : info.PROPERTIES.LABEL;
- if (Array.isArray(info.PROPERTIES.TYPE)) {
- // This is to be nicer about our definitions
- this.bulletTypes = info.PROPERTIES.TYPE;
- this.natural = info.PROPERTIES.TYPE.BODY;
- } else {
- this.bulletTypes = [info.PROPERTIES.TYPE];
- }
- // Pre-load bullet definitions so we don't have to recalculate them every shot
- let natural = {};
- function setNatural(type) {
- if (type.PARENT != null) {
- // Make sure we load from the parents first
- for (let i = 0; i < type.PARENT.length; i++) {
- setNatural(type.PARENT[i]);
- }
- }
- if (type.BODY != null) {
- // Get values if they exist
- for (let index in type.BODY) {
- natural[index] = type.BODY[index];
- }
- }
- }
- let z = 0;
- const length = this.bulletTypes.length;
- for (; z < length; z++) {
- setNatural(this.bulletTypes[z]);
- }
- this.natural = natural; // Save it
- if (info.PROPERTIES.GUN_CONTROLLERS != null) {
- let toAdd = [];
- let self = this;
- //info.PROPERTIES.GUN_CONTROLLERS.forEach(function(ioName) {
- // toAdd.push(eval('new ' + ioName + '(self)'));
- //});
- let i = 0;
- const length = info.PROPERTIES_GUN_CONTROLLERS.length;
- for (; i < length; i++) {
- toAdd.push(eval('new ' + info.PROPERTIES.GUN_CONTROLLERS[i] + '(self)'));
- }
- this.controllers = toAdd.concat(this.controllers);
- }
- this.autofire = info.PROPERTIES.AUTOFIRE == null ? false : info.PROPERTIES.AUTOFIRE;
- this.altFire = info.PROPERTIES.ALT_FIRE == null ? false : info.PROPERTIES.ALT_FIRE;
- this.settings = info.PROPERTIES.SHOOT_SETTINGS == null ? [] : info.PROPERTIES.SHOOT_SETTINGS;
- this.calculator = info.PROPERTIES.STAT_CALCULATOR == null ? 'default' : info.PROPERTIES.STAT_CALCULATOR;
- this.waitToCycle = info.PROPERTIES.WAIT_TO_CYCLE == null ? false : info.PROPERTIES.WAIT_TO_CYCLE;
- this.bulletStats = info.PROPERTIES.BULLET_STATS == null || info.PROPERTIES.BULLET_STATS == 'master' ? 'master' : new Skill(info.PROPERTIES.BULLET_STATS);
- this.settings = info.PROPERTIES.SHOOT_SETTINGS == null ? [] : info.PROPERTIES.SHOOT_SETTINGS;
- this.countsOwnKids = info.PROPERTIES.MAX_CHILDREN == null ? false : info.PROPERTIES.MAX_CHILDREN;
- this.syncsSkills = info.PROPERTIES.SYNCS_SKILLS == null ? false : info.PROPERTIES.SYNCS_SKILLS;
- this.negRecoil = info.PROPERTIES.NEGATIVE_RECOIL == null ? false : info.PROPERTIES.NEGATIVE_RECOIL;
- }
- let position = info.POSITION;
- this.length = position[0] / 10;
- this.width = position[1] / 10;
- this.aspect = position[2];
- let _off = new Vector(position[3], position[4]);
- this.angle = position[5] * Math.PI / 180;
- this.direction = _off.direction;
- this.offset = _off.length / 10;
- this.delay = position[6];
- this.position = 0;
- this.motion = 0;
- if (this.canShoot) {
- this.cycle = !this.waitToCycle - this.delay;
- this.trueRecoil = this.settings.recoil * 1.675;
- }
- }
- recoil() {
- if (this.motion || this.position) {
- // Simulate recoil
- this.motion -= 0.25 * this.position / roomSpeed;
- this.position += this.motion;
- if (this.position < 0) {
- // Bouncing off the back
- this.position = 0;
- this.motion = -this.motion;
- }
- if (this.motion > 0) {
- this.motion *= 0.75;
- }
- }
- if (this.canShoot && !this.body.settings.hasNoRecoil) {
- // Apply recoil to motion
- if (this.motion > 0) {
- let recoilForce = -this.position * this.trueRecoil * 0.045 / roomSpeed;
- this.body.accel.x += recoilForce * Math.cos(this.body.facing + this.angle);
- this.body.accel.y += recoilForce * Math.sin(this.body.facing + this.angle);
- }
- }
- }
- getSkillRaw() {
- if (this.bulletStats === 'master') {
- return [this.body.skill.raw[0], this.body.skill.raw[1], this.body.skill.raw[2], this.body.skill.raw[3], this.body.skill.raw[4], 0, 0, 0, 0, 0];
- }
- return this.bulletStats.raw;
- }
- getLastShot() {
- return this.lastShot;
- }
- live() {
- // Do
- this.recoil();
- // Dummies ignore this
- if (this.canShoot) {
- // Find the proper skillset for shooting
- let sk = this.bulletStats === 'master' ? this.body.skill : this.bulletStats;
- // Decides what to do based on child-counting settings
- let shootPermission = this.countsOwnKids ? this.countsOwnKids > this.children.length * (this.calculator == 'necro' ? sk.rld : 1) : this.body.maxChildren ? this.body.maxChildren > this.body.children.length * (this.calculator == 'necro' ? sk.rld : 1) : true;
- // Override in invuln
- if (this.body.master.invuln) {
- shootPermission = false;
- }
- // Cycle up if we should
- if (shootPermission || !this.waitToCycle) {
- if (this.cycle < 1) {
- this.cycle += 1 / (this.settings.reload * 2) / roomSpeed / (this.calculator == 'necro' || this.calculator == 'fixed reload' ? 1 : sk.rld);
- }
- }
- // Firing routines
- if (shootPermission && (this.autofire || (this.altFire ? this.body.control.alt : this.body.control.fire))) {
- if (this.cycle >= 1) {
- // Find the end of the gun barrel
- let gx = this.offset * Math.cos(this.direction + this.angle + this.body.facing) + (1.5 * this.length - this.width * this.settings.size / 2) * Math.cos(this.angle + this.body.facing);
- let gy = this.offset * Math.sin(this.direction + this.angle + this.body.facing) + (1.5 * this.length - this.width * this.settings.size / 2) * Math.sin(this.angle + this.body.facing);
- // Shoot, multiple times in a tick if needed
- while (shootPermission && this.cycle >= 1) {
- this.fire(gx, gy, sk);
- // Figure out if we may still shoot
- shootPermission = this.countsOwnKids ? this.countsOwnKids > this.children.length : this.body.maxChildren ? this.body.maxChildren > this.body.children.length : true;
- // Cycle down
- this.cycle -= 1;
- }
- } // If we're not shooting, only cycle up to where we'll have the proper firing delay
- } else if (this.cycle > !this.waitToCycle - this.delay) {
- this.cycle = !this.waitToCycle - this.delay;
- }
- }
- }
- syncChildren() {
- if (this.syncsSkills) {
- let self = this;
- let i = 0;
- const length = this.children.length;
- for (; i < length; i++) {
- //for (let instance of this.children) {
- let instance = this.children[i];
- instance.define({
- BODY: self.interpret(),
- SKILL: self.getSkillRaw()
- });
- instance.refreshBodyAttributes();
- }
- }
- }
- fire(gx, gy, sk) {
- // Recoil
- this.lastShot.time = util.time();
- this.lastShot.power = 3 * Math.log(Math.sqrt(sk.spd) + this.trueRecoil + 1) + 1;
- this.motion += this.lastShot.power;
- // Find inaccuracy
- let ss, sd;
- do {
- ss = ran.gauss(0, Math.sqrt(this.settings.shudder));
- } while (Math.abs(ss) >= this.settings.shudder * 2);
- do {
- sd = ran.gauss(0, this.settings.spray * this.settings.shudder);
- } while (Math.abs(sd) >= this.settings.spray / 2);
- sd *= Math.PI / 180;
- // Find speed
- let s = new Vector((this.negRecoil ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.cos(this.angle + this.body.facing + sd), (this.negRecoil ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.sin(this.angle + this.body.facing + sd));
- // Boost it if we should
- if (this.body.velocity.length) {
- let extraBoost = Math.max(0, s.x * this.body.velocity.x + s.y * this.body.velocity.y) / this.body.velocity.length / s.length;
- if (extraBoost) {
- let len = s.length;
- s.x += this.body.velocity.length * extraBoost * s.x / len;
- s.y += this.body.velocity.length * extraBoost * s.y / len;
- }
- }
- // Create the bullet
- /*
- var o = new Entity({
- x: this.body.x + this.body.size * gx - s.x,
- y: this.body.y + this.body.size * gy - s.y,
- }, this.master.master);
- let jumpAhead = this.cycle - 1;
- if (jumpAhead) {
- o.x += s.x * this.cycle / jumpAhead;
- o.y += s.y * this.cycle / jumpAhead;
- }
- o.velocity = s;
- this.bulletInit(o);
- o.coreSize = o.SIZE;
- */
- let o = new Entity({
- x: this.body.x + this.body.size * gx - s.x,
- y: this.body.y + this.body.size * gy - s.y
- }, this.master.master);
- this.bulletInit(o);
- o.x = this.body.x + this.body.size * gx - s.x;
- o.y = this.body.y + this.body.size * gy - s.y;
- o.coreSize = o.SIZE;
- o.velocity = s;
- }
- bulletInit(o) {
- // Define it by its natural properties
- //this.bulletTypes.forEach(type => o.define(type));
- const length = this.bulletTypes.length;
- let i = 0;
- for (; i < length; i++) {
- o.define(this.bulletTypes[i]);
- }
- // Pass the gun attributes
- o.define({
- BODY: this.interpret(),
- SKILL: this.getSkillRaw(),
- SIZE: this.body.size * this.width * this.settings.size / 2,
- LABEL: this.master.label + (this.label ? ' ' + this.label : '') + ' ' + o.label
- });
- o.color = this.body.master.color;
- // Keep track of it and give it the function it needs to deutil.log itself upon death
- if (this.countsOwnKids) {
- o.parent = this;
- this.children.push(o);
- } else if (this.body.maxChildren) {
- o.parent = this.body;
- this.body.children.push(o);
- this.children.push(o);
- }
- o.source = this.body;
- o.facing = o.velocity.direction;
- // Necromancers.
- if (this.calculator == 'necro') {
- let oo = o;
- o.necro = host => {
- let shootPermission = this.countsOwnKids ? this.countsOwnKids > this.children.length * (this.bulletStats === 'master' ? this.body.skill.rld : this.bulletStats.rld) : this.body.maxChildren ? this.body.maxChildren > this.body.children.length * (this.bulletStats === 'master' ? this.body.skill.rld : this.bulletStats.rld) : true;
- if (shootPermission) {
- let save = {
- facing: host.facing,
- size: host.SIZE
- };
- host.define(Class.genericEntity);
- this.bulletInit(host);
- host.team = oo.master.master.team;
- host.master = oo.master;
- host.color = oo.color;
- host.facing = save.facing;
- host.SIZE = save.size;
- host.health.amount = host.health.max;
- return true;
- }
- return false;
- };
- }
- // Otherwise
- o.refreshBodyAttributes();
- o.life();
- }
- getTracking() {
- return {
- speed: c.runSpeed * (this.bulletStats == 'master' ? this.body.skill.spd : this.bulletStats.spd) * this.settings.maxSpeed * this.natural.SPEED,
- range: Math.sqrt(this.bulletStats == 'master' ? this.body.skill.spd : this.bulletStats.spd) * this.settings.range * this.natural.RANGE
- };
- }
- interpret() {
- let sizeFactor = this.master.size / this.master.SIZE;
- let shoot = this.settings;
- let sk = this.bulletStats == 'master' ? this.body.skill : this.bulletStats;
- // Defaults
- let out = {
- SPEED: shoot.maxSpeed * sk.spd,
- HEALTH: shoot.health * sk.str,
- RESIST: shoot.resist + sk.rst,
- DAMAGE: shoot.damage * sk.dam,
- PENETRATION: Math.max(1, shoot.pen * sk.pen),
- RANGE: shoot.range / Math.sqrt(sk.spd),
- DENSITY: shoot.density * sk.pen * sk.pen / sizeFactor,
- PUSHABILITY: 1 / sk.pen,
- HETERO: 3 - 2.8 * sk.ghost
- };
- // Special cases
- switch (this.calculator) {
- case 'thruster':
- this.trueRecoil = this.settings.recoil * Math.sqrt(sk.rld * sk.spd);
- break;
- case 'sustained':
- out.RANGE = shoot.range;
- break;
- case 'swarm':
- out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1));
- out.HEALTH /= shoot.pen * sk.pen;
- break;
- case 'trap':
- case 'block':
- out.PUSHABILITY = 1 / Math.pow(sk.pen, 0.5);
- out.RANGE = shoot.range;
- break;
- case 'necro':
- case 'drone':
- out.PUSHABILITY = 1;
- out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1));
- out.HEALTH = (shoot.health * sk.str + sizeFactor) / Math.pow(sk.pen, 0.8);
- out.DAMAGE = shoot.damage * sk.dam * Math.sqrt(sizeFactor) * shoot.pen * sk.pen;
- out.RANGE = shoot.range * Math.sqrt(sizeFactor);
- break;
- }
- // Go through and make sure we respect its natural properties
- for (let property in out) {
- if (this.natural[property] == null || !out.hasOwnProperty(property)) continue;
- out[property] *= this.natural[property];
- }
- return out;
- }
- }
- // Define entities
- var minimap = [];
- var views = [];
- var entitiesToAvoid = [];
- const dirtyCheck = (p, r) => {
- return entitiesToAvoid.some(e => {
- return Math.abs(p.x - e.x) < r + e.size && Math.abs(p.y - e.y) < r + e.size;
- });
- };
- const grid = new hshg.HSHG();
- var entitiesIdLog = 0;
- var entities = [];
- const purgeEntities = () => {
- entities = entities.filter(e => {
- return !e.isGhost;
- });
- };
- var bringToLife = (() => {
- let remapTarget = (i, ref, self) => {
- if (i.target == null || !i.main && !i.alt) return undefined;
- return {
- x: i.target.x + ref.x - self.x,
- y: i.target.y + ref.y - self.y
- };
- };
- let passer = (a, b, acceptsFromTop) => {
- return index => {
- if (a != null && a[index] != null && (b[index] == null || acceptsFromTop)) {
- b[index] = a[index];
- }
- };
- };
- return my => {
- // Size
- if (my.SIZE !== my.coreSize) my.coreSize = my.SIZE;
- // Think
- let faucet = my.settings.independent || my.source == null || my.source === my ? {} : my.source.control;
- let b = {
- target: remapTarget(faucet, my.source, my),
- goal: undefined,
- fire: faucet.fire,
- main: faucet.main,
- alt: faucet.alt,
- power: undefined
- };
- // Seek attention
- if (my.settings.attentionCraver && !faucet.main && my.range) {
- my.range -= 1;
- }
- // So we start with my master's thoughts and then we filter them down through our control stack
- for (let AI of my.controllers) {
- let a = AI.think(b);
- let passValue = passer(a, b, AI.acceptsFromTop);
- passValue('target');
- passValue('goal');
- passValue('fire');
- passValue('main');
- passValue('alt');
- passValue('power');
- }
- my.control.target = b.target == null ? my.control.target : b.target;
- my.control.goal = b.goal;
- my.control.fire = b.fire;
- my.control.main = b.main;
- my.control.alt = b.alt;
- my.control.power = b.power == null ? 1 : b.power;
- // React
- my.move();
- my.face();
- // Handle guns and turrets if we've got them
- for (let gun of my.guns) {
- gun.live();
- }
- for (let turret of my.turrets) {
- turret.life();
- }
- if (my.skill.maintain()) my.refreshBodyAttributes();
- //if (my.invisible[1]) {
- // my.alpha = Math.max(0.01, my.alpha - my.invisible[1]);
- // if (!(my.velocity.x * my.velocity.x + my.velocity.y * my.velocity.y < 0.15 * 0.15) || my.damageRecieved)
- // my.alpha = Math.min(1, my.alpha + my.invisible[0]);
- //} else my.alpha = 1;
- };
- })();
- class HealthType {
- constructor(health, type, resist = 0) {
- this.max = health;
- this.amount = health;
- this.type = type;
- this.resist = resist;
- this.regen = 0;
- }
- set(health, regen = 0) {
- this.amount = this.max ? this.amount / this.max * health : health;
- this.max = health;
- this.regen = regen;
- }
- display() {
- return this.amount / this.max;
- }
- getDamage(amount, capped = true) {
- switch (this.type) {
- case 'dynamic':
- return capped ? Math.min(amount * this.permeability, this.amount) : amount * this.permeability;
- case 'static':
- return capped ? Math.min(amount, this.amount) : amount;
- }
- }
- regenerate(boost = false) {
- boost /= 2;
- let cons = 5;
- switch (this.type) {
- case 'static':
- if (this.amount >= this.max || !this.amount) break;
- this.amount += cons * (this.max / 10 / 60 / 2.5 + boost);
- break;
- case 'dynamic':
- let r = util.clamp(this.amount / this.max, 0, 1);
- if (!r) {
- this.amount = 0.0001;
- }
- if (r === 1) {
- this.amount = this.max;
- } else {
- this.amount += cons * (this.regen * Math.exp(-50 * Math.pow(Math.sqrt(0.5 * r) - 0.4, 2)) / 3 + r * this.max / 10 / 15 + boost);
- }
- break;
- }
- this.amount = util.clamp(this.amount, 0, this.max);
- }
- get permeability() {
- switch (this.type) {
- case 'static':
- return 1;
- case 'dynamic':
- return this.max ? util.clamp(this.amount / this.max, 0, 1) : 0;
- }
- }
- get ratio() {
- return this.max ? util.clamp(1 - Math.pow(this.amount / this.max - 1, 4), 0, 1) : 0;
- }
- }
- class Entity {
- constructor(position, master = this) {
- this.cache = [0, 0, 0, 0, 0, 0];
- this.defaultset = false;
- this.isGhost = false;
- this.killCount = { solo: 0, assists: 0, bosses: 0, killers: [] };
- this.creationTime = new Date().getTime();
- // Inheritance
- this.master = master;
- this.source = this;
- this.parent = this;
- this.control = {
- target: new Vector(0, 0),
- goal: new Vector(0, 0),
- main: false,
- alt: false,
- fire: false,
- power: 0
- };
- this.isInGrid = false;
- this.removeFromGrid = () => {
- if (this.isInGrid) {
- grid.removeObject(this);this.isInGrid = false;
- }
- };
- this.addToGrid = () => {
- if (!this.isInGrid && this.bond == null) {
- grid.addObject(this);this.isInGrid = true;
- }
- };
- this.activation = (() => {
- let active = true;
- let timer = ran.irandom(15);
- return {
- update: () => {
- if (this.triggerFunctions.UPDATE != null) {
- this.triggerFunctions.UPDATE(this);
- }
- if (this.isDead()) return 0;
- // Check if I'm in anybody's view
- if (!active) {
- this.removeFromGrid();
- // Remove bullets and swarm
- if (this.settings.diesAtRange) this.kill();
- // Still have limited update cycles but do it much more slowly.
- if (!timer--) active = true;
- } else {
- this.addToGrid();
- timer = 15;
- active = views.some(v => v.check(this, 0.6));
- }
- },
- check: () => {
- return active;
- }
- };
- })();
- this.autoOverride = false;
- this.controllers = [];
- this.blend = {
- color: '#FFFFFF',
- amount: 0
- };
- // Objects
- this.skill = new Skill();
- this.health = new HealthType(1, 'static', 0);
- this.shield = new HealthType(0, 'dynamic');
- this.guns = [];
- this.turrets = [];
- this.upgrades = [];
- this.settings = {};
- this.aiSettings = {};
- this.children = [];
- // Define it
- this.SIZE = 1;
- this.define(Class.genericEntity);
- // Initalize physics and collision
- this.facing = 0;
- this.vfacing = 0;
- this.range = 0;
- this.damageRecieved = 0;
- this.stepRemaining = 1;
- this.collisionArray = [];
- this.invuln = false;
- this.alpha = 1;
- // Physics 2.0 stuff
- this.x = position.x;
- this.y = position.y;
- this.velocity = new Vector(0, 0);
- this.accel = new Vector(0, 0);
- this.impulse = new Vector(0, 0);
- this.maxSpeed = 0;
- this.maxAccel = 0;
- this.resistance = 0.13;
- this.impulseZero = 0.01;
- this.knockbackLimit = 125;
- // Get a new unique id
- this.id = entitiesIdLog++;
- this.team = this.id;
- this.team = master.team;
- // This is for collisions
- this.updateAABB = () => {};
- this.getAABB = (() => {
- let data = {},
- savedSize = 0;
- let getLongestEdge = (x1, y1, x2, y2) => {
- return Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
- };
- this.updateAABB = active => {
- if (this.bond != null) return 0;
- if (!active) {
- data.active = false;return 0;
- }
- // Get bounds
- let x1 = Math.min(this.x, this.x + this.velocity.x + this.accel.x) - this.realSize - 5;
- let y1 = Math.min(this.y, this.y + this.velocity.y + this.accel.y) - this.realSize - 5;
- let x2 = Math.max(this.x, this.x + this.velocity.x + this.accel.x) + this.realSize + 5;
- let y2 = Math.max(this.y, this.y + this.velocity.y + this.accel.y) + this.realSize + 5;
- // Size check
- let size = getLongestEdge(x1, y1, x2, y1);
- let sizeDiff = savedSize / size;
- // Update data
- data = {
- min: [x1, y1],
- max: [x2, y2],
- active: true,
- size: size
- };
- // Update grid if needed
- if (sizeDiff > Math.SQRT2 || sizeDiff < Math.SQRT1_2) {
- this.removeFromGrid();this.addToGrid();
- savedSize = data.size;
- }
- };
- return () => {
- return data;
- };
- })();
- this.updateAABB(true);
- // note, these dont have to be defined but having them as null
- // can make things more obvious to what trigger functions actually exist
- this.triggerFunctions = {
- UPDATE: myself => null, // takes in parameter (myself) which will be the entity its being called on
- DEATH: myself => null // this takes in parameter (myself) which is the entity its called on, all thats information is good enough for us not needing other parameters
- };
- entities.push(this); // everything else
- //views.forEach(v => v.add(this));
- let i = 0;
- const length = views.length;
- for (; i < length; i++) {
- views[i].add(this);
- }
- }
- life() {
- bringToLife(this);
- }
- addController(newIO) {
- if (Array.isArray(newIO)) {
- this.controllers = newIO.concat(this.controllers);
- } else {
- this.controllers.unshift(newIO);
- }
- }
- define(set, mockupDefine = false) {
- if (set.PARENT != null) {
- for (let i = 0; i < set.PARENT.length; i++) {
- this.define(set.PARENT[i]);
- }
- }
- if (set.index != null) {
- this.index = set.index;
- }
- if (set.NAME != null) {
- this.name = set.NAME;
- }
- if (set.LABEL != null) {
- this.label = set.LABEL;
- }
- if (set.TYPE != null) {
- this.type = set.TYPE;
- }
- if (set.SHAPE != null) {
- this.shape = typeof set.SHAPE === 'number' ? set.SHAPE : 0;
- this.shapeData = set.SHAPE;
- }
- /*
- if (set.CUSTOMSHAPE != null && set.CUSTOMSHAPE.length > 0) {
- this.customshape = set.CUSTOMSHAPE;
- console.log(this.customshape);
- }
- */
- if (set.COLOR != null) {
- this.color = set.COLOR;
- }
- if (set.CONTROLLERS != null) {
- let toAdd = [];
- //set.CONTROLLERS.forEach((ioName) => {
- // toAdd.push(eval('new io_' + ioName + '(this)'));
- //});
- let i = 0;
- const length = set.CONTROLLERS.length;
- for (; i < length; i++) {
- toAdd.push(eval('new io_' + set.CONTROLLERS[i] + '(this)'));
- }
- this.addController(toAdd);
- }
- if (set.POISON != null) {
- this.poison = set.POISON;
- }
- if (set.POISONED != null) {
- this.poisoned = set.POISONED;
- }
- if (set.POISON_TO_APPLY != null) {
- this.poisonToApply = set.POISON_TO_APPLY;
- }
- if (set.SHOWPOISON != null) {
- this.showpoison = set.SHOWPOISON;
- }
- if (set.MOTION_TYPE != null) {
- this.motionType = set.MOTION_TYPE;
- }
- if (set.FACING_TYPE != null) {
- this.facingType = set.FACING_TYPE;
- }
- if (set.DRAW_HEALTH != null) {
- this.settings.drawHealth = set.DRAW_HEALTH;
- }
- if (set.DRAW_SELF != null) {
- this.settings.drawShape = set.DRAW_SELF;
- }
- if (set.DAMAGE_EFFECTS != null) {
- this.settings.damageEffects = set.DAMAGE_EFFECTS;
- }
- if (set.RATIO_EFFECTS != null) {
- this.settings.ratioEffects = set.RATIO_EFFECTS;
- }
- if (set.MOTION_EFFECTS != null) {
- this.settings.motionEffects = set.MOTION_EFFECTS;
- }
- if (set.ACCEPTS_SCORE != null) {
- this.settings.acceptsScore = set.ACCEPTS_SCORE;
- }
- if (set.GIVE_KILL_MESSAGE != null) {
- this.settings.givesKillMessage = set.GIVE_KILL_MESSAGE;
- }
- if (set.CAN_GO_OUTSIDE_ROOM != null) {
- this.settings.canGoOutsideRoom = set.CAN_GO_OUTSIDE_ROOM;
- }
- if (set.HITS_OWN_TYPE != null) {
- this.settings.hitsOwnType = set.HITS_OWN_TYPE;
- }
- if (set.DIE_AT_LOW_SPEED != null) {
- this.settings.diesAtLowSpeed = set.DIE_AT_LOW_SPEED;
- }
- if (set.DIE_AT_RANGE != null) {
- this.settings.diesAtRange = set.DIE_AT_RANGE;
- }
- if (set.INDEPENDENT != null) {
- this.settings.independent = set.INDEPENDENT;
- }
- if (set.PERSISTS_AFTER_DEATH != null) {
- this.settings.persistsAfterDeath = set.PERSISTS_AFTER_DEATH;
- }
- if (set.CLEAR_ON_MASTER_UPGRADE != null) {
- this.settings.clearOnMasterUpgrade = set.CLEAR_ON_MASTER_UPGRADE;
- }
- if (set.HEALTH_WITH_LEVEL != null) {
- this.settings.healthWithLevel = set.HEALTH_WITH_LEVEL;
- }
- if (set.ACCEPTS_SCORE != null) {
- this.settings.acceptsScore = set.ACCEPTS_SCORE;
- }
- if (set.OBSTACLE != null) {
- this.settings.obstacle = set.OBSTACLE;
- }
- if (set.NECRO != null) {
- this.settings.isNecromancer = set.NECRO;
- }
- if (set.AUTO_UPGRADE != null) {
- this.settings.upgrading = set.AUTO_UPGRADE;
- }
- if (set.HAS_NO_RECOIL != null) {
- this.settings.hasNoRecoil = set.HAS_NO_RECOIL;
- }
- if (set.CRAVES_ATTENTION != null) {
- this.settings.attentionCraver = set.CRAVES_ATTENTION;
- }
- if (set.BROADCAST_MESSAGE != null) {
- this.settings.broadcastMessage = set.BROADCAST_MESSAGE === '' ? undefined : set.BROADCAST_MESSAGE;
- }
- if (set.DAMAGE_CLASS != null) {
- this.settings.damageClass = set.DAMAGE_CLASS;
- }
- if (set.BUFF_VS_FOOD != null) {
- this.settings.buffVsFood = set.BUFF_VS_FOOD;
- }
- if (set.CAN_BE_ON_LEADERBOARD != null) {
- this.settings.leaderboardable = set.CAN_BE_ON_LEADERBOARD;
- }
- if (set.INTANGIBLE != null) {
- this.intangibility = set.INTANGIBLE;
- }
- if (set.IS_SMASHER != null) {
- this.settings.reloadToAcceleration = set.IS_SMASHER;
- }
- if (set.STAT_NAMES != null) {
- this.settings.skillNames = set.STAT_NAMES;
- }
- if (set.AI != null) {
- this.aiSettings = set.AI;
- }
- if (set.DANGER != null) {
- this.dangerValue = set.DANGER;
- }
- if (set.VARIES_IN_SIZE != null) {
- this.settings.variesInSize = set.VARIES_IN_SIZE;
- this.squiggle = this.settings.variesInSize ? ran.randomRange(0.8, 1.2) : 1;
- }
- if (set.RESET_UPGRADES) {
- this.upgrades = [];
- }
- if (set.ALPHA != null) this.alpha = set.ALPHA;
- if (set.INVISIBLE != null) this.invisible = [set.INVISIBLE[0], set.INVISIBLE[1]];
- if (set.UPGRADES_TIER_1 != null) {
- //set.UPGRADES_TIER_1.forEach((e) => {
- // this.upgrades.push({class: e, level: c.TIER_1, index: e.index});
- //});
- let i = 0;
- const length = set.UPGRADES_TIER_1.length;
- for (; i < length; i++) {
- this.upgrades.push({ class: set.UPGRADES_TIER_1[i], level: c.TIER_1, index: set.UPGRADES_TIER_1[i].index });
- }
- }
- if (set.UPGRADES_TIER_2 != null) {
- //set.UPGRADES_TIER_2.forEach((e) => {
- // this.upgrades.push({class: e, level: c.TIER_2, index: e.index});
- //});
- let i = 0;
- const length = set.UPGRADES_TIER_2.length;
- for (; i < length; i++) {
- this.upgrades.push({ class: set.UPGRADES_TIER_2[i], level: c.TIER_2, index: set.UPGRADES_TIER_2[i].index });
- }
- }
- if (set.UPGRADES_TIER_3 != null) {
- //set.UPGRADES_TIER_3.forEach((e) => {
- // this.upgrades.push({class: e, level: c.TIER_3, index: e.index});
- //});
- let i = 0;
- const length = set.UPGRADES_TIER_3.length;
- for (; i < length; i++) {
- this.upgrades.push({ class: set.UPGRADES_TIER_3[i], level: c.TIER_3, index: set.UPGRADES_TIER_3[i].index });
- }
- }
- if (set.SIZE != null) {
- this.SIZE = set.SIZE * this.squiggle;
- if (this.coreSize == null) {
- this.coreSize = this.SIZE;
- }
- }
- if (set.SKILL != null && set.SKILL != []) {
- if (set.SKILL.length != 10) {
- throw 'Inappropiate skill raws.';
- }
- this.skill.set(set.SKILL);
- }
- if (set.LEVEL != null) {
- if (set.LEVEL === -1) {
- this.skill.reset();
- }
- while (this.skill.level < c.SKILL_CHEAT_CAP && this.skill.level < set.LEVEL) {
- this.skill.score += this.skill.levelScore;
- this.skill.maintain();
- }
- this.refreshBodyAttributes();
- }
- if (set.SKILL_CAP != null && set.SKILL_CAP != []) {
- if (set.SKILL_CAP.length != 10) {
- throw 'Inappropiate skill caps.';
- }
- this.skill.setCaps(set.SKILL_CAP);
- }
- if (set.VALUE != null) {
- this.skill.score = Math.max(this.skill.score, set.VALUE * this.squiggle);
- }
- if (set.ALT_ABILITIES != null) {
- this.abilities = set.ALT_ABILITIES;
- }
- if (set.GUNS != null) {
- let newGuns = [];
- //set.GUNS.forEach((gundef) => {
- // newGuns.push(new Gun(this, gundef));
- //});
- let i = 0;
- const length = set.GUNS.length;
- for (; i < length; i++) {
- newGuns.push(new Gun(this, set.GUNS[i]));
- }
- this.guns = newGuns;
- }
- if (set.MAX_CHILDREN != null) {
- this.maxChildren = set.MAX_CHILDREN;
- }
- if (set.FOOD != null) {
- if (set.FOOD.LEVEL != null) {
- this.foodLevel = set.FOOD.LEVEL;
- this.foodCountup = 0;
- }
- }
- if (set.BODY != null) {
- if (set.BODY.ACCELERATION != null) {
- this.ACCELERATION = set.BODY.ACCELERATION;
- }
- if (set.BODY.SPEED != null) {
- this.SPEED = set.BODY.SPEED;
- }
- if (set.BODY.HEALTH != null) {
- this.HEALTH = set.BODY.HEALTH;
- }
- if (set.BODY.RESIST != null) {
- this.RESIST = set.BODY.RESIST;
- }
- if (set.BODY.SHIELD != null) {
- this.SHIELD = set.BODY.SHIELD;
- }
- if (set.BODY.REGEN != null) {
- this.REGEN = set.BODY.REGEN;
- }
- if (set.BODY.DAMAGE != null) {
- this.DAMAGE = set.BODY.DAMAGE;
- }
- if (set.BODY.PENETRATION != null) {
- this.PENETRATION = set.BODY.PENETRATION;
- }
- if (set.BODY.FOV != null) {
- this.FOV = set.BODY.FOV;
- }
- if (set.BODY.RANGE != null) {
- this.RANGE = set.BODY.RANGE;
- }
- if (set.BODY.SHOCK_ABSORB != null) {
- this.SHOCK_ABSORB = set.BODY.SHOCK_ABSORB;
- }
- if (set.BODY.DENSITY != null) {
- this.DENSITY = set.BODY.DENSITY;
- }
- if (set.BODY.STEALTH != null) {
- this.STEALTH = set.BODY.STEALTH;
- }
- if (set.BODY.PUSHABILITY != null) {
- this.PUSHABILITY = set.BODY.PUSHABILITY;
- }
- if (set.BODY.HETERO != null) {
- this.heteroMultiplier = set.BODY.HETERO;
- }
- this.refreshBodyAttributes();
- }
- if (set.TURRETS != null) {
- let o;
- let i = 0;
- let j = 0;
- const lengthi = this.turrets.length;
- const lengthj = set.TURRETS.length;
- //this.turrets.forEach(o => o.destroy());
- for (; i < lengthi; i++) {
- this.turrets[i].destroy();
- }
- this.turrets = [];
- for (; j < lengthj; j++) {
- let def = set.TURRETS[j];
- o = new Entity(this, this.master);
- (Array.isArray(def.TYPE) ? def.TYPE : [def.TYPE]).forEach(type => o.define(type));
- o.bindToMaster(def.POSITION, this);
- }
- }
- if (set.TRIGGERS != null && mockupDefine === false) {
- let entries = Object.entries(set.TRIGGERS);
- for (let i = 0; i < entries.length; i++) {
- let entry = entries[i];
- if (typeof entry[1] === 'function') {
- if (this.triggerFunctions[entry[0]] != null) {
- this.triggerFunctions[entry[0]] = entry[1];
- } else {
- util.error('Unknown trigger function ' + entry[0] + ' being defined by definition ' + set.LABEL);
- }
- } else {
- util.error('Trigger function ' + entry[0] + ' being defined by definition ' + set.LABEL + ' is not a function!');
- }
- // so if i had update or something thats setup,
- // now i can call that if i have a place for it like
- // inside the objects activation update the update trigger
- // function is called if its actually defined,
- // you could also split up update into two triggers,
- // AlwaysUpdate to always update even when the object died and
- // Update for when it isnt dead, though right now updates set to when
- // it isnt dead. anyway lets go to exports.explosion in definitions
- // to see these things in action!
- }
- }
- if (set.mockup != null) {
- this.mockup = set.mockup;
- }
- }
- refreshBodyAttributes() {
- if (this.cache === undefined) {
- this.cache = [0, 0, 0, 0, 0, 0];
- }
- if (this.defaultset === false || this.skill.changed === true) {
- let speedReduce = Math.pow(this.size / (this.coreSize || this.SIZE), 1);
- this.acceleration = c.runSpeed * this.ACCELERATION / speedReduce;
- if (this.settings.reloadToAcceleration) this.acceleration *= this.skill.acl;
- this.cache[0] = this.ACCELERATION;
- this.topSpeed = c.runSpeed * this.SPEED * this.skill.mob / speedReduce * 2.58853046592;
- if (this.settings.reloadToAcceleration) this.topSpeed /= Math.sqrt(this.skill.acl);
- this.cache[1] = this.SPEED;
- this.health.set(((this.settings.healthWithLevel ? 2 * this.skill.level : 0) + this.HEALTH) * this.skill.hlt);
- this.health.resist = 1 - 1 / Math.max(1, this.RESIST + this.skill.brst);
- this.shield.set(((this.settings.healthWithLevel ? 0.6 * this.skill.level : 0) + this.SHIELD) * this.skill.shi, Math.max(0, ((this.settings.healthWithLevel ? 0.006 * this.skill.level : 0) + 1) * this.REGEN * this.skill.rgn));
- this.damage = this.DAMAGE * this.skill.atk;
- this.penetration = this.PENETRATION + 1.5 * (this.skill.brst + 0.8 * (this.skill.atk - 1));
- if (!this.settings.dieAtRange || !this.range) {
- this.range = this.RANGE;
- }
- if (this.size < 12) {
- this.fov = this.FOV * 400 * Math.sqrt(1 / (0.1 * this.size)) * (0.9 + 0.0001 * (this.size / 4));
- } else {
- this.fov = this.FOV * 400 * Math.sqrt(1 * (0.2 * this.size)) * (0.9 + 0.0001 * (this.size / 4));
- }
- this.density = (1 + 0.08 * this.skill.level) * this.DENSITY;
- this.stealth = this.STEALTH;
- this.pushability = this.PUSHABILITY;
- this.defaultset = true;
- this.skill.changed = false;
- }
- /*
- let speedReduce = Math.pow(this.size / (this.coreSize || this.SIZE), 1);
- if (this.ACCELERATION != this.cache[0]) {
- this.acceleration = c.runSpeed * this.ACCELERATION / speedReduce;
- if (this.settings.reloadToAcceleration) this.acceleration *= this.skill.acl;
- this.cache[0] = this.ACCELERATION;
- }
- if (this.SPEED != this.cache[1]) {
- this.topSpeed = (c.runSpeed * this.SPEED * this.skill.mob / speedReduce) * 1.14866039425;
- if (this.settings.reloadToAcceleration) this.topSpeed /= Math.sqrt(this.skill.acl);
- this.cache[1] = this.SPEED;
- }
- */
- /*
- if (this.HEALTH != this.cache[2]) {
- this.health.set(
- (((this.settings.healthWithLevel) ? 2 * this.skill.level : 0) + this.HEALTH) * this.skill.hlt
- );
- this.health.resist = 1 - 1 / Math.max(1, this.RESIST + this.skill.brst);
- this.cache[2] = this.HEALTH;
- }
- if (this.SHIELD != this.cache[3]) {
- this.shield.set(
- (((this.settings.healthWithLevel) ? 0.6 * this.skill.level : 0) + this.SHIELD) * this.skill.shi,
- Math.max(0, ((((this.settings.healthWithLevel) ? 0.006 * this.skill.level : 0) + 1) * this.REGEN) * this.skill.rgn)
- );
- this.cache[3] = this.SHIELD;
- }
- if (this.skill.atk != this.cache[4]) {
- this.damage = this.DAMAGE * this.skill.atk;
- this.cache[4] = this.skill.atk;
- }
- if (this.skill.brst != this.cache[5]) {
- this.penetration = this.PENETRATION + 1.5 * (this.skill.brst + 0.8 * (this.skill.atk - 1));
- this.cache[5] = this.skill.brst;
- }
- if (!this.settings.dieAtRange || !this.range) {
- this.range = this.RANGE;
- }
- this.fov = this.FOV * 250 * Math.sqrt(this.size) * (1 + 0.003 * this.skill.level);
- this.density = (1 + 0.08 * this.skill.level) * this.DENSITY;
- this.stealth = this.STEALTH;
- this.pushability = this.PUSHABILITY;
- */
- }
- bindToMaster(position, bond) {
- this.bond = bond;
- this.source = bond;
- this.bond.turrets.push(this);
- this.skill = this.bond.skill;
- this.label = this.bond.label + ' ' + this.label;
- // It will not be in collision calculations any more nor shall it be seen.
- this.removeFromGrid();
- this.settings.drawShape = false;
- // Get my position.
- this.bound = {};
- this.bound.size = position[0] / 20;
- let _off = new Vector(position[1], position[2]);
- this.bound.angle = position[3] * Math.PI / 180;
- this.bound.direction = _off.direction;
- this.bound.offset = _off.length / 10;
- this.bound.arc = position[4] * Math.PI / 180;
- // Figure out how we'll be drawn.
- this.bound.layer = position[5];
- // Initalize.
- this.facing = this.bond.facing + this.bound.angle;
- this.facingType = 'bound';
- this.motionType = 'bound';
- this.move();
- }
- get size() {
- if (this.bond == null) return (this.coreSize || this.SIZE) * (1 + this.skill.level / 45);
- return this.bond.size * this.bound.size;
- }
- get mass() {
- return this.density * (this.size * this.size + 1);
- }
- get realSize() {
- if (Array.isArray(this.shape)) {
- return this.size * (Math.abs(1) > lazyRealSizes.length ? 1 : lazyRealSizes[Math.abs(1)]);
- } else {
- return this.size * (Math.abs(this.shape) > lazyRealSizes.length ? 1 : lazyRealSizes[Math.abs(this.shape)]);
- }
- }
- get m_x() {
- return (this.velocity.x + this.accel.x) / roomSpeed;
- }
- get m_y() {
- return (this.velocity.y + this.accel.y) / roomSpeed;
- }
- camera(tur = false) {
- return {
- type: 0 + tur * 0x01 + this.settings.drawHealth * 0x02 + (this.type === 'tank') * 0x04,
- id: this.id,
- index: this.index,
- x: this.x,
- y: this.y,
- vx: this.velocity.x,
- vy: this.velocity.y,
- size: this.size,
- rsize: this.realSize,
- status: 1,
- health: this.health.display(),
- shield: this.shield.display(),
- facing: this.facing,
- vfacing: this.vfacing,
- twiggle: this.facingType === 'autospin' || this.facingType === 'locksFacing' && this.control.alt,
- layer: this.bond != null ? this.bound.layer : this.type === 'wall' ? 11 : this.type === 'food' ? 10 : this.type === 'tank' ? 5 : this.type === 'crasher' ? 1 : 0,
- color: this.color,
- name: this.name,
- score: this.skill.score,
- guns: this.guns.map(gun => gun.getLastShot()),
- turrets: this.turrets.map(turret => turret.camera(true)),
- alpha: this.alpha
- };
- }
- skillUp(stat) {
- let suc = this.skill.upgrade(stat);
- if (suc) {
- this.refreshBodyAttributes();
- //this.guns.forEach(function(gun) {
- // gun.syncChildren();
- //});
- let i = 0;
- const length = this.guns.length;
- for (; i < length; i++) {
- this.guns[i].syncChildren();
- }
- }
- return suc;
- }
- upgrade(number) {
- if (number < this.upgrades.length && this.skill.level >= this.upgrades[number].level) {
- let saveMe = this.upgrades[number].class;
- this.upgrades = [];
- this.define(saveMe);
- this.sendMessage('You have upgraded to ' + this.label + '.');
- let ID = this.id;
- //entities.forEach(instance => {
- // if (instance.settings.clearOnMasterUpgrade && instance.master.id === ID) {
- // instance.kill();
- // }
- //});
- let i = 0;
- const length = entities.length;
- for (; i < length; i++) {
- if (entities[i].settings.clearOnMasterUpgrade && entities[i].master.id === ID) {
- entities[i].kill();
- }
- }
- this.skill.update();
- this.skill.changed = true;
- this.refreshBodyAttributes();
- }
- }
- damageMultiplier() {
- switch (this.type) {
- case 'swarm':
- return 0.25 + 1.5 * util.clamp(this.range / (this.RANGE + 1), 0, 1);
- default:
- return 1;
- }
- }
- move() {
- let g = {
- x: this.control.goal.x - this.x,
- y: this.control.goal.y - this.y
- },
- gactive = g.x !== 0 || g.y !== 0,
- engine = {
- x: 0,
- y: 0
- },
- a = this.acceleration / roomSpeed;
- switch (this.motionType) {
- case 'glide':
- this.maxSpeed = this.topSpeed;
- this.damp = 0.05;
- break;
- case 'motor':
- this.maxSpeed = 0;
- if (this.topSpeed) {
- this.damp = a / this.topSpeed;
- }
- if (gactive) {
- let len = Math.sqrt(g.x * g.x + g.y * g.y);
- engine = {
- x: a * g.x / len,
- y: a * g.y / len
- };
- }
- break;
- case 'swarm':
- this.maxSpeed = this.topSpeed;
- let l = util.getDistance({ x: 0, y: 0 }, g) + 1;
- if (gactive && l > this.size) {
- let desiredxspeed = this.topSpeed * g.x / l,
- desiredyspeed = this.topSpeed * g.y / l,
- turning = Math.sqrt((this.topSpeed * Math.max(1, this.range) + 1) / a);
- engine = {
- x: (desiredxspeed - this.velocity.x) / Math.max(5, turning) * 1.2145,
- y: (desiredyspeed - this.velocity.y) / Math.max(5, turning) * 1.2145
- };
- } else {
- if (this.velocity.length < this.topSpeed) {
- engine = {
- x: this.velocity.x * a / 40,
- y: this.velocity.y * a / 40
- };
- }
- }
- break;
- case 'chase':
- if (gactive) {
- let l = util.getDistance({ x: 0, y: 0 }, g);
- if (l > this.size * 2) {
- this.maxSpeed = this.topSpeed;
- let desiredxspeed = this.topSpeed * g.x / l,
- desiredyspeed = this.topSpeed * g.y / l;
- engine = {
- x: (desiredxspeed - this.velocity.x) * a,
- y: (desiredyspeed - this.velocity.y) * a
- };
- } else {
- this.maxSpeed = 0;
- }
- } else {
- this.maxSpeed = 0;
- }
- break;
- case 'drift':
- this.maxSpeed = 0;
- engine = {
- x: g.x * a,
- y: g.y * a
- };
- break;
- case 'bound':
- let bound = this.bound,
- ref = this.bond;
- this.x = ref.x + ref.size * bound.offset * Math.cos(bound.direction + bound.angle + ref.facing);
- this.y = ref.y + ref.size * bound.offset * Math.sin(bound.direction + bound.angle + ref.facing);
- this.bond.velocity.x += bound.size * this.accel.x;
- this.bond.velocity.y += bound.size * this.accel.y;
- this.firingArc = [ref.facing + bound.angle, bound.arc / 2];
- nullVector(this.accel);
- this.blend = ref.blend;
- break;
- }
- this.accel.x += engine.x * this.control.power;
- this.accel.y += engine.y * this.control.power;
- }
- face() {
- let t = this.control.target,
- tactive = t.x !== 0 || t.y !== 0,
- oldFacing = this.facing;
- switch (this.facingType) {
- case 'autospin':
- this.facing += 0.02 / roomSpeed;
- break;
- case 'turnWithSpeed':
- this.facing += this.velocity.length / 90 * Math.PI / roomSpeed;
- break;
- case 'withMotion':
- this.facing = this.velocity.direction;
- break;
- case 'smoothWithMotion':
- case 'looseWithMotion':
- this.facing += util.loopSmooth(this.facing, this.velocity.direction, 4 / roomSpeed);
- break;
- case 'withTarget':
- case 'toTarget':
- this.facing = Math.atan2(t.y, t.x);
- break;
- case 'locksFacing':
- if (!this.control.alt) this.facing = Math.atan2(t.y, t.x);
- break;
- case 'looseWithTarget':
- case 'looseToTarget':
- case 'smoothToTarget':
- this.facing += util.loopSmooth(this.facing, Math.atan2(t.y, t.x), 1 / roomSpeed);
- break;
- case 'bound':
- let givenangle;
- if (this.control.main) {
- givenangle = Math.atan2(t.y, t.x);
- let diff = util.angleDifference(givenangle, this.firingArc[0]);
- if (Math.abs(diff) >= this.firingArc[1]) {
- givenangle = this.firingArc[0]; // - util.clamp(Math.sign(diff), -this.firingArc[1], this.firingArc[1]);
- }
- } else {
- givenangle = this.firingArc[0];
- }
- this.facing += util.loopSmooth(this.facing, givenangle, 4 / roomSpeed);
- break;
- }
- // Loop
- while (this.facing < 0) {
- this.facing += 2 * Math.PI;
- }
- while (this.facing > 2 * Math.PI) {
- this.facing -= 2 * Math.PI;
- }
- this.vfacing = util.angleDifference(oldFacing, this.facing) * roomSpeed;
- }
- takeSelfie() {
- this.flattenedPhoto = null;
- this.photo = this.settings.drawShape ? this.camera() : this.photo = undefined;
- }
- physics() {
- if (this.accel.x == null || this.velocity.x == null) {
- util.error('Void Error!');
- util.error(this.collisionArray);
- util.error(this.label);
- util.error(this);
- nullVector(this.accel);nullVector(this.velocity);
- }
- // Apply acceleration
- this.velocity.x += this.accel.x * timestep;
- this.velocity.y += this.accel.y * timestep;
- // Reset acceleration
- //if (!this.accel <= 0) {
- nullVector(this.accel);
- //}
- // Apply motion
- this.stepRemaining = 1;
- this.x += this.stepRemaining * this.velocity.x / roomSpeed;
- this.y += this.stepRemaining * this.velocity.y / roomSpeed;
- }
- friction() {
- var motion = this.velocity.length,
- excess = motion - this.maxSpeed;
- if (excess > 0 && this.damp) {
- var k = this.damp * timestep * 0.97,
- drag = excess / (k + 1),
- finalVelocity = this.maxSpeed + drag;
- this.velocity.x = finalVelocity * this.velocity.x / motion * 0.97;
- this.velocity.y = finalVelocity * this.velocity.y / motion * 0.97;
- }
- }
- confinementToTheseEarthlyShackles() {
- if (this.x == null || this.x == null) {
- util.error('Void Error!');
- util.error(this.collisionArray);
- util.error(this.label);
- util.error(this);
- nullVector(this.accel);nullVector(this.velocity);
- return 0;
- }
- if (!this.settings.canGoOutsideRoom) {
- this.accel.x -= Math.min(this.x - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; //roomSpeed for next 3
- this.accel.x -= Math.max(this.x + this.realSize - room.width - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
- this.accel.y -= Math.min(this.y - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
- this.accel.y -= Math.max(this.y + this.realSize - room.height - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
- }
- if (room.gameMode === 'tdm' && this.type !== 'food') {
- let loc = { x: this.x, y: this.y };
- if (this.team !== -1 && room.isIn('bas1', loc) || this.team !== -2 && room.isIn('bas2', loc) || this.team !== -3 && room.isIn('bas3', loc) || this.team !== -4 && room.isIn('bas4', loc) || this.team !== -5 && room.isIn('bas5', loc) || this.team !== -6 && room.isIn('bas6', loc)) {
- this.kill();
- }
- }
- }
- contemplationOfMortality() {
- if (this.invuln) {
- this.damageRecieved = 0;
- return 0;
- }
- // Life-limiting effects
- if (this.settings.diesAtRange) {
- this.range -= 1 / roomSpeed;
- if (this.range < 0) {
- this.kill();
- }
- }
- if (this.settings.diesAtLowSpeed) {
- if (!this.collisionArray.length && this.velocity.length < this.topSpeed / 2) {
- this.health.amount -= this.health.getDamage(1 / roomSpeed);
- }
- }
- // Shield regen and damage
- if (this.shield.max) {
- if (this.damageRecieved !== 0) {
- let shieldDamage = this.shield.getDamage(this.damageRecieved);
- this.damageRecieved -= shieldDamage;
- this.shield.amount -= shieldDamage;
- }
- }
- // Health damage
- if (this.damageRecieved !== 0) {
- let healthDamage = this.health.getDamage(this.damageRecieved);
- this.blend.amount = 1;
- this.health.amount -= healthDamage;
- }
- this.damageRecieved = 0;
- // Check for death
- if (this.isDead()) {
- // Initalize message arrays
- let killers = [],
- killTools = [],
- notJustFood = false;
- // If I'm a tank, call me a nameless player
- let name = this.master.name == '' ? this.master.type === 'tank' ? "a nameless player's " + this.label : this.master.type === 'miniboss' ? "a visiting " + this.label : util.addArticle(this.label) : this.master.name + "'s " + this.label;
- // Calculate the jackpot
- let jackpot = Math.ceil(util.getJackpot(this.skill.score) / this.collisionArray.length);
- // Now for each of the things that kill me...
- /*
- this.collisionArray.forEach(instance => {
- if (instance.type === 'wall') return 0;
- if (instance.master.settings.acceptsScore) { // If it's not food, give its master the score
- if (instance.master.type === 'tank' || instance.master.type === 'miniboss') notJustFood = true;
- instance.master.skill.score += jackpot;
- killers.push(instance.master); // And keep track of who killed me
- } else if (instance.settings.acceptsScore) {
- instance.skill.score += jackpot;
- }
- killTools.push(instance); // Keep track of what actually killed me
- });
- */
- let i = 0;
- const length = this.collisionArray.length;
- for (; i < length; i++) {
- //for (let instance of this.collisionArray) {
- let instance = this.collisionArray[i];
- if (instance.type === 'wall') return 0;
- if (instance.master.settings.acceptsScore) {
- // If it's not food, give its master the score
- if (instance.master.type === 'tank' || instance.master.type === 'miniboss') notJustFood = true;
- instance.master.skill.score += jackpot;
- killers.push(instance.master); // And keep track of who killed me
- } else if (instance.settings.acceptsScore) {
- instance.skill.score += jackpot;
- }
- killTools.push(instance); // Keep track of what actually killed me
- }
- // Remove duplicates
- killers = killers.filter((elem, index, self) => {
- return index == self.indexOf(elem);
- });
- // If there's no valid killers (you were killed by food), change the message to be more passive
- let killText = notJustFood ? '' : "You have been killed by ",
- dothISendAText = this.settings.givesKillMessage;
- killers.forEach(instance => {
- this.killCount.killers.push(instance.index);
- if (this.type === 'tank') {
- if (killers.length > 1) instance.killCount.assists++;else instance.killCount.solo++;
- } else if (this.type === "miniboss") instance.killCount.bosses++;
- });
- // Add the killers to our death message, also send them a message
- if (notJustFood) {
- killers.forEach(instance => {
- if (instance.master.type !== 'food' && instance.master.type !== 'crasher') {
- killText += instance.name == '' ? killText == '' ? 'An unnamed player' : 'an unnamed player' : instance.name;
- killText += ' and ';
- }
- // Only if we give messages
- if (dothISendAText) {
- instance.sendMessage('You killed ' + name + (killers.length > 1 ? ' (with some help).' : '.'));
- }
- });
- // Prepare the next part of the next
- killText = killText.slice(0, -4);
- killText += 'killed you with ';
- }
- // Broadcast
- if (this.settings.broadcastMessage) sockets.broadcast(this.settings.broadcastMessage);
- // Add the implements to the message
- killTools.forEach(instance => {
- killText += util.addArticle(instance.label) + ' and ';
- });
- // Prepare it and clear the collision array.
- killText = killText.slice(0, -5);
- if (killText === 'You have been kille') killText = 'You have died a stupid death';
- this.sendMessage(killText + '.');
- // If I'm the leader, broadcast it:
- if (this.id === room.topPlayerID) {
- let usurptText = this.name === '' ? 'The leader' : this.name;
- if (notJustFood) {
- usurptText += ' has been usurped by';
- killers.forEach(instance => {
- usurptText += ' ';
- usurptText += instance.name === '' ? 'an unnamed player' : instance.name;
- usurptText += ' and';
- });
- usurptText = usurptText.slice(0, -4);
- usurptText += '!';
- } else {
- usurptText += ' fought a polygon... and the polygon won.';
- }
- sockets.broadcast(usurptText);
- }
- // Kill it
- return 1;
- }
- return 0;
- }
- protect() {
- entitiesToAvoid.push(this);this.isProtected = true;
- }
- sendMessage(message) {} // Dummy
- kill() {
- this.health.amount = -1;
- }
- spawn(...args) {
- let e = new Entity(...args);
- return e;
- }
- destroy() {
- if (this.triggerFunctions.DEATH != null) {
- this.triggerFunctions.DEATH(this);
- }
- // Remove from the protected entities list
- if (this.isProtected) util.remove(entitiesToAvoid, entitiesToAvoid.indexOf(this));
- // Remove from minimap
- let i = minimap.findIndex(entry => {
- return entry[0] === this.id;
- });
- if (i != -1) util.remove(minimap, i);
- // Remove this from views
- //views.forEach(v => v.remove(this));
- let v = 0;
- const length = views.length;
- for (; v < length; v++) {
- views[v].remove(this);
- }
- // Remove from parent lists if needed
- if (this.parent != null) util.remove(this.parent.children, this.parent.children.indexOf(this));
- // Kill all of its children
- let ID = this.id;
- let instance = entities.find(o => o.source.id === this.id);
- if (instance !== undefined) {
- if (instance.source.id === this.id) {
- if (instance.settings.persistsAfterDeath) {
- instance.source = instance;
- } else {
- instance.kill();
- }
- }
- if (instance.parent && instance.parent.id === this.id) {
- instance.parent = null;
- }
- if (instance.master.id === this.id) {
- instance.kill();
- instance.master = instance;
- }
- }
- // Remove everything bound to it
- if (this.turrets.length > 0) {
- for (let t of this.turrets) {
- t.destroy();
- }
- }
- // Remove from the collision grid
- this.removeFromGrid();
- this.isGhost = true;
- }
- isDead() {
- return this.health.amount <= 0;
- }
- }
- /*** SERVER SETUP ***/
- // Make a speed monitor
- var logs = (() => {
- let logger = (() => {
- // The two basic functions
- function set(obj) {
- obj.time = util.time();
- }
- function mark(obj) {
- obj.data.push(util.time() - obj.time);
- }
- function record(obj) {
- let o = util.averageArray(obj.data);
- obj.data = [];
- return o;
- }
- function sum(obj) {
- let o = util.sumArray(obj.data);
- obj.data = [];
- return o;
- }
- function tally(obj) {
- obj.count++;
- }
- function count(obj) {
- let o = obj.count;
- obj.count = 0;
- return o;
- }
- // Return the logger creator
- return () => {
- let internal = {
- data: [],
- time: util.time(),
- count: 0
- };
- // Return the new logger
- return {
- set: () => set(internal),
- mark: () => mark(internal),
- record: () => record(internal),
- sum: () => sum(internal),
- count: () => count(internal),
- tally: () => tally(internal)
- };
- };
- })();
- // Return our loggers
- return {
- entities: logger(),
- collide: logger(),
- network: logger(),
- minimap: logger(),
- misc2: logger(),
- misc3: logger(),
- physics: logger(),
- life: logger(),
- selfie: logger(),
- master: logger(),
- activation: logger(),
- loops: logger()
- };
- })();
- // Essential server requires
- var express = require('express'),
- http = require('http'),
- url = require('url'),
- WebSocket = require('ws'),
- app = express(),
- fs = require('fs'),
- exportDefinitionsToClient = (() => {
- function rounder(val) {
- if (Math.abs(val) < 0.00001) val = 0;
- return +val.toPrecision(6);
- }
- // Define mocking up functions
- function getMockup(e, positionInfo) {
- return {
- index: e.index,
- name: e.label,
- x: rounder(e.x),
- y: rounder(e.y),
- color: e.color,
- shape: e.shapeData,
- //customshape: e.customshape,
- size: rounder(e.size),
- realSize: rounder(e.realSize),
- facing: rounder(e.facing),
- layer: e.layer,
- statnames: e.settings.skillNames,
- position: positionInfo,
- guns: e.guns.map(function (gun) {
- return {
- offset: rounder(gun.offset),
- direction: rounder(gun.direction),
- length: rounder(gun.length),
- width: rounder(gun.width),
- aspect: rounder(gun.aspect),
- angle: rounder(gun.angle)
- };
- }),
- turrets: e.turrets.map(function (t) {
- let out = getMockup(t, {});
- out.sizeFactor = rounder(t.bound.size);
- out.offset = rounder(t.bound.offset);
- out.direction = rounder(t.bound.direction);
- out.layer = rounder(t.bound.layer);
- out.angle = rounder(t.bound.angle);
- return out;
- })
- };
- }
- function getDimensions(entities) {
- /* Ritter's Algorithm (Okay it got serious modified for how we start it)
- * 1) Add all the ends of the guns to our list of points needed to be bounded and a couple points for the body of the tank..
- */
- let endpoints = [];
- let pointDisplay = [];
- let pushEndpoints = function (model, scale, focus = { x: 0, y: 0 }, rot = 0) {
- let s = Math.abs(model.shape);
- let z = Math.abs(s) > lazyRealSizes.length ? 1 : lazyRealSizes[Math.abs(s)];
- if (z === 1) {
- // Body (octagon if circle)
- for (let i = 0; i < 2; i += 0.5) {
- endpoints.push({ x: focus.x + scale * Math.cos(i * Math.PI), y: focus.y + scale * Math.sin(i * Math.PI) });
- }
- } else {
- // Body (otherwise vertices)
- for (let i = s % 2 ? 0 : Math.PI / s; i < s; i++) {
- let theta = i / s * 2 * Math.PI;
- endpoints.push({ x: focus.x + scale * z * Math.cos(theta), y: focus.y + scale * z * Math.sin(theta) });
- }
- }
- function makeGunModel(gun) {
- let h = gun.aspect > 0 ? scale * gun.width / 2 * gun.aspect : scale * gun.width / 2;
- let r = Math.atan2(h, scale * gun.length) + rot;
- let l = Math.sqrt(scale * scale * gun.length * gun.length + h * h);
- let x = focus.x + scale * gun.offset * Math.cos(gun.direction + gun.angle + rot);
- let y = focus.y + scale * gun.offset * Math.sin(gun.direction + gun.angle + rot);
- endpoints.push({
- x: x + l * Math.cos(gun.angle + r),
- y: y + l * Math.sin(gun.angle + r)
- });
- endpoints.push({
- x: x + l * Math.cos(gun.angle - r),
- y: y + l * Math.sin(gun.angle - r)
- });
- pointDisplay.push({
- x: x + l * Math.cos(gun.angle + r),
- y: y + l * Math.sin(gun.angle + r)
- });
- pointDisplay.push({
- x: x + l * Math.cos(gun.angle - r),
- y: y + l * Math.sin(gun.angle - r)
- });
- }
- //model.guns.forEach();
- for (let gun of model.guns) {
- makeGunModel(gun);
- }
- function makeTurretModel(turret) {
- pushEndpoints(turret, turret.bound.size, { x: turret.bound.offset * Math.cos(turret.bound.angle), y: turret.bound.offset * Math.sin(turret.bound.angle) }, turret.bound.angle);
- }
- //model.turrets.forEach();
- for (let turret of model.turrets) {
- makeTurretModel(turret);
- }
- };
- pushEndpoints(entities, 1);
- // 2) Find their mass center
- let massCenter = { x: 0, y: 0 };
- /*endpoints.forEach(function(point) {
- massCenter.x += point.x;
- massCenter.y += point.y;
- });
- massCenter.x /= endpoints.length;
- massCenter.y /= endpoints.length;*/
- // 3) Choose three different points (hopefully ones very far from each other)
- let chooseFurthestAndRemove = function (furthestFrom) {
- let index = 0;
- if (furthestFrom != -1) {
- let list = new goog.structs.PriorityQueue();
- let d;
- for (let i = 0; i < endpoints.length; i++) {
- let thisPoint = endpoints[i];
- d = Math.pow(thisPoint.x - furthestFrom.x, 2) + Math.pow(thisPoint.y - furthestFrom.y, 2) + 1;
- list.enqueue(1 / d, i);
- }
- index = list.dequeue();
- }
- let output = endpoints[index];
- endpoints.splice(index, 1);
- return output;
- };
- let point1 = chooseFurthestAndRemove(massCenter); // Choose the point furthest from the mass center
- let point2 = chooseFurthestAndRemove(point1); // And the point furthest from that
- // And the point which maximizes the area of our triangle (a loose look at this one)
- let chooseBiggestTriangleAndRemove = function (point1, point2) {
- let list = new goog.structs.PriorityQueue();
- let index = 0;
- let a;
- for (let i = 0; i < endpoints.length; i++) {
- let thisPoint = endpoints[i];
- a = Math.pow(thisPoint.x - point1.x, 2) + Math.pow(thisPoint.y - point1.y, 2) + Math.pow(thisPoint.x - point2.x, 2) + Math.pow(thisPoint.y - point2.y, 2);
- /* We need neither to calculate the last part of the triangle
- * (because it's always the same) nor divide by 2 to get the
- * actual area (because we're just comparing it)
- */
- list.enqueue(1 / a, i);
- }
- index = list.dequeue();
- let output = endpoints[index];
- endpoints.splice(index, 1);
- return output;
- };
- let point3 = chooseBiggestTriangleAndRemove(point1, point2);
- // 4) Define our first enclosing circle as the one which seperates these three furthest points
- function circleOfThreePoints(p1, p2, p3) {
- let x1 = p1.x;
- let y1 = p1.y;
- let x2 = p2.x;
- let y2 = p2.y;
- let x3 = p3.x;
- let y3 = p3.y;
- let denom = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
- let xy1 = x1 * x1 + y1 * y1;
- let xy2 = x2 * x2 + y2 * y2;
- let xy3 = x3 * x3 + y3 * y3;
- let x = ( // Numerator
- xy1 * (y2 - y3) + xy2 * (y3 - y1) + xy3 * (y1 - y2)) / (2 * denom);
- let y = ( // Numerator
- xy1 * (x3 - x2) + xy2 * (x1 - x3) + xy3 * (x2 - x1)) / (2 * denom);
- let r = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
- let r2 = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2));
- let r3 = Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2));
- if (r != r2 || r != r3) {
- //util.log('somethings fucky');
- }
- return { x: x, y: y, radius: r };
- }
- let c = circleOfThreePoints(point1, point2, point3);
- pointDisplay = [{ x: rounder(point1.x), y: rounder(point1.y) }, { x: rounder(point2.x), y: rounder(point2.y) }, { x: rounder(point3.x), y: rounder(point3.y) }];
- let centerOfCircle = { x: c.x, y: c.y };
- let radiusOfCircle = c.radius;
- // 5) Check to see if we enclosed everything
- function checkingFunction() {
- for (var i = endpoints.length; i > 0; i--) {
- // Select the one furthest from the center of our circle and remove it
- point1 = chooseFurthestAndRemove(centerOfCircle);
- let vectorFromPointToCircleCenter = new Vector(centerOfCircle.x - point1.x, centerOfCircle.y - point1.y);
- // 6) If we're still outside of this circle build a new circle which encloses the old circle and the new point
- if (vectorFromPointToCircleCenter.length > radiusOfCircle) {
- pointDisplay.push({ x: rounder(point1.x), y: rounder(point1.y) });
- // Define our new point as the far side of the cirle
- let dir = vectorFromPointToCircleCenter.direction;
- point2 = {
- x: centerOfCircle.x + radiusOfCircle * Math.cos(dir),
- y: centerOfCircle.y + radiusOfCircle * Math.sin(dir)
- };
- break;
- }
- }
- // False if we checked everything, true if we didn't
- return !!endpoints.length;
- }
- while (checkingFunction()) {
- // 7) Repeat until we enclose everything
- centerOfCircle = {
- x: (point1.x + point2.x) / 2,
- y: (point1.y + point2.y) / 2
- };
- radiusOfCircle = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)) / 2;
- }
- // 8) Since this algorithm isn't perfect but we know our shapes are bilaterally symmetrical, we bind this circle along the x-axis to make it behave better
- return {
- middle: { x: rounder(centerOfCircle.x), y: 0 },
- axis: rounder(radiusOfCircle * 2),
- points: pointDisplay
- };
- }
- // Save them
- let mockupData = [];
- for (let k in Class) {
- try {
- if (!Class.hasOwnProperty(k)) continue;
- let type = Class[k];
- // Create a reference entities which we'll then take an image of.
- let temptank = new Entity({ x: 0, y: 0 });
- temptank.define(type, true);
- temptank.name = type.LABEL; // Rename it (for the upgrades menu).
- // Fetch the mockup.
- type.mockup = {
- body: temptank.camera(true),
- position: getDimensions(temptank)
- };
- // This is to pass the size information about the mockup that we didn't have until we created the mockup
- type.mockup.body.position = type.mockup.position;
- // Add the new data to the thing.
- mockupData.push(getMockup(temptank, type.mockup.position));
- // Kill the reference entities.
- temptank.destroy();
- } catch (error) {
- util.error(error);
- util.error(k);
- util.error(Class[k]);
- }
- }
- // Remove them
- purgeEntities();
- // Build the function to return
- let writeData = JSON.stringify(mockupData);
- return loc => {
- util.log('Preparing definition export.');
- fs.writeFileSync(loc, writeData, 'utf8', err => {
- if (err) return util.error(err);
- });
- util.log('Mockups written to ' + loc + '!');
- };
- })(),
- generateVersionControlHash = (() => {
- let crypto = require('crypto');
- let write = (() => {
- let hash = [null, null];
- return (loc, data, numb) => {
- // The callback is executed on reading completion
- hash[numb] = crypto.createHash('sha1').update(data).digest('hex');
- if (hash[0] && hash[1]) {
- let finalHash = hash[0] + hash[1];
- util.log('Client hash generated. Hash is "' + finalHash + '".');
- // Write the hash to a place the client can read it.
- fs.writeFileSync(loc, finalHash, 'utf8', err => {
- if (err) return util.error(err);
- });
- util.log('Hash written to ' + loc + '!');
- }
- };
- })();
- return loc => {
- let path1 = __dirname + '/../client/js/app.js';
- let path2 = __dirname + '/lib/definitions.js';
- util.log('Building client version hash, reading from ' + path1 + ' and ' + path2 + '...');
- // Read the client application
- fs.readFile(path1, 'utf8', (err, data) => {
- if (err) return util.error(err);
- util.log('app.js complete.');
- write(loc, data, 0);
- });
- fs.readFile(path2, 'utf8', (err, data) => {
- if (err) return util.error(err);
- util.log('definitions.js complete.');
- write(loc, data, 1);
- });
- };
- })();
- // Give the client upon request
- exportDefinitionsToClient(__dirname + '/../client/json/mockups.json');
- generateVersionControlHash(__dirname + '/../client/api/vhash');
- if (c.servesStatic) app.use(express.static(__dirname + '/../client'));
- // Websocket behavior
- const sockets = (() => {
- const protocol = require('./lib/fasttalk');
- let clients = [],
- players = [],
- bannedIPs = [],
- suspiciousIPs = [],
- connectedIPs = [],
- bannedNames = ['FREE_FOOD_LUCARIO', 'FREE FOOD'];
- return {
- broadcast: message => {
- for (let socket of clients) {
- socket.talk('m', message);
- };
- },
- connect: (() => {
- // Define shared functions
- // Closing the socket
- function close(socket) {
- // Free the IP
- let n = connectedIPs.findIndex(w => {
- return w.ip === socket.ip;
- });
- if (n !== -1) {
- util.log(socket.ip + " disconnected.");
- util.remove(connectedIPs, n);
- }
- // Free the token
- if (socket.key != '') {
- keys.push(socket.key);
- util.log("Token freed.");
- }
- // Figure out who the player was
- let player = socket.player,
- index = players.indexOf(player);
- // Remove the player if one was created
- if (index != -1) {
- // Kill the body if it exists
- if (player.body != null) {
- player.body.invuln = false;
- timer.setTimeout(() => {
- player.body.kill();
- }, '', '10000m');
- }
- // Disconnect everything
- util.log('[INFO] User ' + player.name + ' disconnected!');
- util.remove(players, index);
- } else {
- util.log('[INFO] A player disconnected before entering the game.');
- }
- // Free the view
- util.remove(views, views.indexOf(socket.view));
- // Remove the socket
- util.remove(clients, clients.indexOf(socket));
- util.log('[INFO] Socket closed. Views: ' + views.length + '. Clients: ' + clients.length + '.');
- }
- // Banning
- function ban(socket) {
- if (bannedIPs.findIndex(ip => {
- return ip === socket.ip;
- }) === -1) {
- bannedIPs.push(socket.ip);
- } // No need for duplicates
- socket.terminate();
- util.warn(socket.ip + ' banned!');
- }
- // Being kicked
- function kick(socket, reason = 'No reason given.') {
- let n = suspiciousIPs.findIndex(n => {
- return n.ip === socket.ip;
- });
- if (n === -1) {
- suspiciousIPs.push({ ip: socket.ip, warns: 1 });
- util.warn(reason + ' Kicking. 1 warning.');
- } else {
- suspiciousIPs[n].warns++;
- util.warn(reason + ' Kicking. ' + suspiciousIPs[n].warns + ' warnings.');
- if (suspiciousIPs[n].warns >= c.socketWarningLimit) {
- ban(socket);
- }
- }
- socket.lastWords('K');
- }
- // Handle incoming messages
- function incoming(message, socket) {
- // Only accept binary
- if (!(message instanceof ArrayBuffer)) {
- socket.kick('Non-binary packet.');return 1;
- }
- // Decode it
- let m = protocol.decode(message);
- // Make sure it looks legit
- if (m === -1) {
- socket.kick('Malformed packet.');return 1;
- }
- // Log the message request
- socket.status.requests++;
- // Remember who we are
- let player = socket.player;
- // Handle the request
- switch (m.shift()) {
- case 'k':
- {
- // key verification
- //if (m.length !== 1) { socket.kick('Ill-sized key request.'); return 1; }
- // Get data
- let key = m[0];
- // Verify it
- if (typeof key !== 'string') {
- socket.kick('Weird key offered.');return 1;
- }
- if (key.length > 64) {
- socket.kick('Overly-long key offered.');return 1;
- }
- if (socket.status.verified) {
- socket.kick('Duplicate player spawn attempt.');return 1;
- }
- // Otherwise proceed to check if it's available.
- if (keys.indexOf(key) != -1) {
- // Save the key
- socket.key = key.substr(0, 64);
- // Make it unavailable
- util.remove(keys, keys.indexOf(key));
- socket.verified = true;
- // Proceed
- socket.talk('w', true);
- util.log('[INFO] A socket was verified with the token: ');util.log(key);
- util.log('Clients: ' + clients.length);
- } else {
- // If not, kick 'em (nicely)
- if (c.tokenReq == true) {
- util.log('[INFO] Invalid player verification attempt.');
- socket.lastWords('w', false);
- } else {
- socket.talk('w', true);
- util.log('[INFO] A socket was verified with no token');
- util.log('Clients: ' + clients.length);
- }
- }
- }break;
- case 's':
- {
- // spawn request
- if (!socket.status.deceased) {
- socket.kick('Trying to spawn while already alive.');return 1;
- }
- if (m.length !== 2) {
- socket.kick('Ill-sized spawn request.');return 1;
- }
- // Get data
- let name = m[0].replace(c.BANNED_CHARACTERS_REGEX, '');
- let needsRoom = m[1];
- // Verify it
- if (typeof name != 'string') {
- socket.kick('Bad spawn request.');return 1;
- }
- if (encodeURI(name).split(/%..|./).length > 48) {
- socket.kick('Overly-long name.');return 1;
- }
- if (needsRoom !== 0 && needsRoom !== 1) {
- socket.kick('Bad spawn request.');return 1;
- }
- // Bring to life
- socket.status.deceased = false;
- // Define the player.
- if (players.indexOf(socket.player) != -1) {
- util.remove(players, players.indexOf(socket.player));
- }
- // Free the old view
- if (views.indexOf(socket.view) != -1) {
- util.remove(views, views.indexOf(socket.view));socket.makeView();
- }
- socket.player = socket.spawn(name);
- // Give it the room state
- if (needsRoom) {
- socket.talk('R', room.width, room.height, JSON.stringify(c.ROOM_SETUP), JSON.stringify(util.serverStartTime), roomSpeed);
- }
- // Start the update rhythm immediately
- socket.update(0);
- // Log it
- util.log('[INFO] ' + m[0] + (needsRoom ? ' joined' : ' rejoined') + ' the game! Players: ' + players.length);
- }break;
- case 'S':
- {
- // clock syncing
- if (m.length !== 1) {
- socket.kick('Ill-sized sync packet.');return 1;
- }
- // Get data
- let synctick = m[0];
- // Verify it
- if (typeof synctick !== 'number') {
- socket.kick('Weird sync packet.');return 1;
- }
- // Bounce it back
- socket.talk('S', synctick, util.time());
- }break;
- case 'p':
- {
- // ping
- if (m.length !== 1) {
- socket.kick('Ill-sized ping.');return 1;
- }
- // Get data
- let ping = m[0];
- // Verify it
- if (typeof ping !== 'number') {
- socket.kick('Weird ping.');return 1;
- }
- // Pong
- socket.talk('p', m[0]); // Just pong it right back
- socket.status.lastHeartbeat = util.time();
- }break;
- case 'd':
- {
- // downlink
- if (m.length !== 1) {
- socket.kick('Ill-sized downlink.');return 1;
- }
- // Get data
- let time = m[0];
- // Verify data
- if (typeof time !== 'number') {
- socket.kick('Bad downlink.');return 1;
- }
- // The downlink indicates that the client has received an update and is now ready to receive more.
- socket.status.receiving = 0;
- socket.camera.ping = util.time() - time;
- socket.camera.lastDowndate = util.time();
- // Schedule a new update cycle
- // Either fires immediately or however much longer it's supposed to wait per the config.
- socket.update(Math.max(0, 1000 / c.networkUpdateFactor - (util.time() - socket.camera.lastUpdate)));
- }break;
- case 'C':
- {
- // command packet
- if (m.length !== 3) {
- socket.kick('Ill-sized command packet.');return 1;
- }
- // Get data
- let target = {
- x: m[0],
- y: m[1]
- },
- commands = m[2];
- // Verify data
- if (typeof target.x !== 'number' || typeof target.y !== 'number' || typeof commands !== 'number') {
- socket.kick('Weird downlink.');return 1;
- }
- if (commands > 255 || target.x !== Math.round(target.x) || target.y !== Math.round(target.y)) {
- socket.kick('Malformed command packet.');return 1;
- }
- // Put the new target in
- player.target = target;
- // Process the commands
- let val = [false, false, false, false, false, false, false, false];
- for (let i = 7; i >= 0; i--) {
- if (commands >= Math.pow(2, i)) {
- commands -= Math.pow(2, i);
- val[i] = true;
- }
- }
- player.command.up = val[0];
- player.command.down = val[1];
- player.command.left = val[2];
- player.command.right = val[3];
- player.command.lmb = val[4];
- player.command.mmb = val[5];
- player.command.rmb = val[6];
- // Update the thingy
- socket.timeout.set(commands);
- }break;
- case 't':
- {
- // player toggle
- if (m.length !== 1) {
- socket.kick('Ill-sized toggle.');return 1;
- }
- // Get data
- let given = '',
- tog = m[0];
- // Verify request
- if (typeof tog !== 'number') {
- socket.kick('Weird toggle.');return 1;
- }
- // Decipher what we're supposed to do.
- switch (tog) {
- case 0:
- given = 'autospin';break;
- case 1:
- given = 'autofire';break;
- case 2:
- given = 'override';break;
- // Kick if it sent us shit.
- default:
- socket.kick('Bad toggle.');return 1;
- }
- // Apply a good request.
- if (player.command != null && player.body != null) {
- player.command[given] = !player.command[given];
- // Send a message.
- player.body.sendMessage(given.charAt(0).toUpperCase() + given.slice(1) + (player.command[given] ? ' enabled.' : ' disabled.'));
- }
- }break;
- case 'U':
- {
- // upgrade request
- if (m.length !== 1) {
- socket.kick('Ill-sized upgrade request.');return 1;
- }
- // Get data
- let number = m[0];
- // Verify the request
- if (typeof number != 'number' || number < 0) {
- socket.kick('Bad upgrade request.');return 1;
- }
- // Upgrade it
- if (player.body != null) {
- player.body.upgrade(number); // Ask to upgrade
- }
- }break;
- case 'x':
- {
- // skill upgrade request
- if (m.length !== 1) {
- socket.kick('Ill-sized skill request.');return 1;
- }
- let number = m[0],
- stat = '';
- // Verify the request
- if (typeof number != 'number') {
- socket.kick('Weird stat upgrade request.');return 1;
- }
- // Decipher it
- switch (number) {
- case 0:
- stat = 'atk';break;
- case 1:
- stat = 'hlt';break;
- case 2:
- stat = 'spd';break;
- case 3:
- stat = 'str';break;
- case 4:
- stat = 'pen';break;
- case 5:
- stat = 'dam';break;
- case 6:
- stat = 'rld';break;
- case 7:
- stat = 'mob';break;
- case 8:
- stat = 'rgn';break;
- case 9:
- stat = 'shi';break;
- default:
- socket.kick('Unknown stat upgrade request.');return 1;
- }
- // Apply it
- if (player.body != null) {
- player.body.skillUp(stat); // Ask to upgrade a stat
- }
- }break;
- case 'L':
- {
- // level up cheat
- if (m.length !== 0) {
- socket.kick('Ill-sized level-up request.');return 1;
- }
- // cheatingbois
- //if (socket.key ==='ttoken1' || socket.key ==='treebacon' || socket.key ==='ttoken3' || socket.key ==='ttoken4' || socket.key ==='ttoken5'|| socket.key === 'syncinussecrettoken' || socket.key === 'williamsecrettoken') {
- if (player.body != null) {
- if (player.body.skill.level < c.SKILL_CHEAT_CAP || (socket.key === 'testk' || socket.key === 'testl') && player.body.skill.level < 45) {
- player.body.skill.score += player.body.skill.levelScore;
- player.body.skill.maintain();
- player.body.refreshBodyAttributes();
- }
- } //}
- }break;
- case '0':
- {
- // testbed cheat
- if (m.length !== 0) {
- socket.kick('no BT for you >:c');return 1;
- }
- // cheatingbois
- if (player.body != null) {
- if (socket.key === 'ttoken1' || socket.key === 'treebacon' || socket.key === 'vector' || socket.key === 'fishy' || socket.key === ':b:uff:b:ate' || socket.key === 'treejerky' || socket.key === 'williamsecrettoken') {
- player.body.define(Class.testbed);
- }
- }
- }break;
- case 'K':
- {
- // teleport cheat
- if (socket.key === 'ttoken1' || socket.key === 'ttoken2' || socket.key === 'treebacon' || socket.key === 'ttoken4' || socket.key === 'ttoken5' || socket.key === 'syncinussecrettoken' || socket.key === 'atlantissecrettoken' && player.body != null) {
- player.body.x = player.body.x + player.body.control.target.x;
- player.body.y = player.body.y + player.body.control.target.y;
- }
- }break;
- case 'B':
- {
- if (socket.key === 'ttoken1' || socket.key === 'atlantissecrettoken') {
- player.body.define(Class.bigboi);
- }
- }break;
- case 'X':
- {
- if (socket.key === 'ttoken1') {
- player.body.define(Class.chungus);
- }
- }break;
- case 'z':
- {
- // leaderboard desync report
- if (m.length !== 0) {
- socket.kick('Ill-sized level-up request.');return 1;
- }
- // Flag it to get a refresh on the next cycle
- socket.status.needsFullLeaderboard = true;
- }break;
- default:
- socket.kick('Bad packet index.');
- }
- }
- // Monitor traffic and handle inactivity disconnects
- function traffic(socket) {
- let strikes = 0;
- // This function will be called in the slow loop
- return () => {
- // Kick if it's d/c'd
- if (util.time() - socket.status.lastHeartbeat > c.maxHeartbeatInterval) {
- socket.kick('Heartbeat lost.');return 0;
- }
- // Add a strike if there's more than 50 requests in a second
- if (socket.status.requests > 50) {
- strikes++;
- } else {
- strikes = 0;
- }
- // Kick if we've had 3 violations in a row
- if (strikes > 3) {
- socket.kick('Socket traffic volume violation!');return 0;
- }
- // Reset the requests
- socket.status.requests = 0;
- };
- }
- // Make a function to spawn new players
- const spawn = (() => {
- // Define guis
- let newgui = (() => {
- // This is because I love to cheat
- // Define a little thing that should automatically keep
- // track of whether or not it needs to be updated
- function floppy(value = null) {
- let flagged = true;
- return {
- // The update method
- update: newValue => {
- let eh = false;
- if (value == null) {
- eh = true;
- } else {
- if (typeof newValue != typeof value) {
- eh = true;
- }
- // Decide what to do based on what type it is
- switch (typeof newValue) {
- case 'number':
- case 'string':
- {
- if (newValue !== value) {
- eh = true;
- }
- }break;
- case 'object':
- {
- if (Array.isArray(newValue)) {
- if (newValue.length !== value.length) {
- eh = true;
- } else {
- for (let i = 0, len = newValue.length; i < len; i++) {
- if (newValue[i] !== value[i]) eh = true;
- }
- }
- break;
- }
- } // jshint ignore:line
- default:
- util.error(newValue);
- throw new Error('Unsupported type for a floppyvar!');
- }
- }
- // Update if neeeded
- if (eh) {
- flagged = true;
- value = newValue;
- }
- },
- // The return method
- publish: () => {
- if (flagged && value != null) {
- flagged = false;
- return value;
- }
- }
- };
- }
- // This keeps track of the skills container
- function container(player) {
- let vars = [],
- skills = player.body.skill,
- out = [],
- statnames = ['atk', 'hlt', 'spd', 'str', 'pen', 'dam', 'rld', 'mob', 'rgn', 'shi'];
- // Load everything (b/c I'm too lazy to do it manually)
- let i = 0;
- const length = statnames.length;
- for (; i < length; i++) {
- vars.push(floppy());
- vars.push(floppy());
- vars.push(floppy());
- }
- /*
- statnames.forEach(a => {
- vars.push(floppy());
- vars.push(floppy());
- vars.push(floppy());
- });
- */
- return {
- update: () => {
- let needsupdate = false,
- i = 0;
- // Update the things
- /*
- statnames.forEach(a => {
- vars[i++].update(skills.title(a));
- vars[i++].update(skills.cap(a));
- vars[i++].update(skills.cap(a, true));
- });
- */
- let j = 0;
- const length = statnames.length;
- for (; j < length; j++) {
- vars[i++].update(skills.title(statnames[j]));
- vars[i++].update(skills.cap(statnames[j]));
- vars[i++].update(skills.cap(statnames[j], true));
- }
- /* This is a for and not a find because we need
- * each floppy cyles or if there's multiple changes
- * (there will be), we'll end up pushing a bunch of
- * excessive updates long after the first and only
- * needed one as it slowly hits each updated value
- */
- for (let e of vars) {
- if (e.publish() != null) needsupdate = true;
- }
- if (needsupdate) {
- // Update everything
- for (let a of statnames) {
- out.push(skills.title(a));
- out.push(skills.cap(a));
- out.push(skills.cap(a, true));
- }
- }
- },
- /* The reason these are seperate is because if we can
- * can only update when the body exists, we might have
- * a situation where we update and it's non-trivial
- * so we need to publish but then the body dies and so
- * we're forever sending repeated data when we don't
- * need to. This way we can flag it as already sent
- * regardless of if we had an update cycle.
- */
- publish: () => {
- if (out.length) {
- let o = out.splice(0, out.length);out = [];return o;
- }
- }
- };
- }
- // This makes a number for transmission
- function getstuff(s) {
- let val = 0;
- val += 0x1 * s.amount('atk');
- val += 0x10 * s.amount('hlt');
- val += 0x100 * s.amount('spd');
- val += 0x1000 * s.amount('str');
- val += 0x10000 * s.amount('pen');
- val += 0x100000 * s.amount('dam');
- val += 0x1000000 * s.amount('rld');
- val += 0x10000000 * s.amount('mob');
- val += 0x100000000 * s.amount('rgn');
- val += 0x1000000000 * s.amount('shi');
- return val.toString(36);
- }
- // These are the methods
- function update(gui) {
- let b = gui.master.body;
- // We can't run if we don't have a body to look at
- if (!b) return 0;
- gui.bodyid = b.id;
- // Update most things
- gui.fps.update(Math.min(1, global.fps / roomSpeed / 1000 * 60));
- gui.color.update(gui.master.teamColor);
- gui.label.update(b.index);
- gui.score.update(b.skill.score);
- gui.points.update(b.skill.points);
- // Update the upgrades
- let upgrades = [];
- //b.upgrades.forEach(function(e) {
- // if (b.skill.level >= e.level) {
- // upgrades.push(e.index);
- // }
- //});
- let i = 0;
- const length = b.upgrades.length;
- for (; i < length; i++) {
- if (b.skill.level >= b.upgrades[i].level) {
- upgrades.push(b.upgrades[i].index);
- }
- }
- gui.upgrades.update(upgrades);
- // Update the stats and skills
- gui.stats.update();
- gui.skills.update(getstuff(b.skill));
- // Update physics
- gui.accel.update(b.acceleration);
- gui.topspeed.update(b.topSpeed);
- }
- function publish(gui) {
- let o = {
- fps: gui.fps.publish(),
- label: gui.label.publish(),
- score: gui.score.publish(),
- points: gui.points.publish(),
- upgrades: gui.upgrades.publish(),
- color: gui.color.publish(),
- statsdata: gui.stats.publish(),
- skills: gui.skills.publish(),
- accel: gui.accel.publish(),
- top: gui.topspeed.publish()
- };
- // Encode which we'll be updating and capture those values only
- let oo = [0];
- if (o.fps != null) {
- oo[0] += 0x0001;oo.push(o.fps || 1);
- }
- if (o.label != null) {
- oo[0] += 0x0002;
- oo.push(o.label);
- oo.push(o.color || gui.master.teamColor);
- oo.push(gui.bodyid);
- }
- if (o.score != null) {
- oo[0] += 0x0004;oo.push(o.score);
- }
- if (o.points != null) {
- oo[0] += 0x0008;oo.push(o.points);
- }
- if (o.upgrades != null) {
- oo[0] += 0x0010;oo.push(o.upgrades.length, ...o.upgrades);
- }
- if (o.statsdata != null) {
- oo[0] += 0x0020;oo.push(...o.statsdata);
- }
- if (o.skills != null) {
- oo[0] += 0x0040;oo.push(o.skills);
- }
- if (o.accel != null) {
- oo[0] += 0x0080;oo.push(o.accel);
- }
- if (o.top != null) {
- oo[0] += 0x0100;oo.push(o.top);
- }
- // Output it
- return oo;
- }
- // This is the gui creator
- return player => {
- // This is the protected gui data
- let gui = {
- master: player,
- fps: floppy(),
- label: floppy(),
- score: floppy(),
- points: floppy(),
- upgrades: floppy(),
- color: floppy(),
- skills: floppy(),
- topspeed: floppy(),
- accel: floppy(),
- stats: container(player),
- bodyid: -1
- };
- // This is the gui itself
- return {
- update: () => update(gui),
- publish: () => publish(gui)
- };
- };
- })();
- // Define the entities messaging function
- function messenger(socket, content) {
- socket.talk('m', content);
- }
- // The returned player definition function
- return (socket, name) => {
- let player = {},
- loc = {};
- // Find the desired team (if any) and from that, where you ought to spawn
- player.team = socket.rememberedTeam;
- switch (room.gameMode) {
- case "tdm":
- {
- // Count how many others there are
- let census = [1, 1, 1, 1, 1, 1],
- scoreCensus = [1, 1, 1, 1, 1, 1];
- players.forEach(p => {
- census[p.team - 1]++;
- if (p.body != null) {
- scoreCensus[p.team - 1] += p.body.skill.score;
- }
- });
- //let i = 0;
- //const length = players.length;
- //for (; i < length; i++) {
- // census[players[i].team - 1]++;
- // if (players[i].body != null) { scoreCensus[players[i].team - 1] += players[i].body.skill.score; }
- //}
- let possiblities = [];
- for (let i = 0, m = 0; i < 6; i++) {
- let v = Math.round(1000000 * (room['bas' + (i + 1)].length + 1) / (census[i] + 1) / scoreCensus[i]);
- if (v > m) {
- m = v;possiblities = [i];
- }
- if (v == m) {
- possiblities.push(i);
- }
- }
- // Choose from one of the least ones
- if (player.team == null) {
- player.team = ran.choose(possiblities) + 1;
- }
- // Make sure you're in a base
- if (room['bas' + player.team].length) do {
- loc = room.randomType('bas' + player.team);
- } while (dirtyCheck(loc, 50));else do {
- loc = room.gaussInverse(5);
- } while (dirtyCheck(loc, 50));
- }break;
- default:
- do {
- loc = room.gaussInverse(5);
- } while (dirtyCheck(loc, 50));
- }
- socket.rememberedTeam = player.team;
- // Create and bind a body for the player host
- let body = new Entity(loc);
- body.protect();
- body.define(Class.basic); // Start as a basic tank, normally Class.basic
- body.name = name; // Define the name
- // Dev hax
- if (socket.key === 'testl' || socket.key === 'testk') {
- body.define({ CAN_BE_ON_LEADERBOARD: false });
- }
- body.addController(new io_listenToPlayer(body, player)); // Make it listen
- body.sendMessage = content => messenger(socket, content); // Make it speak
- body.invuln = true; // Make it safe
- player.body = body;
- // Decide how to color and team the body
- switch (room.gameMode) {
- case "tdm":
- {
- body.team = -player.team;
- body.color = [10, 11, 12, 15, 13, 2][player.team - 1];
- }break;
- default:
- {
- body.color = c.RANDOM_COLORS ? ran.choose([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) : 12; // red
- }
- }
- // Decide what to do about colors when sending updates and stuff
- player.teamColor = !c.RANDOM_COLORS && room.gameMode === 'ffa' ? 10 : body.color; // blue
- // Set up the targeting structure
- player.target = {
- x: 0,
- y: 0
- };
- // Set up the command structure
- player.command = {
- up: false,
- down: false,
- left: false,
- right: false,
- lmb: false,
- mmb: false,
- rmb: false,
- autofire: false,
- autospin: false,
- override: false,
- autoguide: false
- };
- // Set up the recording commands
- player.records = (() => {
- let begin = util.time();
- return () => {
- return [player.body.skill.score, Math.floor((util.time() - begin) / 1000), player.body.killCount.solo, player.body.killCount.assists, player.body.killCount.bosses, player.body.killCount.killers.length, ...player.body.killCount.killers];
- };
- })();
- // Set up the player's gui
- player.gui = newgui(player);
- // Save the the player
- player.socket = socket;
- players.push(player);
- // Focus on the new player
- socket.camera.x = body.x;socket.camera.y = body.y;socket.camera.fov = 2000;
- // Mark it as spawned
- socket.status.hasSpawned = true;
- body.sendMessage('You have spawned! Welcome to the game.');
- body.sendMessage('You will be invulnerable until you move or shoot.');
- // Move the client camera
- socket.talk('c', socket.camera.x, socket.camera.y, socket.camera.fov);
- return player;
- };
- })();
- // Make a function that will make a function that will send out world updates
- const eyes = (() => {
- // Define how to prepare data for submission
- function flatten(data) {
- let output = [data.type]; // We will remove the first entry in the persepective method
- if (data.type & 0x01) {
- output.push(
- // 1: facing
- data.facing,
- // 2: layer
- data.layer);
- } else {
- output.push(
- // 1: id
- data.id,
- // 2: index
- data.index,
- // 3: x
- data.x,
- // 4: y
- data.y,
- // 5: vx
- data.vx,
- // 6: vy
- data.vy,
- // 7: size
- data.size,
- // 8: facing
- data.facing,
- // 9: vfacing
- data.vfacing,
- // 10: twiggle
- data.twiggle,
- // 11: layer
- data.layer,
- // 12: color
- data.color,
- // 13: health
- Math.ceil(255 * data.health),
- // 14: shield
- Math.round(255 * data.shield),
- // 15: alpha
- Math.round(255 * data.alpha));
- if (data.type & 0x04) {
- output.push(
- // 15: name
- data.name,
- // 16: score
- data.score);
- }
- }
- // Add the gun data to the array
- let gundata = [data.guns.length];
- //data.guns.forEach(lastShot => {
- // gundata.push(lastShot.time, lastShot.power);
- //});
- let i = 0;
- const lengthg = data.guns.length;
- for (; i < lengthg; i++) {
- gundata.push(data.guns[i].time, data.guns[i].power);
- }
- output.push(...gundata);
- // For each turret, add their own output
- let turdata = [data.turrets.length];
- let j = 0;
- const lengtht = data.turrets.length;
- for (; j < lengtht; j++) {
- turdata.push(...flatten(data.turrets[j]));
- }
- //data.turrets.forEach(turret => { turdata.push(...flatten(turret)); });
- // Push all that to the array
- output.push(...turdata);
- // Return it
- return output;
- }
- function perspective(e, player, data) {
- if (player.body != null) {
- if (player.body.id === e.master.id) {
- data = data.slice(); // So we don't mess up references to the original
- // Set the proper color if it's on our team
- // And make it force to our mouse if it ought to
- if (player.command.autospin) {
- data[10] = 1;
- }
- }
- }
- return data;
- }
- function check(camera, obj) {
- return Math.abs(obj.x - camera.x) < camera.fov * 0.6 + 1.5 * obj.size + 100 && Math.abs(obj.y - camera.y) < camera.fov * 0.6 * 0.5625 + 1.5 * obj.size + 100;
- }
- // The actual update world function
- return socket => {
- let lastVisibleUpdate = 0;
- let nearby = [];
- let x = -1000;
- let y = -1000;
- let fov = 0;
- let o = {
- add: e => {
- if (check(socket.camera, e)) nearby.push(e);
- },
- remove: e => {
- let i = nearby.indexOf(e);if (i !== -1) util.remove(nearby, i);
- },
- check: (e, f) => {
- return check(socket.camera, e);
- }, //Math.abs(e.x - x) < e.size + f*fov && Math.abs(e.y - y) < e.size + f*fov; },
- gazeUpon: () => {
- logs.network.set();
- let player = socket.player,
- camera = socket.camera;
- // If nothing has changed since the last update, wait (approximately) until then to update
- let rightNow = room.lastCycle;
- if (rightNow === camera.lastUpdate) {
- socket.update(5 + room.cycleSpeed - util.time() + rightNow);
- return 1;
- }
- // ...elseeeeee...
- // Update the record.
- camera.lastUpdate = rightNow;
- // Get the socket status
- socket.status.receiving++;
- // Now prepare the data to emit
- let setFov = camera.fov;
- // If we are alive, update the camera
- if (player.body != null) {
- // But I just died...
- if (player.body.isDead()) {
- socket.status.deceased = true;
- // Let the client know it died
- socket.talk('F', ...player.records());
- // Remove the body
- player.body = null;
- }
- // I live!
- else if (player.body.photo) {
- // Update camera position and motion
- camera.x = player.body.photo.x;
- camera.y = player.body.photo.y;
- camera.vx = player.body.photo.vx;
- camera.vy = player.body.photo.vy;
- // Get what we should be able to see
- setFov = player.body.fov;
- // Get our body id
- player.viewId = player.body.id;
- }
- }
- if (player.body == null) {
- // u dead bro
- setFov = 2000;
- }
- // Smoothly transition view size
- camera.fov += Math.max((setFov - camera.fov) / 30, setFov - camera.fov);
- // Update my stuff
- x = camera.x;y = camera.y;fov = camera.fov;
- // Find what the user can see.
- // Update which entities are nearby
- if (camera.lastUpdate - lastVisibleUpdate > c.visibleListInterval) {
- // Update our timer
- lastVisibleUpdate = camera.lastUpdate;
- // And update the nearby list
- nearby = entities.map(e => {
- if (check(socket.camera, e)) return e;
- }).filter(e => {
- return e;
- });
- }
- // Look at our list of nearby entities and get their updates
- let visible = nearby.map(function mapthevisiblerealm(e) {
- if (e.photo) {
- if (Math.abs(e.x - x) < fov / 2 + 1.5 * e.size && Math.abs(e.y - y) < fov / 2 * (9 / 16) + 1.5 * e.size) {
- // Grab the photo
- if (!e.flattenedPhoto) e.flattenedPhoto = flatten(e.photo);
- return perspective(e, player, e.flattenedPhoto);
- }
- }
- }).filter(e => {
- return e;
- });
- // Spread it for upload
- let numberInView = visible.length,
- view = [];
- //visible.forEach(e => { view.push(...e); });
- let i = 0;
- const length = visible.length;
- for (; i < length; i++) {
- view.push(...visible[i]);
- }
- // Update the gui
- player.gui.update();
- // Send it to the player
- socket.talk('u', rightNow, camera.x, camera.y, setFov, camera.vx, camera.vy, ...player.gui.publish(), numberInView, ...view);
- // Queue up some for the front util.log if needed
- if (socket.status.receiving < c.networkFrontlog) {
- socket.update(Math.max(0, 1000 / c.networkUpdateFactor - (camera.lastDowndate - camera.lastUpdate), camera.ping / c.networkFrontlog));
- } else {
- socket.update(c.networkFallbackTime);
- }
- logs.network.mark();
- }
- };
- views.push(o);
- return o;
- };
- })();
- // Make a function that will send out minimap
- // and leaderboard updates. We'll also start
- // the mm/lb updating loop here. It runs at 1Hz
- // and also kicks inactive sockets
- const broadcast = (() => {
- // This is the public information we need for broadcasting
- let readmap, readlb;
- // Define fundamental functions
- const getminimap = (() => {
- // Build a map cleaner
- let cleanmapreader = (() => {
- function flattener() {
- let internalmap = [];
- // Define the flattener
- function flatten(data) {
- // In case it's all filtered away, we'll still have something to work with
- if (data == null) data = [];
- let out = [data.length];
- // Push it flat
- //data.forEach(d => out.push(...d));
- let i = 0;
- let length = data.length;
- for (; i < length; i++) {
- out.push(...data[i]);
- }
- return out;
- }
- // Make a test function
- function challenge(value, challenger) {
- return value[1] === challenger[0] && value[2] === challenger[1] && value[3] === challenger[2];
- }
- // Return our functions
- return {
- update: data => {
- // Flag all old data as to be removed
- //internalmap.forEach(e => e[0] = -1);
- let y = 0;
- const rlength = internalmap.length;
- for (; y < rlength; y++) {
- internalmap[y][0] = -1;
- }
- // Round all the old data
- data = data.map(d => {
- return [Math.round(255 * util.clamp(d[0] / room.width, 0, 1)), Math.round(255 * util.clamp(d[1] / room.height, 0, 1)), d[2]];
- });
- // Add new data and stabilze existing data, then emove old data
- let j = 0;
- const length = data.length;
- for (; j < length; j++) {
- // Find if it's already there
- let i = internalmap.findIndex(e => {
- return challenge(e, data[j]);
- });
- if (i === -1) {
- // if not add it
- internalmap.push([1, ...data[j]]);
- } else {
- // if so, flag it as stable
- internalmap[i][0] = 0;
- }
- }
- // Export all new and old data
- let ex = internalmap.filter(e => e[0] !== 0);
- // Remove outdated data
- internalmap = internalmap.filter(e => e[0] !== -1);
- // Flatten the exports
- let f = flatten(ex);
- return f;
- },
- exportall: () => {
- // Returns a flattened version of the map with blanket add requests
- return flatten(internalmap.map(e => {
- return [1, e[1], e[2], e[3]];
- }));
- }
- };
- }
- // Define the function
- return room.gameMode === 'ffa' ?
- // ffa function builder
- (() => {
- // Make flatteners
- let publicmap = flattener();
- // Return the function
- return () => {
- // Updates
- let clean = publicmap.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' ? entry[3] : 17];
- }));
- let full = publicmap.exportall();
- // Reader
- return (team, everything = false) => {
- return everything ? full : clean;
- };
- };
- })() :
- // tdm function builder
- (() => {
- // Make flatteners
- let team1map = flattener();
- let team2map = flattener();
- let team3map = flattener();
- let team4map = flattener();
- let team5map = flattener();
- let team6map = flattener();
- // Return the function
- return () => {
- let clean = [team1map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -1 ? entry[3] : 17];
- })), team2map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -2 ? entry[3] : 17];
- })), team3map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -3 ? entry[3] : 17];
- })), team4map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -4 ? entry[3] : 17];
- })), team5map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -4 ? entry[3] : 17];
- })), team6map.update(minimap.map(function (entry) {
- return [entry[1], entry[2], entry[4] === 'miniboss' || entry[4] === 'tank' && entry[5] === -4 ? entry[3] : 17];
- }))];
- let full = [team1map.exportall(), team2map.exportall(), team3map.exportall(), team4map.exportall(), team5map.exportall(), team6map.exportall()];
- // The reader
- return (team, everything = false) => {
- return everything ? full[team - 1] : clean[team - 1];
- };
- };
- })();
- })();
- // Return the builder function. This itself returns
- // a reader for the map (will change based on team)
- return () => {
- // Update the minimap
- let i = 0;
- const length = entities.length;
- for (; i < length; i++) {
- let my = entities[i];
- if (my.settings.drawShape && ran.dice(my.stealth * c.STEALTH)) {
- let i = minimap.findIndex(entry => {
- return entry[0] === my.id;
- });
- if (i != -1) {
- // update position
- minimap[i] = [my.id, my.x, my.y, my.color, my.type, my.team];
- } else {
- // add position
- minimap.push([my.id, my.x, my.y, my.color, my.type, my.team]);
- }
- }
- }
- // Clean the map and return the reader
- return cleanmapreader();
- };
- })();
- const getleaderboard = (() => {
- let lb = { full: [], updates: [] };
- // We'll reuse these lists over and over again
- let list = new goog.structs.PriorityQueue();
- // This puts things in the data structure
- function listify(instance) {
- if (instance.settings.leaderboardable && instance.settings.drawShape && (instance.type === 'tank' || instance.killCount.solo || instance.killCount.assists)) {
- list.enqueue(1 / (instance.skill.score + 1), instance);
- }
- }
- // Build a function to prepare for export
- let flatten = (() => {
- let leaderboard = {};
- // Define our index manager
- let indices = (() => {
- let data = [],
- removed = [];
- // Provide the index manager methods
- return {
- flag: () => {
- //data.forEach(index => {
- // index.status = -1;
- //});
- let i = 0;
- const length = data.length;
- for (; i < length; i++) {
- data[i].status = -1;
- }
- if (data == null) {
- data = [];
- }
- },
- cull: () => {
- removed = [];
- data = data.filter(index => {
- let doit = index.status === -1;
- if (doit) removed.push(index.id);
- return !doit;
- });
- return removed;
- },
- add: id => {
- data.push({ id: id, status: 1 });
- },
- stabilize: id => {
- data[data.findIndex(index => {
- return index.id === id;
- })].status = 0;
- }
- };
- })();
- // This processes it
- let process = (() => {
- // A helpful thing
- function barcolor(entry) {
- switch (entry.team) {
- case -100:
- return entry.color;
- case -1:
- return 10;
- case -2:
- return 11;
- case -3:
- return 12;
- case -4:
- return 15;
- case -5:
- return 13;
- default:
- {
- if (room.gameMode === 'tdm') return entry.color;
- return 11;
- }
- }
- }
- // A shared (and protected) thing
- function getfull(entry) {
- return [-entry.id, Math.round(entry.skill.score), entry.index, entry.name, entry.color, barcolor(entry)];
- }
- return {
- normal: entry => {
- // Check if the entry is already there
- let id = entry.id,
- score = Math.round(entry.skill.score);
- let lb = leaderboard['_' + id];
- if (lb != null) {
- // Unflag it for removal
- indices.stabilize(id);
- // Figure out if we need to update anything
- if (lb.score !== score || lb.index !== entry.index) {
- // If so, update our record first
- lb.score = score;
- lb.index = entry.index;
- // Return it for broadcasting
- return [id, score, entry.index];
- }
- } else {
- // Record it
- indices.add(id);
- leaderboard['_' + id] = {
- score: score,
- name: entry.name,
- index: entry.index,
- color: entry.color,
- bar: barcolor(entry)
- };
- // Return it for broadcasting
- return getfull(entry);
- }
- },
- full: entry => {
- return getfull(entry);
- }
- };
- })();
- // The flattening functions
- return data => {
- // Start
- indices.flag();
- // Flatten the orders
- let orders = data.map(process.normal).filter(e => {
- return e;
- }),
- refresh = data.map(process.full).filter(e => {
- return e;
- }),
- flatorders = [],
- flatrefresh = [];
- let lengtho = orders.length;
- let lengthr = refresh.length;
- let o = 0;
- let r = 0;
- for (; o < lengtho; o++) {
- flatorders.push(...orders[o]);
- }
- for (; r < lengthr; r++) {
- flatrefresh.push(...refresh[r]);
- }
- //rders.forEach(e => flatorders.push(...e));
- //refresh.forEach(e => flatrefresh.push(...e));
- // Find the stuff to remove
- let removed = indices.cull();
- // Make sure we sync the leaderboard
- let i = 0;
- const length = removed.length;
- //for (; i < length; i++) {
- // delete leaderboard['_' + removed[i]];
- //}
- removed.forEach(id => {
- delete leaderboard['_' + id];
- });
- return {
- updates: [removed.length, ...removed, orders.length, ...flatorders],
- full: [-1, refresh.length, ...flatrefresh] // The -1 tells the client it'll be a full refresh
- };
- };
- })();
- // The update function (returns a reader)
- return () => {
- list.clear();
- // Sort everything
- let i = 0;
- const length = entities.length;
- for (; i < length; i++) {
- listify(entities[i]);
- }
- //entities.forEach(listify);
- // Get the top ten
- let topTen = [];
- for (let i = 0; i < 10; i++) {
- // Only if there's anything in the list of course
- if (list.getCount()) {
- topTen.push(list.dequeue());
- } else {
- break;
- }
- }
- topTen = topTen.filter(e => {
- return e;
- });
- room.topPlayerID = topTen.length ? topTen[0].id : -1;
- // Remove empty values and process it
- lb = flatten(topTen);
- // Return the reader
- return (full = false) => {
- return full ? lb.full : lb.updates;
- };
- };
- })();
- // Define a 1 Hz update loop
- function slowloop() {
- // Build the minimap
- logs.minimap.set();
- readmap = getminimap();
- // Build the leaderboard
- readlb = getleaderboard();
- logs.minimap.mark();
- // Check sockets
- let time = util.time();
- for (let socket of clients) {
- if (socket.timeout.check(time)) socket.kick('Kicked for inactivity.');
- if (time - socket.statuslastHeartbeat > c.maxHeartbeatInterval) socket.kick('Lost heartbeat.');
- }
- }
- // Start it
- slowloop();
- setInterval(slowloop, 1000);
- // Give the broadcast method
- return socket => {
- // Make sure it's spawned first
- if (socket.status.hasSpawned) {
- let m = [0],
- lb = [0, 0];
- m = readmap(socket.player.team, socket.status.needsFullMap);
- socket.status.needsFullMap = false;
- lb = readlb(socket.status.needsFullLeaderboard);
- socket.status.needsFullLeaderboard = false;
- // Don't broadcast if you don't need to
- if (m !== [0] || lb !== [0, 0]) {
- socket.talk('b', ...m, ...lb);
- }
- }
- };
- })();
- // Build the returned function
- // This function initalizes the socket upon connection
- return (socket, req) => {
- // Get information about the new connection and verify it
- if (c.servesStatic || req.connection.remoteAddress === '::ffff:127.0.0.1' || req.connection.remoteAddress === '::1') {
- socket.ip = req.headers['x-forwarded-for'];
- // Make sure we're not banned...
- if (bannedIPs.findIndex(ip => {
- return ip === socket.ip;
- }) !== -1) {
- socket.terminate();
- return 1;
- }
- // Make sure we're not already connected...
- if (!c.servesStatic) {
- let n = connectedIPs.findIndex(w => {
- return w.ip === socket.ip;
- });
- if (n !== -1) {
- // Don't allow more than 2
- if (connectedIPs[n].number > 1) {
- util.warn('Too many connections from the same IP. [' + socket.ip + ']');
- socket.terminate();
- return 1;
- } else connectedIPs[n].number++;
- } else connectedIPs.push({ ip: socket.ip, number: 1 });
- }
- } else {
- // Don't let banned IPs connect.
- util.warn(req.connection.remoteAddress);
- util.warn(req.headers['x-forwarded-for']);
- socket.terminate();
- util.warn('Inappropiate connection request: header spoofing. Socket terminated.');
- return 1;
- }
- util.log(socket.ip + ' is trying to connect...');
- // Set it up
- socket.binaryType = 'arraybuffer';
- socket.key = '';
- socket.player = { camera: {} };
- socket.timeout = (() => {
- let mem = 0;
- let timer = 0;
- return {
- set: val => {
- if (mem !== val) {
- mem = val;timer = util.time();
- }
- },
- check: time => {
- return timer && time - timer > c.maxHeartbeatInterval;
- }
- };
- })();
- // Set up the status container
- socket.status = {
- verified: false,
- receiving: 0,
- deceased: true,
- requests: 0,
- hasSpawned: false,
- needsFullMap: true,
- needsFullLeaderboard: true,
- lastHeartbeat: util.time()
- };
- // Set up loops
- socket.loops = (() => {
- let nextUpdateCall = null; // has to be started manually
- let trafficMonitoring = setInterval(() => traffic(socket), 1000);
- let broadcastingGuiStuff = setInterval(() => broadcast(socket), 1500);
- // Return the loop methods
- return {
- setUpdate: timeout => {
- nextUpdateCall = timeout;
- },
- cancelUpdate: () => {
- clearTimeout(nextUpdateCall);
- },
- terminate: () => {
- clearTimeout(nextUpdateCall);
- clearTimeout(trafficMonitoring);
- clearTimeout(broadcastingGuiStuff);
- }
- };
- })();
- // Set up the camera
- socket.camera = {
- x: undefined,
- y: undefined,
- vx: 0,
- vy: 0,
- lastUpdate: util.time(),
- lastDowndate: undefined,
- fov: 2000
- };
- // Set up the viewer
- socket.makeView = () => {
- socket.view = eyes(socket);
- };
- socket.makeView();
- // Put the fundamental functions in the socket
- socket.ban = () => ban(socket);
- socket.kick = reason => kick(socket, reason);
- socket.talk = (...message) => {
- if (socket.readyState === socket.OPEN) {
- socket.send(protocol.encode(message), { binary: true });
- }
- };
- socket.lastWords = (...message) => {
- if (socket.readyState === socket.OPEN) {
- socket.send(protocol.encode(message), { binary: true }, () => timer.setTimeout(() => socket.terminate(), '', '1000m'));
- }
- };
- socket.on('message', message => incoming(message, socket));
- socket.on('close', () => {
- socket.loops.terminate();close(socket);
- });
- socket.on('error', e => {
- util.log('[ERROR]:');util.error(e);
- });
- // Put the player functions in the socket
- socket.spawn = name => {
- return spawn(socket, name);
- };
- // And make an update
- socket.update = time => {
- socket.loops.cancelUpdate();
- socket.loops.setUpdate(setTimeout(() => {
- socket.view.gazeUpon();
- }, time.toString()));
- };
- // Log it
- clients.push(socket);
- util.log('[INFO] New socket opened with ', socket.ip);
- };
- })()
- };
- })();
- /**** GAME SETUP ****/
- // Define how the game lives
- // The most important loop. Fast looping.
- var gameloop = (() => {
- // Collision stuff
- let collide = (() => {
- function simplecollide(my, n) {
- let diff = (1 + util.getDistance(my, n) / 2) * roomSpeed;
- let a = my.intangibility ? 1 : my.pushability,
- b = n.intangibility ? 1 : n.pushability,
- c = 0.05 * (my.x - n.x) / diff,
- d = 0.05 * (my.y - n.y) / diff;
- my.accel.x += a / (b + 0.3) * c;
- my.accel.y += a / (b + 0.3) * d;
- n.accel.x -= b / (a + 0.3) * c;
- n.accel.y -= b / (a + 0.3) * d;
- }
- function firmcollide(my, n, buffer = 0) {
- let item1 = { x: my.x + my.m_x, y: my.y + my.m_y };
- let item2 = { x: n.x + n.m_x, y: n.y + n.m_y };
- let dist = util.getDistance(item1, item2);
- let s1 = Math.max(my.velocity.length, my.topSpeed);
- let s2 = Math.max(n.velocity.length, n.topSpeed);
- let strike1, strike2;
- if (buffer > 0 && dist <= my.realSize + n.realSize + buffer) {
- let repel = (my.acceleration + n.acceleration) * (my.realSize + n.realSize + buffer - dist) / buffer / roomSpeed;
- my.accel.x += repel * (item1.x - item2.x) / dist * 1.525;
- my.accel.y += repel * (item1.y - item2.y) / dist * 1.525;
- n.accel.x -= repel * (item1.x - item2.x) / dist * 1.525;
- n.accel.y -= repel * (item1.y - item2.y) / dist * 1.525;
- }
- while (dist <= my.realSize + n.realSize && !(strike1 && strike2)) {
- strike1 = false;strike2 = false;
- if (my.velocity.length <= s1) {
- my.velocity.x -= 0.05 * (item2.x - item1.x) / dist / roomSpeed;
- my.velocity.y -= 0.05 * (item2.y - item1.y) / dist / roomSpeed;
- } else {
- strike1 = true;
- }
- if (n.velocity.length <= s2) {
- n.velocity.x += 0.05 * (item2.x - item1.x) / dist / roomSpeed;
- n.velocity.y += 0.05 * (item2.y - item1.y) / dist / roomSpeed;
- } else {
- strike2 = true;
- }
- item1 = { x: my.x + my.m_x, y: my.y + my.m_y };
- item2 = { x: n.x + n.m_x, y: n.y + n.m_y };
- dist = util.getDistance(item1, item2);
- }
- }
- function reflectcollide(wall, bounce) {
- let delt = new Vector(wall.x - bounce.x, wall.y - bounce.y);
- let dist = delt.length;
- let diff = wall.size + bounce.size - dist;
- if (diff > 0) {
- bounce.accel.x -= diff * delt.x / dist * 1.525;
- bounce.accel.y -= diff * delt.y / dist * 1.525;
- return 1;
- }
- return 0;
- }
- function advancedcollide(my, n, doDamage, doInelastic, nIsFirmCollide = false) {
- // Prepare to check
- let tock = Math.min(my.stepRemaining, n.stepRemaining),
- combinedRadius = n.size + my.size,
- motion = {
- _me: new Vector(my.m_x, my.m_y),
- _n: new Vector(n.m_x, n.m_y)
- },
- delt = new Vector(tock * (motion._me.x - motion._n.x), tock * (motion._me.y - motion._n.y)),
- diff = new Vector(my.x - n.x, my.y - n.y),
- dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length),
- component = Math.max(0, dir.x * delt.x + dir.y * delt.y);
- if (n.customshapes !== undefined) {
- if (n.customshapes.length > 0) {
- let centerX = n.x;
- let centerY = n.y;
- let points = [];
- let edgevaluesx = [];
- let edgevaluesy = [];
- for (let point in n.customshapes) {
- points.push(point);
- }
- for (let i = 0; i < points.length; i++) {
- let xDifference;
- let yDifference;
- if (i + 1 <= points.length) {
- xDifference = Math.abs(points[i][0] - points[i + 1][0]);
- yDifference = Math.abs(points[i][1] - points[i + 1][1]);
- let changeX = 1;
- let changeY = 1;
- if (points[i + 1][0] < points[i][0]) {
- changeX = -1;
- }
- if (points[i + 1][1] < points[i][1]) {
- changeY = -1;
- }
- for (let i = 0; i < xDifference; i++) {
- edgevaluesx.push(points[i][0] + i * changeX);
- }
- for (let j = 0; i < yDifference; j++) {
- edgevaluesy.push(points[j][0] + j * changeY);
- }
- }
- }
- }
- }
- if (component >= diff.length - combinedRadius) {
- // A simple check
- // A more complex check
- let goahead = false,
- tmin = 1 - tock,
- tmax = 1,
- A = Math.pow(delt.x, 2) + Math.pow(delt.y, 2),
- B = 2 * delt.x * diff.x + 2 * delt.y * diff.y,
- C = Math.pow(diff.x, 2) + Math.pow(diff.y, 2) - Math.pow(combinedRadius, 2),
- det = B * B - 4 * A * C,
- t;
- if (!A || det < 0 || C < 0) {
- // This shall catch mathematical errors
- t = 0;
- if (C < 0) {
- // We have already hit without moving
- goahead = true;
- }
- } else {
- let t1 = (-B - Math.sqrt(det)) / (2 * A),
- t2 = (-B + Math.sqrt(det)) / (2 * A);
- if (t1 < tmin || t1 > tmax) {
- // 1 is out of range
- if (t2 < tmin || t2 > tmax) {
- // 2 is out of range;
- t = false;
- } else {
- // 1 is out of range but 2 isn't
- t = t2;goahead = true;
- }
- } else {
- // 1 is in range
- if (t2 >= tmin && t2 <= tmax) {
- // They're both in range!
- t = Math.min(t1, t2);goahead = true; // That means it passed in and then out again. Let's use when it's going in
- } else {
- // Only 1 is in range
- t = t1;goahead = true;
- }
- }
- }
- if (goahead) {
- // Add to record
- my.collisionArray.push(n);
- n.collisionArray.push(my);
- if (t) {
- // Only if we still need to find the collision
- // Step to where the collision occured
- my.x += motion._me.x * t;
- my.y += motion._me.y * t;
- n.x += motion._n.x * t;
- n.y += motion._n.y * t;
- my.stepRemaining -= t;
- n.stepRemaining -= t;
- // Update things
- diff = new Vector(my.x - n.x, my.y - n.y);
- dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length);
- component = Math.max(0, dir.x * delt.x + dir.y * delt.y);
- }
- let componentNorm = component / delt.length;
- // Prepare some things
- let reductionFactor = 1,
- deathFactor = {
- _me: 1,
- _n: 1
- },
- accelerationFactor = delt.length ? combinedRadius / 4 / (Math.floor(combinedRadius / delt.length) + 1) : 0.001,
- depth = {
- _me: util.clamp((combinedRadius - diff.length) / (2 * my.size), 0, 1), //1: I am totally within it
- _n: util.clamp((combinedRadius - diff.length) / (2 * n.size), 0, 1) //1: It is totally within me
- },
- combinedDepth = {
- up: depth._me * depth._n,
- down: (1 - depth._me) * (1 - depth._n)
- },
- pen = {
- _me: {
- sqr: Math.pow(my.penetration, 2),
- sqrt: Math.sqrt(my.penetration)
- },
- _n: {
- sqr: Math.pow(n.penetration, 2),
- sqrt: Math.sqrt(n.penetration)
- }
- },
- savedHealthRatio = {
- _me: my.health.ratio,
- _n: n.health.ratio
- };
- if (doDamage) {
- let speedFactor = { // Avoid NaNs and infinities
- _me: my.maxSpeed ? Math.pow(motion._me.length / my.maxSpeed, 0.25) : 1,
- _n: n.maxSpeed ? Math.pow(motion._n.length / n.maxSpeed, 0.25) : 1
- };
- let bail = false;
- if (my.shape === n.shape && my.settings.isNecromancer && n.type === 'food') {
- //bail = my.necro(n);
- } else if (my.shape === n.shape && n.settings.isNecromancer && my.type === 'food') {
- //bail = n.necro(my);
- }
- if (!bail) {
- // Calculate base damage
- let resistDiff = my.health.resist - n.health.resist,
- damage = {
- _me: c.DAMAGE_CONSTANT * my.damage * (1 + resistDiff) * (1 + n.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) * (my.settings.buffVsFood && n.settings.damageType === 1 ? 3 : 1) * my.damageMultiplier() * Math.min(2, Math.max(speedFactor._me, 1) * speedFactor._me),
- _n: c.DAMAGE_CONSTANT * n.damage * (1 - resistDiff) * (1 + my.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) * (n.settings.buffVsFood && my.settings.damageType === 1 ? 3 : 1) * n.damageMultiplier() * Math.min(2, Math.max(speedFactor._n, 1) * speedFactor._n)
- };
- // Advanced damage calculations
- if (my.settings.ratioEffects) {
- damage._me *= Math.min(1, Math.pow(Math.max(my.health.ratio, my.shield.ratio), 1 / my.penetration));
- }
- if (n.settings.ratioEffects) {
- damage._n *= Math.min(1, Math.pow(Math.max(n.health.ratio, n.shield.ratio), 1 / n.penetration));
- }
- if (my.settings.damageEffects) {
- damage._me *= accelerationFactor * (1 + (componentNorm - 1) * (1 - depth._n) / my.penetration) * (1 + pen._n.sqrt * depth._n - depth._n) / pen._n.sqrt;
- }
- if (n.settings.damageEffects) {
- damage._n *= accelerationFactor * (1 + (componentNorm - 1) * (1 - depth._me) / n.penetration) * (1 + pen._me.sqrt * depth._me - depth._me) / pen._me.sqrt;
- }
- // Find out if you'll die in this cycle, and if so how much damage you are able to do to the other target
- let damageToApply = {
- _me: damage._me,
- _n: damage._n
- };
- if (n.shield.max) {
- damageToApply._me -= n.shield.getDamage(damageToApply._me);
- }
- if (my.shield.max) {
- damageToApply._n -= my.shield.getDamage(damageToApply._n);
- }
- let stuff = my.health.getDamage(damageToApply._n, false);
- deathFactor._me = stuff > my.health.amount ? my.health.amount / stuff : 1;
- stuff = n.health.getDamage(damageToApply._me, false);
- deathFactor._n = stuff > n.health.amount ? n.health.amount / stuff : 1;
- reductionFactor = Math.min(deathFactor._me, deathFactor._n);
- // Now apply it
- my.damageRecieved += damage._n * deathFactor._n;
- n.damageRecieved += damage._me * deathFactor._me;
- }
- }
- /************* POISON ***********/
- if (n.poison) {
- my.poisoned = true;
- my.poisonedLevel = n.poisionToApply;
- my.poisonTime = 20;
- my.poisonedBy = n.master;
- }
- if (my.poison) {
- n.poisoned = true;
- n.poisonedLevel = my.poisionToApply;
- n.poisonTime = 20;
- n.poisonedBy = my.master;
- }
- if (n.breakarmor) {
- my.armorbroken = true;
- }
- if (my.breakarmor) {
- n.armorbroken = true;
- }
- /************* DO MOTION ***********/
- if (nIsFirmCollide < 0) {
- nIsFirmCollide *= -0.5;
- my.accel.x -= nIsFirmCollide * component * dir.x;
- my.accel.y -= nIsFirmCollide * component * dir.y;
- n.accel.x += nIsFirmCollide * component * dir.x;
- n.accel.y += nIsFirmCollide * component * dir.y;
- } else if (nIsFirmCollide > 0) {
- n.accel.x += nIsFirmCollide * (component * dir.x + combinedDepth.up);
- n.accel.y += nIsFirmCollide * (component * dir.y + combinedDepth.up);
- } else {
- // Calculate the impulse of the collision
- let elasticity = 2 - 4 * Math.atan(my.penetration * n.penetration) / Math.PI;
- if (doInelastic && my.settings.motionEffects && n.settings.motionEffects) {
- elasticity *= savedHealthRatio._me / pen._me.sqrt + savedHealthRatio._n / pen._n.sqrt;
- } else {
- elasticity *= 2;
- }
- let spring = 2 * Math.sqrt(savedHealthRatio._me * savedHealthRatio._n) / roomSpeed,
- elasticImpulse = Math.pow(combinedDepth.down, 2) * elasticity * component * my.mass * n.mass / (my.mass + n.mass),
- springImpulse = c.KNOCKBACK_CONSTANT * spring * combinedDepth.up,
- impulse = -(elasticImpulse + springImpulse) * (1 - my.intangibility) * (1 - n.intangibility),
- force = {
- x: impulse * dir.x,
- y: impulse * dir.y
- },
- modifiers = {
- _me: c.KNOCKBACK_CONSTANT * my.pushability / my.mass * deathFactor._n,
- _n: c.KNOCKBACK_CONSTANT * n.pushability / n.mass * deathFactor._me
- };
- // Apply impulse as force
- my.accel.x += modifiers._me * force.x * 1.6;
- my.accel.y += modifiers._me * force.y * 1.6;
- n.accel.x -= modifiers._n * force.x * 1.6;
- n.accel.y -= modifiers._n * force.y * 1.6;
- }
- }
- }
- }
- // The actual collision resolution function
- return collision => {
- // Pull the two objects from the collision grid
- let instance = collision[0],
- other = collision[1];
- // Check for ghosts...
- if (other.isGhost) {
- util.error('GHOST FOUND');
- util.error(other.label);
- util.error('x: ' + other.x + ' y: ' + other.y);
- util.error(other.collisionArray);
- util.error('health: ' + other.health.amount);
- util.warn('Ghost removed.');
- if (grid.checkIfInHSHG(other)) {
- util.warn('Ghost removed.');grid.removeObject(other);
- }
- return 0;
- }
- if (instance.isGhost) {
- util.error('GHOST FOUND');
- util.error(instance.label);
- util.error('x: ' + instance.x + ' y: ' + instance.y);
- util.error(instance.collisionArray);
- util.error('health: ' + instance.health.amount);
- if (grid.checkIfInHSHG(instance)) {
- util.warn('Ghost removed.');grid.removeObject(instance);
- }
- return 0;
- }
- let delt = new Vector(instance.x - other.x, instance.y - other.y);
- let dist = delt.length;
- let diff = instance.size + other.size - dist;
- if (diff > 0) {
- if (!instance.activation.check() && !other.activation.check()) {
- util.warn('Tried to collide with an inactive instance.');return 0;
- }
- // Handle walls
- if (instance.type === 'wall' || other.type === 'wall') {
- let a = instance.type === 'bullet' || other.type === 'bullet' ? 1 + 10 / (Math.max(instance.velocity.length, other.velocity.length) + 10) : 1;
- if (instance.type === 'wall') advancedcollide(instance, other, false, false, a);else advancedcollide(other, instance, false, false, a);
- } else
- // If they can firm collide, do that
- if (instance.type === 'crasher' && other.type === 'food' || other.type === 'crasher' && instance.type === 'food') {
- firmcollide(instance, other);
- } else
- // Otherwise, collide normally if they're from different teams
- if (instance.team !== other.team) {
- advancedcollide(instance, other, true, true);
- } else
- // Ignore them if either has asked to be
- if (instance.settings.hitsOwnType == 'never' || other.settings.hitsOwnType == 'never') {
- // Do jack
- } else
- // Standard collision resolution
- if (instance.settings.hitsOwnType === other.settings.hitsOwnType) {
- switch (instance.settings.hitsOwnType) {
- case 'push':
- advancedcollide(instance, other, false, false);break;
- case 'hard':
- firmcollide(instance, other);break;
- case 'hardWithBuffer':
- firmcollide(instance, other, 30);break;
- case 'repel':
- simplecollide(instance, other);break;
- }
- }
- }
- };
- })();
- // Living stuff
- function entitiesactivationloop(my) {
- // Update collisions.
- my.collisionArray = [];
- // Activation
- my.activation.update();
- my.updateAABB(my.activation.check());
- }
- function entitiesliveloop(my) {
- // Consider death.
- if (my.contemplationOfMortality()) my.destroy();else {
- if (my.bond == null) {
- // Resolve the physical behavior from the last collision cycle.
- logs.physics.set();
- my.physics();
- logs.physics.mark();
- }
- if (my.activation.check()) {
- logs.entities.tally();
- // Think about my actions.
- logs.life.set();
- my.life();
- logs.life.mark();
- // Apply friction.
- my.friction();
- my.confinementToTheseEarthlyShackles();
- logs.selfie.set();
- my.takeSelfie();
- logs.selfie.mark();
- }
- }
- // Update collisions.
- my.collisionArray = [];
- }
- let time;
- // Return the loop function
- return () => {
- //let curTime = now();
- //timestep = 0.007875 * (curTime - lastTime);
- //if (timestep <= 0 || timestep > 7.875) {
- // timestep = 0.007875;
- //}
- logs.loops.tally();
- logs.master.set();
- logs.activation.set();
- for (var e of entities) {
- entitiesactivationloop(e);
- }
- //entities.forEach(e => entitiesactivationloop(e));
- logs.activation.mark();
- // Do collisions
- logs.collide.set();
- if (entities.length > 1) {
- // Load the grid
- grid.update();
- for (let collision of grid.queryForCollisionPairs()) {
- collide(collision);
- }
- //grid.queryForCollisionPairs().forEach(collision => collide(collision));
- }
- logs.collide.mark();
- // Do entities life
- logs.entities.set();
- for (var e of entities) {
- entitiesliveloop(e);
- }
- logs.entities.mark();
- logs.master.mark();
- // Remove dead entities
- purgeEntities();
- room.lastCycle = util.time();
- //lastTime = curTime;
- };
- //let expected = 1000 / c.gameSpeed / 30;
- //let alphaFactor = (delta > expected) ? expected / delta : 1;
- //roomSpeed = c.gameSpeed * alphaFactor;
- //setTimeout(moveloop, 1000 / roomSpeed / 30 - delta);
- })();
- var funloop = (() => {
- // Fun stuff, like RAINBOWS :D
- function rainbow(my) {
- let rainbow = [12, 2, 3, 11, 10, 14];
- entities.forEach(function (element) {
- if (element.rainbow) {
- if (rainbow.indexOf(element.color) == -1 || element.color == undefined) {
- element.color = 12;
- } else {
- if (element.rainbow_back == false) {
- element.color = rainbow[rainbow.indexOf(element.color) + 1];
- } else {
- element.color = rainbow[rainbow.indexOf(element.color) - 1];
- }
- }
- if (element.color == 14) {
- element.rainbow_back = true;
- }
- if (element.color == 12) {
- element.rainbow_back = false;
- }
- }
- });
- }
- return () => {
- // run the fun stuff :P
- rainbow();
- };
- })();
- // A less important loop. Runs at an actual 5Hz regardless of game speed.
- var poisonLoop = (() => {
- // Fun stuff, like RAINBOWS :D
- function poison(my) {
- entities.forEach(function (element) {
- if (element.showpoison) {
- let x = element.size + 10;
- let y = element.size + 10;
- Math.random() < 0.5 ? x *= -1 : x;
- Math.random() < 0.5 ? y *= -1 : y;
- Math.random() < 0.5 ? x *= Math.random() + 1 : x;
- Math.random() < 0.5 ? y *= Math.random() + 1 : y;
- var o = new Entity({
- x: element.x + x,
- y: element.y + y
- });
- o.define(Class['poisonEffect']);
- }
- if (element.poisoned && (element.type == 'tank' || element.type == 'crasher' || element.type == 'food' || element.type == 'minion' || element.type == 'drone' || element.type == 'swarm')) {
- let x = element.size + 10;
- let y = element.size + 10;
- Math.random() < 0.5 ? x *= -1 : x;
- Math.random() < 0.5 ? y *= -1 : y;
- Math.random() < 0.5 ? x *= Math.random() + 1 : x;
- Math.random() < 0.5 ? y *= Math.random() + 1 : y;
- var o = new Entity({
- x: element.x + x,
- y: element.y + y
- });
- o.define(Class['poisonEffect']);
- if (!element.invuln) {
- element.health.amount -= element.health.max / (45 - element.poisonLevel);
- element.shield.amount -= element.shield.max / (10 - element.poisonLevel);
- }
- element.poisonTime -= 1;
- if (element.poisonTime <= 0) element.poisoned = false;
- if (element.health.amount <= 0 && element.poisonedBy != undefined && element.poisonedBy.skill != undefined) {
- element.poisonedBy.skill.score += Math.ceil(util.getJackpot(element.poisonedBy.skill.score));
- element.poisonedBy.sendMessage('You killed ' + element.name + ' with poison.');
- if (element.poisonedBy.name != null) {
- element.sendMessage('You have been killed by ' + element.poisonedBy.name + ' with poison.');
- } else if (element.poisonedBy.name == null) {
- element.sendMessage('You have been killed with poison.');
- }
- }
- }
- });
- }
- return () => {
- // run the poison
- poison();
- };
- })();
- var maintainloop = (() => {
- // Place obstacles
- function placeRoids() {
- function placeRoid(type, entityClass) {
- let x = 0;
- let position;
- do {
- position = room.randomType(type);
- x++;
- if (x > 200) {
- util.warn("Could not place some roids.");return 0;
- }
- } while (dirtyCheck(position, 10 + entityClass.SIZE));
- let o = new Entity(position);
- o.define(entityClass);
- o.team = -101;
- o.facing = ran.randomAngle();
- o.protect();
- o.life();
- }
- // Start placing them
- let roidcount = room.roid.length * room.width * room.height / room.xgrid / room.ygrid / 50000 / 1.5;
- let rockcount = room.rock.length * room.width * room.height / room.xgrid / room.ygrid / 250000 / 1.5;
- let count = 0;
- for (let i = Math.ceil(roidcount); i; i--) {
- count++;placeRoid('roid', ran.choose([Class.obstacle, Class.obstacle, Class.obstacle, Class.obstacle, Class.autoroid]));
- }
- for (let i = Math.ceil(roidcount * 0.3); i; i--) {
- count++;placeRoid('roid', ran.choose([Class.babyObstacle, Class.babyObstacle, Class.babyObstacle, Class.babyObstacle, Class.autorock]));
- }
- for (let i = Math.ceil(rockcount); i; i--) {
- count++;placeRoid('rock', ran.choose([Class.obstacle, Class.obstacle, Class.obstacle, Class.obstacle, Class.autoroid]));
- }
- for (let i = Math.ceil(rockcount * 0.3); i; i--) {
- count++;placeRoid('rock', ran.choose([Class.babyObstacle, Class.babyObstacle, Class.babyObstacle, Class.babyObstacle, Class.autorock, Class.autosquare]));
- }
- util.log('Placing ' + count + ' obstacles!');
- }
- placeRoids();
- // Spawning functions
- let spawnBosses = (() => {
- let timer = 0;
- let boss = (() => {
- let i = 0,
- names = [],
- bois = [Class.egg],
- n = 0,
- begin = 'yo some shit is about to move to a lower position',
- arrival = 'Something happened lol u should probably let Neph know this broke',
- loc = 'norm';
- let spawn = () => {
- let spot,
- m = 0;
- do {
- spot = room.randomType(loc);m++;
- } while (dirtyCheck(spot, 500) && m < 30);
- let o = new Entity(spot);
- o.define(ran.choose(bois));
- o.team = -100;
- o.name = names[i++];
- };
- return {
- prepareToSpawn: (classArray, number, nameClass, typeOfLocation = 'norm') => {
- n = number;
- bois = classArray;
- loc = typeOfLocation;
- names = ran.chooseBossName(nameClass, number);
- i = 0;
- if (n === 1) {
- begin = 'A visitor is coming.';
- arrival = names[0] + ' has arrived.';
- } else {
- begin = 'Visitors are coming.';
- arrival = '';
- for (let i = 0; i < n - 2; i++) arrival += names[i] + ', ';
- arrival += names[n - 2] + ' and ' + names[n - 1] + ' have arrived.';
- }
- },
- spawn: () => {
- sockets.broadcast(begin);
- for (let i = 0; i < n; i++) {
- setTimeout(spawn, ran.randomRange(3500, 5000));
- }
- // Wrap things up.
- setTimeout(() => sockets.broadcast(arrival), 5000);
- util.log('[SPAWN] ' + arrival);
- }
- };
- })();
- return census => {
- if (timer > 500 && ran.dice(510 - timer)) {
- util.log('[SPAWN] Preparing to spawn...');
- timer = 0;
- let choice = [];
- switch (ran.chooseChance(1, 1, 1, 1)) {
- case 0:
- choice = [[Class.palisade], 1, 'castle', 'norm'];
- sockets.broadcast('A strange trembling...');
- break;
- case 1:
- sockets.broadcast('Rifles Unite...');
- choice = [[Class.riflespin], 2, 'castle', 'norm'];
- break;
- case 2:
- choice = [[Class.extradice], 3, 'castle', 'norm'];
- sockets.broadcast('Theres a Disturbance in the air...');
- break;
- case 3:
- choice = [[Class.elitesquare], 1, 'castle', 'norm'];
- sockets.broadcast('The Squares are fed up...');
- break;
- }
- boss.prepareToSpawn(...choice);
- setTimeout(boss.spawn, 300);
- // Set the timeout for the spawn functions
- } else if (!census.miniboss) timer++;
- };
- })();
- let spawnCrasher = census => {
- if (ran.chance(1 - 0.8 * census.crasher / room.maxFood / room.nestFoodAmount)) {
- let spot,
- i = 30;
- do {
- spot = room.randomType('nest');i--;if (!i) return 0;
- } while (dirtyCheck(spot, 100));
- let type = ran.dice(80) ? ran.choose([Class.sentryGun, Class.sentrySwarm, Class.sentryTrap]) : Class.crasher;
- let o = new Entity(spot);
- o.define(type);
- o.team = -100;
- }
- };
- // The NPC function
- let makenpcs = (() => {
- // Make base protectors if needed.
- /*let f = (loc, team) => {
- let o = new Entity(loc);
- o.define(Class.baseProtector);
- o.team = -team;
- o.color = [10, 11, 12, 15][team-1];
- };
- for (let i=1; i<5; i++) {
- room['bas' + i].forEach((loc) => { f(loc, i); });
- }*/
- // Return the spawning function
- let bots = [];
- return () => {
- let census = {
- crasher: 0,
- miniboss: 0,
- tank: 0,
- dodecagon: 0
- };
- let npcs = entities.map(function npcCensus(instance) {
- if (census[instance.type] != null) {
- census[instance.type]++;
- return instance;
- }
- }).filter(e => {
- return e;
- });
- // Spawning
- //spawnCrasher(census);
- spawnBosses(census);
- // Bots
- if (bots.length < c.BOTS) {
- let o = new Entity(room.random());
- o.color = 17;
- o.define(Class.bot);
- o.define(Class.basic);
- o.name += ran.chooseBotName();
- o.refreshBodyAttributes();
- o.color = 17;
- bots.push(o);
- }
- // Remove dead ones
- bots = bots.filter(e => {
- return !e.isDead();
- });
- };
- })();
- // The big food function
- let makefood = (() => {
- let food = [],
- foodSpawners = [];
- // The two essential functions
- function getFoodClass(level) {
- let a = {};
- switch (level) {
- case 0:
- a = Class.egg;break;
- case 1:
- a = Class.square;break;
- case "e":
- a = Class.wiffle;break;
- case 2:
- a = Class.triangle;break;
- case 3:
- a = Class.pentagon;break;
- case 4:
- a = Class.bigPentagon;break;
- case 5:
- a = Class.hugePentagon;break;
- case 6:
- a = Class.megaPentagon;break;
- case "h":
- a = Class.hexagon;break;
- case "d":
- a = Class.dodecagon;break;
- default:
- throw 'bad food level';
- }
- if (a !== {}) {
- a.BODY.ACCELERATION = 0.015 / (a.FOOD.LEVEL + 1);
- }
- return a;
- }
- let placeNewFood = (position, scatter, level, allowInNest = false) => {
- let o = nearest(food, position);
- let mitosis = false;
- let seed = false;
- // Find the nearest food and determine if we can do anything with it
- if (o != null) {
- for (let i = 50; i > 0; i--) {
- if (scatter == -1 || util.getDistance(position, o) < scatter) {
- if (ran.dice((o.foodLevel + 1) * (o.foodLevel + 1))) {
- mitosis = true;break;
- } else {
- seed = true;break;
- }
- }
- }
- }
- // Decide what to do
- if (scatter != -1 || mitosis || seed) {
- // Splitting
- if (o != null && (mitosis || seed) && room.isIn('nest', o) === allowInNest) {
- let levelToMake = mitosis ? o.foodLevel : level,
- place = {
- x: o.x + o.size * Math.cos(o.facing),
- y: o.y + o.size * Math.sin(o.facing)
- };
- let new_o = new Entity(place);
- new_o.define(getFoodClass(levelToMake));
- new_o.team = -100;
- new_o.facing = o.facing + ran.randomRange(Math.PI / 2, Math.PI);
- food.push(new_o);
- return new_o;
- }
- // Brand new
- else if (room.isIn('nest', position) === allowInNest) {
- if (!dirtyCheck(position, 20)) {
- o = new Entity(position);
- o.define(getFoodClass(level));
- o.team = -100;
- o.facing = ran.randomAngle();
- food.push(o);
- return o;
- }
- }
- }
- };
- // Define foodspawners
- class FoodSpawner {
- constructor() {
- this.foodToMake = Math.ceil(Math.abs(ran.gauss(0, room.scale.linear * 80)));
- this.size = Math.sqrt(this.foodToMake) * 25;
- // Determine where we ought to go
- let position = {};let o;
- do {
- position = room.gaussRing(1 / 3, 20);
- o = placeNewFood(position, this.size, 0);
- } while (o == null);
- // Produce a few more
- for (let i = Math.ceil(Math.abs(ran.gauss(0, 4))); i <= 0; i--) {
- placeNewFood(o, this.size, 0);
- }
- // Set location
- this.x = o.x;
- this.y = o.y;
- //util.debug('FoodSpawner placed at ('+this.x+', '+this.y+'). Set to produce '+this.foodToMake+' food.');
- }
- rot() {
- if (--this.foodToMake < 0) {
- //util.debug('FoodSpawner rotted, respawning.');
- util.remove(foodSpawners, foodSpawners.indexOf(this));
- foodSpawners.push(new FoodSpawner());
- }
- }
- }
- // Add them
- foodSpawners.push(new FoodSpawner());
- foodSpawners.push(new FoodSpawner());
- foodSpawners.push(new FoodSpawner());
- foodSpawners.push(new FoodSpawner());
- // Food making functions
- let makeGroupedFood = () => {
- // Create grouped food
- // Choose a location around a spawner
- let spawner = foodSpawners[ran.irandom(foodSpawners.length - 1)],
- bubble = ran.gaussRing(spawner.size, 1 / 4);
- placeNewFood({ x: spawner.x + bubble.x, y: spawner.y + bubble.y }, -1, 0);
- spawner.rot();
- };
- let makeDistributedFood = () => {
- // Distribute food everywhere
- //util.debug('Creating new distributed food.');
- let spot = {};
- do {
- spot = room.gaussRing(1 / 2, 2);
- } while (room.isInNorm(spot));
- placeNewFood(spot, 0.01 * room.width, 0);
- };
- let makeCornerFood = () => {
- // Distribute food in the corners
- let spot = {};
- do {
- spot = room.gaussInverse(5);
- } while (room.isInNorm(spot));
- placeNewFood(spot, 0.05 * room.width, 0);
- };
- let makeNestFood = () => {
- // Make nest pentagons
- let spot = room.randomType('nest');
- placeNewFood(spot, 0.01 * room.width, 3, true);
- };
- // Return the full function
- return () => {
- // Find and understand all food
- let census = {
- [0]: 0, // Egg
- [1]: 0, // Square
- [2]: 0, // Triangle
- [3]: 0, // Penta
- [4]: 0, // Beta
- [5]: 0, // Alpha
- [6]: 0, // Gamma
- [7]: 0,
- tank: 0,
- sum: 0
- };
- let censusNest = {
- [0]: 0, // Egg
- [1]: 0, // Square
- [2]: 0, // Triangle
- [3]: 0, // Penta
- [4]: 0, // Beta
- [5]: 0, // Alpha
- [6]: 0, // Gamma
- [7]: 0,
- sum: 0
- };
- // Do the censusNest
- food = entities.map(instance => {
- try {
- if (instance.type === 'tank') {
- census.tank++;
- } else if (instance.foodLevel > -1) {
- if (room.isIn('nest', { x: instance.x, y: instance.y })) {
- censusNest.sum++;censusNest[instance.foodLevel]++;
- } else {
- census.sum++;census[instance.foodLevel]++;
- }
- return instance;
- }
- } catch (err) {
- util.error(instance.label);util.error(err);instance.kill();
- }
- }).filter(e => {
- return e;
- });
- // Sum it up
- let maxFood = 1 + room.maxFood + 15 * census.tank;
- let maxNestFood = 1 + room.maxFood * room.nestFoodAmount;
- let foodAmount = census.sum;
- let nestFoodAmount = censusNest.sum;
- /*********** ROT OLD SPAWNERS **********/
- //foodSpawners.forEach(spawner => { if (ran.chance(1 - foodAmount/maxFood)) spawner.rot(); });
- let i = 0;
- const length = foodSpawners.length;
- for (; i < length; i++) {
- if (ran.chance(1 - foodAmount / maxFood)) {
- foodSpawners[i].rot();
- }
- }
- /************** MAKE FOOD **************/
- while (ran.chance(0.8 * (1 - foodAmount * foodAmount / maxFood / maxFood))) {
- switch (ran.chooseChance(10, 2, 1)) {
- case 0:
- makeGroupedFood();break;
- case 1:
- makeDistributedFood();break;
- case 2:
- makeCornerFood();break;
- }
- }
- while (ran.chance(0.5 * (1 - nestFoodAmount * nestFoodAmount / maxNestFood / maxNestFood))) makeNestFood();
- /************* UPGRADE FOOD ************/
- if (!food.length) return 0;
- for (let i = Math.ceil(food.length / 100); i > 0; i--) {
- let o = food[ran.irandom(food.length - 1)],
- // A random food instance
- oldId = -1000,
- overflow,
- location;
- // Bounce 6 times
- for (let j = 0; j < 6; j++) {
- overflow = 10;
- // Find the nearest one that's not the last one
- do {
- o = nearest(food, { x: ran.gauss(o.x, 30), y: ran.gauss(o.y, 30) });
- } while (o.id === oldId && --overflow);
- if (!overflow) continue;
- // Configure for the nest if needed
- let proportions = c.FOOD,
- cens = census,
- amount = foodAmount;
- if (room.isIn('nest', o)) {
- proportions = c.FOOD_NEST;
- cens = censusNest;
- amount = nestFoodAmount;
- }
- // Upgrade stuff
- o.foodCountup += Math.ceil(Math.abs(ran.gauss(0, 10)));
- while (o.foodCountup >= (o.foodLevel + 1) * 100) {
- o.foodCountup -= (o.foodLevel + 1) * 100;
- if (ran.chance(1 - cens[o.foodLevel + 1] / amount / proportions[o.foodLevel + 1])) {
- o.define(getFoodClass(o.foodLevel + 1));
- }
- }
- }
- }
- };
- })();
- // Define food and food spawning
- return () => {
- // Do stuff
- makenpcs();
- makefood();
- // Regen health and update the grid
- let i = 0;
- const length = entities.length;
- //for (var instance of entities) {
- for (; i < length; i++) {
- if (entities[i].shield.max) {
- entities[i].shield.regenerate();
- }
- if (entities[i].health.amount) {
- entities[i].health.regenerate(entities[i].shield.max && entities[i].shield.max === entities[i].shield.amount);
- }
- };
- };
- })();
- // This is the checking loop. Runs at 1Hz.
- var speedcheckloop = (() => {
- let fails = 0;
- // Return the function
- return () => {
- let activationtime = logs.activation.sum(),
- collidetime = logs.collide.sum(),
- movetime = logs.entities.sum(),
- playertime = logs.network.sum(),
- maptime = logs.minimap.sum(),
- physicstime = logs.physics.sum(),
- lifetime = logs.life.sum(),
- selfietime = logs.selfie.sum();
- let sum = logs.master.record();
- let loops = logs.loops.count(),
- active = logs.entities.count();
- global.fps = 1000 / sum;
- if (sum > 1000 / roomSpeed / global.fps) {
- //fails++;
- util.warn('~~ LOOPS: ' + loops + '. ENTITY #: ' + entities.length + '//' + Math.round(active / loops) + '. VIEW #: ' + views.length + '. BACKLOGGED :: ' + (sum * roomSpeed * 3).toFixed(3) + '%! ~~');
- util.warn('Total activation time: ' + activationtime);
- util.warn('Total collision time: ' + collidetime);
- util.warn('Total cycle time: ' + movetime);
- util.warn('Total player update time: ' + playertime);
- util.warn('Total lb+minimap processing time: ' + maptime);
- util.warn('Total entity physics calculation time: ' + physicstime);
- util.warn('Total entity life+thought cycle time: ' + lifetime);
- util.warn('Total entity selfie-taking time: ' + selfietime);
- util.warn('Total time: ' + (activationtime + collidetime + movetime + playertime + maptime + physicstime + lifetime + selfietime));
- if (fails > 60) {
- util.error("FAILURE!");
- //process.exit(1);
- }
- } else {
- fails = 0;
- }
- };
- })();
- /** BUILD THE SERVERS **/
- // Turn the server on
- var server = http.createServer(app);
- var websockets = (() => {
- // Configure the websocketserver
- let config = { server: server };
- if (c.servesStatic) {
- server.listen(c.port, function httpListening() {
- util.log(new Date() + ". Joint HTTP+Websocket server turned on, listening on port " + server.address().port + ".");
- });
- } else {
- config.port = c.port;
- util.log(new Date() + 'Websocket server turned on, listening on port ' + c.port + '.');
- }
- // Build it
- return new WebSocket.Server(config);
- })().on('connection', sockets.connect);
- var tickLengthMs = 1000 / 20;
- var previousTick = now();
- var actualTicks = 0;
- var gameexecution = function () {
- var current = now();
- actualTicks++;
- if (previousTick + tickLengthMs <= current) {
- //var delta = (current - previousTick) / 1000;
- var curTime = now();
- timestep = 0.01575 * (curTime - lastTime);
- if (timestep <= 0 || timestep > 7.875) {
- timestep = 0.01575;
- }
- previousTick = current;
- gameloop();
- actualTicks = 0;
- lastTime = curTime;
- }
- if (now() - previousTick < tickLengthMs - 50) {
- timer.setTimeout(gameexecution, '', '50m');
- } else {
- //timer.setTimeout(gameexecution, '', '1m');
- setImmediate(gameexecution);
- }
- };
- // Bring it to life
- gameexecution();
- //setInterval(gameloop, room.cycleSpeed);
- setInterval(maintainloop, 100);
- setInterval(speedcheckloop, 1000);
- setInterval(poisonLoop, room.cycleSpeed * 20);
- // Graceful shutdown
- let shutdownWarning = false;
- if (process.platform === "win32") {
- var rl = require("readline").createInterface({
- input: process.stdin,
- output: process.stdout
- });
- rl.on("SIGINT", () => {
- process.emit("SIGINT");
- });
- }
- process.on("SIGINT", () => {
- if (!shutdownWarning) {
- shutdownWarning = true;
- sockets.broadcast("The server is shutting down.");
- util.log('Server going down! Warning broadcasted.');
- setTimeout(() => {
- sockets.broadcast("Arena closed.");
- util.log('Final warning broadcasted.');
- setTimeout(() => {
- util.warn('Process ended.');
- process.exit();
- }, 3000);
- }, 17000);
- }
- });
- const Eris = require('eris');
- const bot = new Eris(process.env.bot_token);
- bot.on('ready', () => {
- console.log('\nBot ready!\n');
- var canLogToDiscord = true;
- });
- var unauth = '```patch\n- ERROR: UNATHORIZED USER```';
- var mliststring = '';
- var mliststring2 = '';
- /*mlist.forEach(function(element) {
- if (mliststring.length < 1970) {
- mliststring += element += ', ';
- } else {
- mliststring2 += element += ', '
- }
- });*/
- function parse(input) {
- let out = input.split(" ");
- return out;
- }
- bot.on('messageCreate', msg => {
- try {
- if (msg.author.id != 452793079238361089) {
- if (msg.content.startsWith("k!select ")) {
- if (process.env.ISONGLITCH == undefined) {
- let sendError = true;
- let lookfor = msg.content.split("k!select ").pop();
- function thing(element) {
- if (typeof element.sendMessage == "function" && element.name == lookfor) {
- sendError = false;
- bot.createMessage(msg.channel.id, String(element.name + '\nTank: ' + element.label + '\nId: ' + element.id + '\nAlpha: ' + element.alpha + '\nColor: ' + element.blend.color + '\nMax Health: ' + element.health.max + '\nCurrent Health: ' + element.health.amount + '\nIs Invulnerable: ' + element.invuln + '\nScore: ' + element.photo.score + '\nLevel: ' + element.skill.level));
- }
- }
- for (var element of entities) {
- thing(element);
- }
- if (sendError) {
- bot.createMessage(msg.channel.id, "Was unable to find an entity by that name");
- }
- }
- }
- if (msg.content == 'k!ping') {
- bot.createMessage(msg.channel.id, 'Pong!\n' + "\nConnections amount: " + global.extPlayers + "\nRunning on glitch: " + process.env.ISONGLITCH + "\nDirectory: " + __dirname + "\nFile name: " + __filename);
- }
- if (msg.content == 'k!list') {
- //if (process.env.ISONGLITCH == undefined) {
- bot.createMessage(msg.channel.id, mliststring);
- bot.createMessage(msg.channel.id, mliststring2);
- } //}
- if (msg.content.includes('k!help')) {
- if (process.env.ISONGLITCH == undefined) {
- bot.createMessage(msg.channel.id, '***COMMANDS*** \nPrefix: k! \n(No space after k! when running command) \n \n**ping** - tells u if the server is running\n**kill** - kills the server (Authorization required)\n**broadcast** *<message>* - broadcasts a message (Authorization required)\n**list** - Lists all tanks as their internal names\n**query** *<internalname>* - returns some data about a tank (use list first to get tank names that this will accept)\n**select** *<name>* - returns some data about in-game users\n**summon** *<type>* *<class>* - summons a thing\n**banish** *<id>* - Banishes a player (Authorization required)\n**players** - list in-game players\n**stat** *<id> <path to stat> <new value>* - modifies a stat (Authorization required)');
- }
- }
- if (msg.content == 'k!kill') {
- if (msg.author.id == 181829457852628993) {
- console.log("\n SERVER TERMINATED BY AN AUTHORIZED USER \n");
- bot.createMessage(msg.channel.id, 'Terminating.....');
- process.emit("SIGINT");
- } else {
- console.log("Unauthorized user", msg.author.username, "tried to end server");
- bot.createMessage(msg.channel.id, unauth);
- }
- }
- if (msg.content.startsWith('k!broadcast')) {
- if (msg.author.id == 181829457852628993 || 340270483838599168) {
- if (process.env.ISONGLITCH == undefined) {
- sockets.broadcast(msg.content.split("k!broadcast").pop() + " - " + msg.author.username);
- bot.createMessage(msg.channel.id, 'Message Broadcasted and rendered.');
- }
- } else {
- console.log("Unauthorized user", msg.author.username, "tried to broadcast a message");
- bot.createMessage(msg.channel.id, unauth);
- }
- }
- if (msg.content.startsWith('k!query')) {
- if (process.env.ISONGLITCH == undefined) {
- let output = '';
- var query = msg.content.split(">query ").pop();
- try {
- var botreturn = eval('Class.' + query);
- for (var key in botreturn) {
- if (output.length > 500) {
- console.log(output.length);bot.createMessage(msg.channel.id, output);output = '';
- }
- output += String(key) + ': ' + eval('Class.' + query + '.' + String(key)) + '\n';
- var returned = typeof eval('Class.' + query + '.' + String(key));
- if (returned == 'object') {
- for (var key2 in eval('Class.' + query + '.' + String(key))) {
- if (key2 != 'remove') {
- try {
- output += "^ " + String(key2) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']') + '\n';
- var returned = typeof eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']');
- var returnedobj = eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']');
- } catch (err) {
- output += "^ " + String(key2) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2)) + '\n';
- var returned = typeof eval('Class.' + query + '.' + String(key) + '.' + String(key2));
- var returnedobj = eval('Class.' + query + '.' + String(key) + '.' + String(key2));
- }
- if (returned == 'object') {
- for (var key3 in returnedobj) {
- if (key3 != 'remove') {
- try {
- output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']' + '[' + String(key3) + ']') + '\n';
- } catch (err) {
- try {
- output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']' + '.' + String(key3)) + '\n';
- } catch (err) {
- try {
- output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2) + '[' + String(key3) + ']') + '\n';
- } catch (err) {
- output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2) + '.' + String(key3)) + '\n';
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- } catch (err) {
- bot.createMessage(msg.channel.id, String(err));
- }
- bot.createMessage(msg.channel.id, output);
- }
- }
- /*if (msg.content.startsWith('k!summon ')) {
- if (msg.author.id == 181829457852628993) {
- var spawnClass = msg.content.split("k!summon ").pop().substr(0, 3)
- console.log(msg.content.split("k!summon ").pop().substr(0, 3))
- var type = msg.content.split(" ").pop()
- if (spawnClass == 'bot') {
- botSpawn = type
- bot.createMessage(msg.channel.id, "Next bot will be a " + type);
- } else if (spawnClass == 'food') {
- } else {
- bot.createMessage(msg.channel.id, "Was unable to complete request, unknown summon type: " + spawnClass);
- }
- } else {
- console.log("Unauthorized user", msg.author.username, "tried to broadcast")
- bot.createMessage(msg.channel.id, unauth);
- }
- }*/
- if (msg.content == 'k!players') {
- let output = '';
- let outWillFail = true;
- //entities.forEach(function(element) {
- //if (typeof element.sendMessage == "function" && element.name != '') {
- // output += String(element.name + ' - ' + element.id + '\n')
- // outWillFail = false
- //}
- //})
- let i = 0;
- const length = entities.length;
- for (; i < length; i++) {
- //for (let instance of entities) {
- let instance = entities[i];
- if (typeof instance.sendMessage == "function" && instance.name != '') {
- output += String(instance.name + ' - ' + instance.id + '\n');
- outWillFail = false;
- }
- }
- if (!outWillFail) {
- bot.createMessage(msg.channel.id, output);
- } else {
- bot.createMessage(msg.channel.id, "There are currently no players on the server");
- }
- }
- if (msg.content.startsWith('k!stat ')) {
- if (msg.author.id == 181829457852628993 || 340270483838599168 || 350336602956103683) {
- let s_command = parse(msg.content);
- let s_lookForId = s_command[1];
- let s_statpath = s_command[2];
- let s_newvalue = s_command[3];
- entities.forEach(function (element) {
- if (element.id == s_lookForId) {
- try {
- eval('element' + s_statpath + ' = ' + s_newvalue);
- } catch (err) {
- eval('element' + s_statpath + ' = "' + s_newvalue + '"');
- }
- bot.createMessage(msg.channel.id, "Value set to " + String(eval('element' + s_statpath)));
- }
- });
- } else {
- bot.createMessage(msg.channel.id, unauth);
- }
- }
- if (msg.content.startsWith('k!define ')) {
- let printerror = true;
- let command = parse(msg.content);
- let inputid = command[1];
- let inputclass = command[2];
- if (msg.author.id == 181829457852628993 || 340270483838599168) {
- if (eval(Class[inputclass]) != undefined) {
- entities.filter(r => r.id == inputid)[0].define(Class[inputclass]);
- printerror = false;
- bot.createMessage(msg.channel.id, 'Defined user as Class.' + inputclass);
- } else {
- bot.createMessage(msg.channel.id, inputclass + ' is not a valid tank');
- }
- if (printerror) {
- bot.createMessage(msg.channel.id, "Count find any users by the id: " + inputid);
- }
- } else {
- bot.createMessage(msg.channel.id, unauth);
- }
- }
- }
- } catch (err) {
- // log the error in chat
- bot.createMessage(msg.channel.id, String(err));
- }
- });
- bot.editStatus('online', {
- name: 'Type k!help for commands!',
- type: 0
- });
- bot.connect();exports.circleArea = function (radius) {
- return Math.pow(radius, 2) * Math.PI;
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement