Advertisement
Guest User

Untitled

a guest
May 25th, 2019
298
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 250.30 KB | None | 0 0
  1. /*jslint node: true */
  2. /*jshint -W061 */
  3. /*global goog, Map, let */
  4. "use strict";
  5.  
  6. // General requires
  7. require('google-closure-library');
  8. goog.require('goog.structs.PriorityQueue');
  9. goog.require('goog.structs.QuadTree');
  10.  
  11. // Import game settings.
  12. const c = require('../../config.json');
  13.  
  14. // Import utilities.
  15. const util = require('./lib/util');
  16. const ran = require('./lib/random');
  17. const hshg = require('./lib/hshg');
  18. const now = require('performance-now');
  19. const nano = require('nanotimer');
  20.  
  21. let timer = new nano();
  22.  
  23.  
  24. // Let's get a cheaper array removal thing
  25. Array.prototype.remove = index => {
  26. if(index === this.length - 1){
  27. return this.pop();
  28. } else {
  29. let r = this[index];
  30. this[index] = this.pop();
  31. return r;
  32. }
  33. };
  34.  
  35. var placedDodecagon = false;
  36.  
  37. // Define player keys
  38.  
  39. var keys = [
  40. 'ttoken1',
  41. 'ttoken2jerky',
  42. 'treejerky',
  43. 'treebacon',
  44. 'baconjerkey',
  45. 'treedirt',
  46. 'bacontreecatjerky',
  47. 'vector',
  48. 'fishy',
  49. ':b:uff:b:ate',
  50. 'panda',
  51. ];
  52.  
  53.  
  54.  
  55. global.fps = "Unknown";
  56. var roomSpeed = c.gameSpeed;
  57.  
  58. let lastTime = now();
  59. let timestep = 1;
  60.  
  61. var previousTick = now();
  62.  
  63. const room = {
  64. lastCycle: undefined,
  65. cycleSpeed: 1000 / roomSpeed / 20,
  66. width: c.WIDTH,
  67. height: c.HEIGHT,
  68. setup: c.ROOM_SETUP,
  69. xgrid: c.X_GRID,
  70. ygrid: c.Y_GRID,
  71. gameMode: c.MODE,
  72. skillBoost: c.SKILL_BOOST,
  73. scale: {
  74. square: c.WIDTH * c.HEIGHT / 100000000,
  75. linear: Math.sqrt(c.WIDTH * c.HEIGHT / 100000000),
  76. },
  77. maxFood: c.WIDTH * c.HEIGHT / 100000 * c.FOOD_AMOUNT,
  78. isInRoom: location => {
  79. return (location.x < 0 || location.x > c.WIDTH || location.y < 0 || location.y > c.HEIGHT) ? (
  80. false
  81. ) : (
  82. true
  83. );
  84. },
  85. topPlayerID: -1,
  86. };
  87. room.findType = type => {
  88. let output = [];
  89. let j = 0;
  90. let x = 0;
  91. const lengthrow = room.setup.length;
  92. //room.setup.forEach(row => {
  93. for (; x < lengthrow; x++) {
  94. let i = 0;
  95. let y = 0;
  96. const lengthcell = room.setup[x].length;
  97. //row.forEach(cell => {
  98. for (; y < lengthcell; y++) {
  99. if (room.setup[x][y] === type) {
  100. output.push({ x: (i + 0.5) * room.width / room.xgrid, y: (j + 0.5) * room.height / room.ygrid, });
  101. }
  102. i++;
  103. }
  104. j++;
  105. }
  106. room[type] = output;
  107. };
  108. room.findType('nest');
  109. room.findType('norm');
  110. room.findType('bas1');
  111. room.findType('bas2');
  112. room.findType('bas3');
  113. room.findType('bas4');
  114. room.findType('bas5');
  115. room.findType('bas6');
  116. room.findType('roid');
  117. room.findType('rock');
  118. room.nestFoodAmount = 1.5 * Math.sqrt(room.nest.length) / room.xgrid / room.ygrid;
  119. room.random = () => {
  120. return {
  121. x: ran.irandom(room.width),
  122. y: ran.irandom(room.height),
  123. };
  124. };
  125. room.randomType = type => {
  126. let selection = room[type][ran.irandom(room[type].length-1)];
  127. return {
  128. x: ran.irandom(0.5*room.width/room.xgrid) * ran.choose([-1, 1]) + selection.x,
  129. y: ran.irandom(0.5*room.height/room.ygrid) * ran.choose([-1, 1]) + selection.y,
  130. };
  131. };
  132. room.gauss = clustering => {
  133. let output;
  134. do {
  135. output = {
  136. x: ran.gauss(room.width/2, room.height/clustering),
  137. y: ran.gauss(room.width/2, room.height/clustering),
  138. };
  139. } while (!room.isInRoom(output));
  140. };
  141. room.gaussInverse = clustering => {
  142. let output;
  143. do {
  144. output = {
  145. x: ran.gaussInverse(0, room.width, clustering),
  146. y: ran.gaussInverse(0, room.height, clustering),
  147. };
  148. } while (!room.isInRoom(output));
  149. return output;
  150. };
  151. room.gaussRing = (radius, clustering) => {
  152. let output;
  153. do {
  154. output = ran.gaussRing(room.width * radius, clustering);
  155. output = {
  156. x: output.x + room.width/2,
  157. y: output.y + room.height/2,
  158. };
  159. } while (!room.isInRoom(output));
  160. return output;
  161. };
  162. room.isIn = (type, location) => {
  163. if (room.isInRoom(location)) {
  164. let a = Math.floor(location.y * room.ygrid / room.height);
  165. let b = Math.floor(location.x * room.xgrid / room.width);
  166. return type === room.setup[a][b];
  167. } else {
  168. return false;
  169. }
  170. };
  171. room.isInNorm = location => {
  172. if (room.isInRoom(location)) {
  173. let a = Math.floor(location.y * room.ygrid / room.height);
  174. let b = Math.floor(location.x * room.xgrid / room.width);
  175. let v = room.setup[a][b];
  176. return v !== 'nest';
  177. } else {
  178. return false;
  179. }
  180. };
  181. room.gaussType = (type, clustering) => {
  182. let selection = room[type][ran.irandom(room[type].length-1)];
  183. let location = {};
  184. do {
  185. location = {
  186. x: ran.gauss(selection.x, room.width/room.xgrid/clustering),
  187. y: ran.gauss(selection.y, room.height/room.ygrid/clustering),
  188. };
  189. } while (!room.isIn(type, location));
  190. return location;
  191. };
  192. util.log(room.width + ' x ' + room.height + ' room initalized. Max food: ' + room.maxFood + ', max nest food: ' + (room.maxFood * room.nestFoodAmount) + '.');
  193.  
  194. // Define a vector
  195. class Vector {
  196. constructor(x, y) { //Vector constructor.
  197. this.x = x;
  198. this.y = y;
  199. }
  200.  
  201. update() {
  202. this.len = this.length;
  203. this.dir = this.direction;
  204. }
  205.  
  206. get length() {
  207. return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
  208. }
  209.  
  210. get direction() {
  211. return Math.atan2(this.y, this.x);
  212. }
  213. }
  214. function nullVector(v) {
  215. v.x = 0; v.y = 0; //this guy's useful
  216. }
  217.  
  218. // Get class definitions and index them
  219. var Class = (() => {
  220. let def = require('./lib/definitions'),
  221. i = 0;
  222. for (let k in def) {
  223. if (!def.hasOwnProperty(k)) continue;
  224. def[k].index = i++;
  225. }
  226. return def;
  227. })();
  228.  
  229. // Define IOs (AI)
  230. function nearest(array, location, test = () => { return true; }) {
  231. let list = new goog.structs.PriorityQueue();
  232. let d;
  233. if (!array.length) {
  234. return undefined;
  235. }
  236. array.forEach(function(instance) {
  237. d = Math.pow(instance.x - location.x, 2) + Math.pow(instance.y - location.y, 2);
  238. if (test(instance, d)) {
  239. list.enqueue(d, instance);
  240. }
  241. });
  242. return list.dequeue();
  243. }
  244. function timeOfImpact(p, v, s) {
  245. // Requires relative position and velocity to aiming point
  246. let a = s * s - (v.x * v.x + v.y * v.y);
  247. let b = p.x * v.x + p.y * v.y;
  248. let c = p.x * p.x + p.y * p.y;
  249.  
  250. let d = b * b + a * c;
  251.  
  252. let t = 0;
  253. if (d >= 0) {
  254. t = Math.max(0, (b + Math.sqrt(d)) / a);
  255. }
  256.  
  257. return t*0.9;
  258. }
  259.  
  260. class IO {
  261. constructor(body) {
  262. this.body = body;
  263. this.acceptsFromTop = true;
  264. }
  265.  
  266. think() {
  267. return {
  268. target: null,
  269. goal: null,
  270. fire: null,
  271. main: null,
  272. alt: null,
  273. power: null,
  274. };
  275. }
  276. }
  277. class io_doNothing extends IO {
  278. constructor(body) {
  279. super(body);
  280. this.acceptsFromTop = false;
  281. }
  282.  
  283. think() {
  284. return {
  285. goal: {
  286. x: this.body.x,
  287. y: this.body.y,
  288. },
  289. main: false,
  290. alt: false,
  291. fire: false,
  292. };
  293. }
  294. }
  295. class io_moveInCircles extends IO {
  296. constructor(body) {
  297. super(body);
  298. this.acceptsFromTop = false;
  299. this.timer = ran.irandom(10) + 3;
  300. this.goal = {
  301. x: this.body.x + 10*Math.cos(-this.body.facing),
  302. y: this.body.y + 10*Math.sin(-this.body.facing),
  303. };
  304. }
  305.  
  306. think() {
  307. if (!(this.timer--)) {
  308. this.timer = 10;
  309. this.goal = {
  310. x: this.body.x + 10*Math.cos(-this.body.facing),
  311. y: this.body.y + 10*Math.sin(-this.body.facing),
  312. };
  313. }
  314. return { goal: this.goal };
  315. }
  316. }
  317. class io_listenToPlayer extends IO {
  318. constructor(b, p) {
  319. super(b);
  320. this.player = p;
  321. this.acceptsFromTop = false;
  322. }
  323.  
  324. // THE PLAYER MUST HAVE A VALID COMMAND AND TARGET OBJECT
  325.  
  326. think() {
  327. let targ = {
  328. x: this.player.target.x,
  329. y: this.player.target.y,
  330. };
  331. if (this.player.command.autospin) {
  332. let kk = Math.atan2(this.body.control.target.y, this.body.control.target.x) + 0.02;
  333. targ = {
  334. x: 100 * Math.cos(kk),
  335. y: 100 * Math.sin(kk),
  336. };
  337. }
  338. if (this.body.invuln) {
  339. if (this.player.command.right || this.player.command.left || this.player.command.up || this.player.command.down || this.player.command.lmb) {
  340. this.body.invuln = false;
  341. }
  342. }
  343. this.body.autoOverride = this.player.command.override;
  344. return {
  345. target: targ,
  346. goal: {
  347. x: this.body.x + this.player.command.right - this.player.command.left,
  348. y: this.body.y + this.player.command.down - this.player.command.up,
  349. },
  350. fire: this.player.command.lmb || this.player.command.autofire,
  351. main: this.player.command.lmb || this.player.command.autospin || this.player.command.autofire,
  352. alt: this.player.command.rmb,
  353. };
  354. }
  355. }
  356. class io_mapTargetToGoal extends IO {
  357. constructor(b) {
  358. super(b);
  359. }
  360.  
  361. think(input) {
  362. if (input.main || input.alt) {
  363. return {
  364. goal: {
  365. x: input.target.x + this.body.x,
  366. y: input.target.y + this.body.y,
  367. },
  368. power: 1,
  369. };
  370. }
  371. }
  372. }
  373. class io_boomerang extends IO {
  374. constructor(b) {
  375. super(b);
  376. this.r = 0;
  377. this.b = b;
  378. this.m = b.master;
  379. this.turnover = false;
  380. let len = 10 * util.getDistance({x: 0, y:0}, b.master.control.target);
  381. this.myGoal = {
  382. x: 3 * b.master.control.target.x + b.master.x,
  383. y: 3 * b.master.control.target.y + b.master.y,
  384. };
  385. }
  386. think(input) {
  387. if (this.b.range > this.r) this.r = this.b.range;
  388. 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;
  389. if (!this.turnover) {
  390. if (this.r && this.b.range < this.r * 0.5) { this.turnover = true; }
  391. return {
  392. goal: this.myGoal,
  393. power: t,
  394. };
  395. } else {
  396. return {
  397. goal: {
  398. x: this.m.x,
  399. y: this.m.y,
  400. },
  401. power: t,
  402. };
  403. }
  404. }
  405. }
  406. class io_goToMasterTarget extends IO {
  407. constructor(body) {
  408. super(body);
  409. this.myGoal = {
  410. x: body.master.control.target.x + body.master.x,
  411. y: body.master.control.target.y + body.master.y,
  412. };
  413. this.countdown = 5;
  414. }
  415.  
  416. think() {
  417. if (this.countdown) {
  418. if (util.getDistance(this.body, this.myGoal) < 1) { this.countdown--; }
  419. return {
  420. goal: {
  421. x: this.myGoal.x,
  422. y: this.myGoal.y,
  423. },
  424. };
  425. }
  426. }
  427. }
  428. class io_canRepel extends IO {
  429. constructor(b) {
  430. super(b);
  431. }
  432.  
  433. think(input) {
  434. if (input.alt && input.target) {
  435. return {
  436. target: {
  437. x: -input.target.x,
  438. y: -input.target.y,
  439. },
  440. main: true,
  441. };
  442. }
  443. }
  444. }
  445. class io_alwaysFire extends IO {
  446. constructor(body) {
  447. super(body);
  448. }
  449.  
  450. think() {
  451. return {
  452. fire: true,
  453. };
  454. }
  455. }
  456. class io_targetSelf extends IO {
  457. constructor(body) {
  458. super(body);
  459. }
  460.  
  461. think() {
  462. return {
  463. main: true,
  464. target: { x: 0, y: 0, },
  465. };
  466. }
  467. }
  468. class io_mapAltToFire extends IO {
  469. constructor(body) {
  470. super(body);
  471. }
  472.  
  473. think(input) {
  474. if (input.alt) {
  475. return {
  476. fire: true,
  477. };
  478. }
  479. }
  480. }
  481. class io_onlyAcceptInArc extends IO {
  482. constructor(body) {
  483. super(body);
  484. }
  485.  
  486. think(input) {
  487. if (input.target && this.body.firingArc != null) {
  488. if (Math.abs(util.angleDifference(Math.atan2(input.target.y, input.target.x), this.body.firingArc[0])) >= this.body.firingArc[1]) {
  489. return {
  490. fire: false,
  491. alt: false,
  492. main: false,
  493. };
  494. }
  495. }
  496. }
  497. }
  498. class io_nearestDifferentMaster extends IO {
  499. constructor(body) {
  500. super(body);
  501. this.targetLock = undefined;
  502. this.tick = ran.irandom(30);
  503. this.lead = 0;
  504. this.validTargets = this.buildList(body.fov);
  505. this.oldHealth = body.health.display();
  506. }
  507.  
  508. buildList(range) {
  509. // Establish whom we judge in reference to
  510. let m = { x: this.body.x, y: this.body.y, },
  511. mm = { x: this.body.master.master.x, y: this.body.master.master.y, },
  512. mostDangerous = 0,
  513. sqrRange = range * range,
  514. keepTarget = false;
  515. // Filter through everybody...
  516. let out = entities.map(e => {
  517. // Only look at those within our view, and our parent's view, not dead, not our kind, not a bullet/trap/block etc
  518. if (e.health.amount > 0) {
  519. if (!e.invuln) {
  520. if (e.alpha > 0.015) {
  521. if (e.master.master.team !== this.body.master.master.team) {
  522. if (e.master.master.team !== -101) {
  523. if (e.type === 'tank' || e.type === 'crasher' || (!this.body.aiSettings.shapefriend && e.type === 'food')) {
  524. if (Math.abs(e.x - m.x) < range && Math.abs(e.y - m.y) < range) {
  525. if (!this.body.aiSettings.blind || (Math.abs(e.x - mm.x) < range && Math.abs(e.y - mm.y) < range)) return e;
  526. } } } } } } }
  527. }).filter((e) => { return e; });
  528.  
  529. if (!out.length) return [];
  530.  
  531. out = out.map((e) => {
  532. // Only look at those within range and arc (more expensive, so we only do it on the few)
  533. let yaboi = false;
  534. if (Math.pow(this.body.x - e.x, 2) + Math.pow(this.body.y - e.y, 2) < sqrRange) {
  535. if (this.body.firingArc == null || this.body.aiSettings.view360) {
  536. yaboi = true;
  537. } else if (Math.abs(util.angleDifference(util.getDirection(this.body, e), this.body.firingArc[0])) < this.body.firingArc[1]) yaboi = true;
  538. }
  539. if (yaboi) {
  540. mostDangerous = Math.max(e.dangerValue, mostDangerous);
  541. return e;
  542. }
  543. }).filter((e) => {
  544. // Only return the highest tier of danger
  545. if (e != null) { if (this.body.aiSettings.farm || e.dangerValue === mostDangerous) {
  546. if (this.targetLock) { if (e.id === this.targetLock.id) keepTarget = true; }
  547. return e;
  548. } }
  549. });
  550. // Reset target if it's not in there
  551. if (!keepTarget) this.targetLock = undefined;
  552. return out;
  553. }
  554.  
  555. think(input) {
  556. // Override target lock upon other commands
  557. if (input.main || input.alt || this.body.master.autoOverride) {
  558. this.targetLock = undefined; return {};
  559. }
  560. // Otherwise, consider how fast we can either move to ram it or shoot at a potiential target.
  561. let tracking = this.body.topSpeed,
  562. range = this.body.fov;
  563. // Use whether we have functional guns to decide
  564. for (let i=0; i<this.body.guns.length; i++) {
  565. if (this.body.guns[i].canShoot && !this.body.aiSettings.skynet) {
  566. let v = this.body.guns[i].getTracking();
  567. tracking = v.speed;
  568. range = Math.min(range, v.speed * v.range);
  569. break;
  570. }
  571. }
  572. // Check if my target's alive
  573. if (this.targetLock) { if (this.targetLock.health.amount <= 0) {
  574. this.targetLock = undefined;
  575. this.tick = 100;
  576. } }
  577. // Think damn hard
  578. if (this.tick++ > 15 * roomSpeed) {
  579. this.tick = 0;
  580. this.validTargets = this.buildList(range);
  581. // Ditch our old target if it's invalid
  582. if (this.targetLock && this.validTargets.indexOf(this.targetLock) === -1) {
  583. this.targetLock = undefined;
  584. }
  585. // Lock new target if we still don't have one.
  586. if (this.targetLock == null && this.validTargets.length) {
  587. this.targetLock = (this.validTargets.length === 1) ? this.validTargets[0] : nearest(this.validTargets, { x: this.body.x, y: this.body.y });
  588. this.tick = -90;
  589. }
  590. }
  591. // Lock onto whoever's shooting me.
  592. // let damageRef = (this.body.bond == null) ? this.body : this.body.bond;
  593. // if (damageRef.collisionArray.length && damageRef.health.display() < this.oldHealth) {
  594. // this.oldHealth = damageRef.health.display();
  595. // if (this.validTargets.indexOf(damageRef.collisionArray[0]) === -1) {
  596. // this.targetLock = (damageRef.collisionArray[0].master.id === -1) ? damageRef.collisionArray[0].source : damageRef.collisionArray[0].master;
  597. // }
  598. // }
  599. // Consider how fast it's moving and shoot at it
  600. if (this.targetLock != null) {
  601. let radial = this.targetLock.velocity;
  602. let diff = {
  603. x: this.targetLock.x - this.body.x,
  604. y: this.targetLock.y - this.body.y,
  605. };
  606. /// Refresh lead time
  607. if (this.tick % 4 === 0) {
  608. this.lead = 0;
  609. // Find lead time (or don't)
  610. if (!this.body.aiSettings.chase) {
  611. let toi = timeOfImpact(diff, radial, tracking);
  612. this.lead = toi;
  613. }
  614. }
  615. // And return our aim
  616. return {
  617. target: {
  618. x: diff.x + this.lead * radial.x,
  619. y: diff.y + this.lead * radial.y,
  620. },
  621. fire: true,
  622. main: true,
  623. };
  624. }
  625. return {};
  626. }
  627. }
  628. class io_avoid extends IO {
  629. constructor(body) {
  630. super(body);
  631. }
  632.  
  633. think(input) {
  634. let masterId = this.body.master.id;
  635. let range = this.body.size * this.body.size * 100 ;
  636. this.avoid = nearest(
  637. entities,
  638. { x: this.body.x, y: this.body.y },
  639. function(test, sqrdst) {
  640. return (
  641. test.master.id !== masterId &&
  642. (test.type === 'bullet' || test.type === 'drone' || test.type === 'swarm' || test.type === 'trap' || test.type === 'block') &&
  643. sqrdst < range
  644. ); }
  645. );
  646. // Aim at that target
  647. if (this.avoid != null) {
  648. // Consider how fast it's moving.
  649. let delt = new Vector(this.body.velocity.x - this.avoid.velocity.x, this.body.velocity.y - this.avoid.velocity.y);
  650. let diff = new Vector(this.avoid.x - this.body.x, this.avoid.y - this.body.y);
  651. let comp = (delt.x * diff. x + delt.y * diff.y) / delt.length / diff.length;
  652. let goal = {};
  653. if (comp > 0) {
  654. if (input.goal) {
  655. let goalDist = Math.sqrt(range / (input.goal.x * input.goal.x + input.goal.y * input.goal.y));
  656. goal = {
  657. x: input.goal.x * goalDist - diff.x * comp,
  658. y: input.goal.y * goalDist - diff.y * comp,
  659. };
  660. } else {
  661. goal = {
  662. x: -diff.x * comp,
  663. y: -diff.y * comp,
  664. };
  665. }
  666. return goal;
  667. }
  668. }
  669. }
  670. }
  671. class io_minion extends IO {
  672. constructor(body) {
  673. super(body);
  674. this.turnwise = 1;
  675. }
  676.  
  677. think(input) {
  678. if (this.body.aiSettings.reverseDirection && ran.chance(0.005)) { this.turnwise = -1 * this.turnwise; }
  679. if (input.target != null && (input.alt || input.main)) {
  680. let sizeFactor = Math.sqrt(this.body.master.size / this.body.master.SIZE);
  681. let leash = 60 * sizeFactor;
  682. let orbit = 120 * sizeFactor;
  683. let repel = 135 * sizeFactor;
  684. let goal;
  685. let power = 1;
  686. let target = new Vector(input.target.x, input.target.y);
  687. if (input.alt) {
  688. // Leash
  689. if (target.length < leash) {
  690. goal = {
  691. x: this.body.x + target.x,
  692. y: this.body.y + target.y,
  693. };
  694. // Spiral repel
  695. } else if (target.length < repel) {
  696. let dir = -this.turnwise * target.direction + Math.PI / 5;
  697. goal = {
  698. x: this.body.x + Math.cos(dir),
  699. y: this.body.y + Math.sin(dir),
  700. };
  701. // Free repel
  702. } else {
  703. goal = {
  704. x: this.body.x - target.x,
  705. y: this.body.y - target.y,
  706. };
  707. }
  708. } else if (input.main) {
  709. // Orbit point
  710. let dir = this.turnwise * target.direction + 0.01;
  711. goal = {
  712. x: this.body.x + target.x - orbit * Math.cos(dir),
  713. y: this.body.y + target.y - orbit * Math.sin(dir),
  714. };
  715. if (Math.abs(target.length - orbit) < this.body.size * 2) {
  716. power = 0.7;
  717. }
  718. }
  719. return {
  720. goal: goal,
  721. power: power,
  722. };
  723. }
  724. }
  725. }
  726. class io_hangOutNearMaster extends IO {
  727. constructor(body) {
  728. super(body);
  729. this.acceptsFromTop = false;
  730. this.orbit = 30;
  731. this.currentGoal = { x: this.body.source.x, y: this.body.source.y, };
  732. this.timer = 0;
  733. }
  734. think(input) {
  735. if (this.body.source != this.body) {
  736. let bound1 = this.orbit * 0.8 + this.body.source.size + this.body.size;
  737. let bound2 = this.orbit * 1.5 + this.body.source.size + this.body.size;
  738. let dist = util.getDistance(this.body, this.body.source) + Math.PI / 8;
  739. let output = {
  740. target: {
  741. x: this.body.velocity.x,
  742. y: this.body.velocity.y,
  743. },
  744. goal: this.currentGoal,
  745. power: undefined,
  746. };
  747. // Set a goal
  748. if (dist > bound2 || this.timer > 30) {
  749. this.timer = 0;
  750.  
  751. let dir = util.getDirection(this.body, this.body.source) + Math.PI * ran.random(0.5);
  752. let len = ran.randomRange(bound1, bound2);
  753. let x = this.body.source.x - len * Math.cos(dir);
  754. let y = this.body.source.y - len * Math.sin(dir);
  755. this.currentGoal = {
  756. x: x,
  757. y: y,
  758. };
  759. }
  760. if (dist < bound2) {
  761. output.power = 0.15;
  762. if (ran.chance(0.3)) { this.timer++; }
  763. }
  764. return output;
  765. }
  766. }
  767. }
  768. class io_spin extends IO {
  769. constructor(b) {
  770. super(b);
  771. this.a = 0;
  772. }
  773.  
  774. think(input) {
  775. this.a += 0.05;
  776. let offset = 0;
  777. if (this.body.bond != null) {
  778. offset = this.body.bound.angle;
  779. }
  780. return {
  781. target: {
  782. x: Math.cos(this.a + offset),
  783. y: Math.sin(this.a + offset),
  784. },
  785. main: true,
  786. };
  787. }
  788. }
  789. class io_fastspin extends IO {
  790. constructor(b) {
  791. super(b);
  792. this.a = 0;
  793. }
  794.  
  795. think(input) {
  796. this.a += 0.072;
  797. let offset = 0;
  798. if (this.body.bond != null) {
  799. offset = this.body.bound.angle;
  800. }
  801. return {
  802. target: {
  803. x: Math.cos(this.a + offset),
  804. y: Math.sin(this.a + offset),
  805. },
  806. main: true,
  807. };
  808. }
  809. }
  810. class io_testspin extends IO {
  811. constructor(b) {
  812. super(b);
  813. this.a = 0;
  814. }
  815.  
  816. think(input) {
  817. this.a += 0.04;
  818. let offset = Math.sin(this.a);
  819. if (this.body.bond != null) {
  820. offset = this.body.bound.angle;
  821. }
  822. return {
  823. target: {
  824. x: Math.cos(this.a + offset),
  825. y: Math.sin(this.a + offset),
  826. },
  827. main: true,
  828. };
  829. }
  830. }
  831. class io_reversespin extends IO {
  832. constructor(b) {
  833. super(b);
  834. this.a = 0;
  835. }
  836.  
  837. think(input) {
  838. this.a -= 0.05;
  839. let offset = 0;
  840. if (this.body.bond != null) {
  841. offset = this.body.bound.angle;
  842. }
  843. return {
  844. target: {
  845. x: Math.cos(this.a + offset),
  846. y: Math.sin(this.a + offset),
  847. },
  848. main: true,
  849. };
  850. }
  851. }
  852. class io_dontTurn extends IO {
  853. constructor(b) {
  854. super(b);
  855. }
  856.  
  857. think(input) {
  858. return {
  859. target: {
  860. x: 1,
  861. y: 0,
  862. },
  863. main: true,
  864. };
  865. }
  866. }
  867. class io_fleeAtLowHealth extends IO {
  868. constructor(b) {
  869. super(b);
  870. this.fear = util.clamp(ran.gauss(0.7, 0.15), 0.1, 0.9);
  871. }
  872.  
  873. think(input) {
  874. if (input.fire && input.target != null && this.body.health.amount < this.body.health.max * this.fear) {
  875. return {
  876. goal: {
  877. x: this.body.x - input.target.x,
  878. y: this.body.y - input.target.y,
  879. },
  880. };
  881. }
  882. }
  883.  
  884. }
  885.  
  886. /***** ENTITIES *****/
  887. // Define skills
  888. const skcnv = {
  889. rld: 0,
  890. pen: 1,
  891. str: 2,
  892. dam: 3,
  893. spd: 4,
  894.  
  895. shi: 5,
  896. atk: 6,
  897. hlt: 7,
  898. rgn: 8,
  899. mob: 9,
  900. };
  901. const levelers = [
  902. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
  903. 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
  904. 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
  905. 31, 32, 33, 34, 35, 36, 38, 40, 42, 44,
  906. ];
  907. class Skill {
  908. constructor(inital = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) { // Just skill stuff.
  909. this.changed = true;
  910. this.raw = inital;
  911. this.caps = [];
  912. this.setCaps([
  913. c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL,
  914. c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL
  915. ]);
  916. this.name = [
  917. 'Reload',
  918. 'Bullet Penetration',
  919. 'Bullet Health',
  920. 'Bullet Damage',
  921. 'Bullet Speed',
  922. 'Shield Capacity',
  923. 'Body Damage',
  924. 'Max Health',
  925. 'Shield Regeneration',
  926. 'Movement Speed',
  927. ];
  928. this.atk = 0;
  929. this.hlt = 0;
  930. this.spd = 0;
  931. this.str = 0;
  932. this.pen = 0;
  933. this.dam = 0;
  934. this.rld = 0;
  935. this.mob = 0;
  936. this.rgn = 0;
  937. this.shi = 0;
  938. this.rst = 0;
  939. this.brst = 0;
  940. this.ghost = 0;
  941. this.acl = 0;
  942.  
  943. this.reset();
  944. }
  945.  
  946. reset() {
  947. this.points = 0;
  948. this.score = 0;
  949. this.deduction = 0;
  950. this.level = 0;
  951. this.canUpgrade = false;
  952. this.update();
  953. this.maintain();
  954. }
  955.  
  956. update() {
  957. let curve = (() => {
  958. function make(x) { return Math.log(4*x + 1) / Math.log(5); }
  959. let a = [];
  960. for (let i=0; i<c.MAX_SKILL*2; i++) { a.push(make(i/c.MAX_SKILL)); }
  961. // The actual lookup function
  962. return x => { return a[x * c.MAX_SKILL]; };
  963. })();
  964. function apply(f, x) { return (x<0) ? 1 / (1 - x * f) : f * x + 1; }
  965. for (let i=0; i<10; i++) {
  966. if (this.raw[i] > this.caps[i]) {
  967. this.points += this.raw[i] - this.caps[i];
  968. this.raw[i] = this.caps[i];
  969. }
  970. }
  971. let attrib = [];
  972. for (let i=0; i<5; i++) { for (let j=0; j<2; j+=1) {
  973. attrib[i + 5*j] = curve(
  974. (
  975. this.raw[i + 5*j] +
  976. this.bleed(i, j)
  977. ) / c.MAX_SKILL);
  978. } }
  979. this.rld = Math.pow(0.5, attrib[skcnv.rld]);
  980. this.pen = apply(2.5, attrib[skcnv.pen]);
  981. this.str = apply(2, attrib[skcnv.str]);
  982. this.dam = apply(3, attrib[skcnv.dam]);
  983. this.spd = (0.5 + apply(1.5, attrib[skcnv.spd])) / 3;
  984.  
  985. this.acl = apply(0.5, attrib[skcnv.rld]);
  986.  
  987. this.rst = 0.5 * attrib[skcnv.str] + 2.5 * attrib[skcnv.pen];
  988. this.ghost = attrib[skcnv.pen];
  989.  
  990. this.shi = c.GLASS_HEALTH_FACTOR * apply(3 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.shi]);
  991. this.atk = apply(1, attrib[skcnv.atk]);
  992. this.hlt = c.GLASS_HEALTH_FACTOR * apply(2 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.hlt]);
  993. this.mob = apply(0.8, attrib[skcnv.mob]);
  994. this.rgn = apply(25, attrib[skcnv.rgn]);
  995.  
  996. this.brst = 0.3 * (0.5 * attrib[skcnv.atk] + 0.5 * attrib[skcnv.hlt] + attrib[skcnv.rgn]);
  997. this.changed = true;
  998. }
  999.  
  1000. set(thing) {
  1001. this.raw[0] = thing[0];
  1002. this.raw[1] = thing[1];
  1003. this.raw[2] = thing[2];
  1004. this.raw[3] = thing[3];
  1005. this.raw[4] = thing[4];
  1006. this.raw[5] = thing[5];
  1007. this.raw[6] = thing[6];
  1008. this.raw[7] = thing[7];
  1009. this.raw[8] = thing[8];
  1010. this.raw[9] = thing[9];
  1011. this.update();
  1012. }
  1013.  
  1014. setCaps(thing) {
  1015. this.caps[0] = thing[0];
  1016. this.caps[1] = thing[1];
  1017. this.caps[2] = thing[2];
  1018. this.caps[3] = thing[3];
  1019. this.caps[4] = thing[4];
  1020. this.caps[5] = thing[5];
  1021. this.caps[6] = thing[6];
  1022. this.caps[7] = thing[7];
  1023. this.caps[8] = thing[8];
  1024. this.caps[9] = thing[9];
  1025. this.update();
  1026. }
  1027.  
  1028. maintain() {
  1029. if (this.level < c.SKILL_CAP) {
  1030. if (this.score - this.deduction >= this.levelScore) {
  1031. this.deduction += this.levelScore;
  1032. this.level += 1;
  1033. this.points += this.levelPoints;
  1034. if (this.level == c.TIER_1 || this.level == c.TIER_2 || this.level == c.TIER_3) {
  1035. this.canUpgrade = true;
  1036. }
  1037. this.update();
  1038. return true;
  1039. }
  1040. }
  1041. return false;
  1042. }
  1043.  
  1044. get levelScore() {
  1045. return Math.ceil(1.8 * Math.pow(this.level + 1, 1.8) - 2 * this.level + 1);
  1046. }
  1047.  
  1048. get progress() {
  1049. return (this.levelScore) ? (this.score - this.deduction) / this.levelScore : 0;
  1050. }
  1051.  
  1052. get levelPoints() {
  1053. if (levelers.findIndex(e => { return e === this.level; }) != -1) { return 1; } return 0;
  1054.  
  1055. }
  1056.  
  1057. cap(skill, real = false) {
  1058. if (!real && this.level < c.SKILL_SOFT_CAP) {
  1059. return Math.round(this.caps[skcnv[skill]] * c.SOFT_MAX_SKILL);
  1060. }
  1061. return this.caps[skcnv[skill]];
  1062. }
  1063.  
  1064. bleed(i, j) {
  1065. let a = ((i + 2) % 5) + 5*j,
  1066. b = ((i + ((j===1) ? 1 : 4)) % 5) + 5*j;
  1067. let value = 0;
  1068. let denom = Math.max(c.MAX_SKILL, this.caps[i + 5*j]);
  1069. value += (1 - Math.pow(this.raw[a] / denom - 1, 2)) * this.raw[a] * c.SKILL_LEAK;
  1070. value -= Math.pow(this.raw[b] / denom, 2) * this.raw[b] * c.SKILL_LEAK ;
  1071.  
  1072. return value;
  1073. }
  1074.  
  1075. upgrade(stat) {
  1076. if (this.points && this.amount(stat) < this.cap(stat)) {
  1077. this.change(stat, 1);
  1078. this.points -= 1;
  1079. return true;
  1080. }
  1081. return false;
  1082. }
  1083.  
  1084. title(stat) {
  1085. return this.name[skcnv[stat]];
  1086. }
  1087.  
  1088. /*
  1089. let i = skcnv[skill] % 5,
  1090. j = (skcnv[skill] - i) / 5;
  1091. let roundvalue = Math.round(this.bleed(i, j) * 10);
  1092. let string = '';
  1093. if (roundvalue > 0) { string += '+' + roundvalue + '%'; }
  1094. if (roundvalue < 0) { string += '-' + roundvalue + '%'; }
  1095.  
  1096. return string;
  1097. */
  1098.  
  1099. amount(skill) {
  1100. return this.raw[skcnv[skill]];
  1101. }
  1102.  
  1103. change(skill, levels) {
  1104. this.raw[skcnv[skill]] += levels;
  1105. this.update();
  1106. }
  1107. }
  1108.  
  1109. const lazyRealSizes = (() => {
  1110. let o = [1, 1, 1];
  1111. for (var i=3; i<16; i++) {
  1112. // 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...
  1113. o.push(
  1114. Math.sqrt((2 * Math.PI / i) * (1 / Math.sin(2 * Math.PI / i)))
  1115. );
  1116. }
  1117. return o;
  1118. })();
  1119.  
  1120. // Define how guns work
  1121. class Gun {
  1122. constructor(body, info) {
  1123. this.lastShot = {
  1124. time: 0,
  1125. power: 0,
  1126. };
  1127. this.body = body;
  1128. this.master = body.source;
  1129. this.label = '';
  1130. this.controllers = [];
  1131. this.children = [];
  1132. this.control = {
  1133. target: new Vector(0, 0),
  1134. goal: new Vector(0, 0),
  1135. main: false,
  1136. alt: false,
  1137. fire: false,
  1138. };
  1139. this.poisoned = false
  1140. this.poison = false
  1141. this.poisonedBy = -1
  1142. this.poisonLevel = 0
  1143. this.poisonToApply = 0
  1144. this.showpoison = false
  1145. this.poisonTimer = 0
  1146. this.canShoot = false;
  1147. if (info.PROPERTIES != null && info.PROPERTIES.TYPE != null) {
  1148. this.canShoot = true;
  1149. this.label = (info.PROPERTIES.LABEL == null) ?
  1150. '' : info.PROPERTIES.LABEL;
  1151. if (Array.isArray(info.PROPERTIES.TYPE)) { // This is to be nicer about our definitions
  1152. this.bulletTypes = info.PROPERTIES.TYPE;
  1153. this.natural = info.PROPERTIES.TYPE.BODY;
  1154. } else {
  1155. this.bulletTypes = [info.PROPERTIES.TYPE];
  1156. }
  1157. // Pre-load bullet definitions so we don't have to recalculate them every shot
  1158. let natural = {};
  1159. function setNatural(type) {
  1160. if (type.PARENT != null) { // Make sure we load from the parents first
  1161. for (let i=0; i<type.PARENT.length; i++) {
  1162. setNatural(type.PARENT[i]);
  1163. }
  1164. }
  1165. if (type.BODY != null) { // Get values if they exist
  1166. for (let index in type.BODY) {
  1167. natural[index] = type.BODY[index];
  1168. }
  1169. }
  1170. }
  1171. let z = 0;
  1172. const length = this.bulletTypes.length;
  1173. for (; z < length; z++) {
  1174. setNatural(this.bulletTypes[z]);
  1175. }
  1176. this.natural = natural; // Save it
  1177. if (info.PROPERTIES.GUN_CONTROLLERS != null) {
  1178. let toAdd = [];
  1179. let self = this;
  1180. //info.PROPERTIES.GUN_CONTROLLERS.forEach(function(ioName) {
  1181. // toAdd.push(eval('new ' + ioName + '(self)'));
  1182. //});
  1183. let i = 0;
  1184. const length = info.PROPERTIES_GUN_CONTROLLERS.length;
  1185. for (; i < length; i++) {
  1186. toAdd.push(eval('new ' + info.PROPERTIES.GUN_CONTROLLERS[i] + '(self)'));
  1187. }
  1188. this.controllers = toAdd.concat(this.controllers);
  1189. }
  1190. this.autofire = (info.PROPERTIES.AUTOFIRE == null) ?
  1191. false : info.PROPERTIES.AUTOFIRE;
  1192. this.altFire = (info.PROPERTIES.ALT_FIRE == null) ?
  1193. false : info.PROPERTIES.ALT_FIRE;
  1194. this.settings = (info.PROPERTIES.SHOOT_SETTINGS == null) ?
  1195. [] : info.PROPERTIES.SHOOT_SETTINGS;
  1196. this.calculator = (info.PROPERTIES.STAT_CALCULATOR == null) ?
  1197. 'default' : info.PROPERTIES.STAT_CALCULATOR;
  1198. this.waitToCycle = (info.PROPERTIES.WAIT_TO_CYCLE == null) ?
  1199. false : info.PROPERTIES.WAIT_TO_CYCLE;
  1200. this.bulletStats = (info.PROPERTIES.BULLET_STATS == null || info.PROPERTIES.BULLET_STATS == 'master') ?
  1201. 'master' : new Skill(info.PROPERTIES.BULLET_STATS);
  1202. this.settings = (info.PROPERTIES.SHOOT_SETTINGS == null) ?
  1203. [] : info.PROPERTIES.SHOOT_SETTINGS;
  1204. this.countsOwnKids = (info.PROPERTIES.MAX_CHILDREN == null) ?
  1205. false : info.PROPERTIES.MAX_CHILDREN;
  1206. this.syncsSkills = (info.PROPERTIES.SYNCS_SKILLS == null) ?
  1207. false : info.PROPERTIES.SYNCS_SKILLS;
  1208. this.negRecoil = (info.PROPERTIES.NEGATIVE_RECOIL == null) ?
  1209. false : info.PROPERTIES.NEGATIVE_RECOIL;
  1210. }
  1211. let position = info.POSITION;
  1212. this.length = position[0] / 10;
  1213. this.width = position[1] / 10;
  1214. this.aspect = position[2];
  1215. let _off = new Vector(position[3], position[4]);
  1216. this.angle = position[5] * Math.PI / 180;
  1217. this.direction = _off.direction;
  1218. this.offset = _off.length / 10;
  1219. this.delay = position[6];
  1220.  
  1221. this.position = 0;
  1222. this.motion = 0;
  1223. if (this.canShoot) {
  1224. this.cycle = !this.waitToCycle - this.delay;
  1225. this.trueRecoil = this.settings.recoil * 1.675;
  1226. }
  1227. }
  1228.  
  1229. recoil() {
  1230. if (this.motion || this.position) {
  1231. // Simulate recoil
  1232. this.motion -= 0.25 * this.position / roomSpeed;
  1233. this.position += this.motion;
  1234. if (this.position < 0) { // Bouncing off the back
  1235. this.position = 0;
  1236. this.motion = -this.motion;
  1237. }
  1238. if (this.motion > 0) {
  1239. this.motion *= 0.75;
  1240. }
  1241. }
  1242. if (this.canShoot && !this.body.settings.hasNoRecoil) {
  1243. // Apply recoil to motion
  1244. if (this.motion > 0) {
  1245. let recoilForce = -this.position * this.trueRecoil * 0.045 / roomSpeed;
  1246. this.body.accel.x += recoilForce * Math.cos(this.body.facing + this.angle);
  1247. this.body.accel.y += recoilForce * Math.sin(this.body.facing + this.angle);
  1248. }
  1249. }
  1250. }
  1251.  
  1252. getSkillRaw() {
  1253. if (this.bulletStats === 'master') {
  1254. return [
  1255. this.body.skill.raw[0],
  1256. this.body.skill.raw[1],
  1257. this.body.skill.raw[2],
  1258. this.body.skill.raw[3],
  1259. this.body.skill.raw[4],
  1260. 0, 0, 0, 0, 0,
  1261. ];
  1262. }
  1263. return this.bulletStats.raw;
  1264. }
  1265.  
  1266. getLastShot() {
  1267. return this.lastShot;
  1268. }
  1269.  
  1270. live() {
  1271. // Do
  1272. this.recoil();
  1273. // Dummies ignore this
  1274. if (this.canShoot) {
  1275. // Find the proper skillset for shooting
  1276. let sk = (this.bulletStats === 'master') ? this.body.skill : this.bulletStats;
  1277. // Decides what to do based on child-counting settings
  1278. let shootPermission = (this.countsOwnKids) ?
  1279. this.countsOwnKids > this.children.length * ((this.calculator == 'necro') ? sk.rld : 1)
  1280. : (this.body.maxChildren) ?
  1281. this.body.maxChildren > this.body.children.length * ((this.calculator == 'necro') ? sk.rld : 1)
  1282. : true;
  1283. // Override in invuln
  1284. if (this.body.master.invuln) {
  1285. shootPermission = false;
  1286. }
  1287. // Cycle up if we should
  1288. if (shootPermission || !this.waitToCycle) {
  1289. if (this.cycle < 1) {
  1290. this.cycle += 1 / (this.settings.reload * 2) / roomSpeed / ((this.calculator == 'necro' || this.calculator == 'fixed reload') ? 1 : sk.rld);
  1291. }
  1292. }
  1293. // Firing routines
  1294. if (shootPermission && (this.autofire || ((this.altFire) ? this.body.control.alt : this.body.control.fire))) {
  1295. if (this.cycle >= 1) {
  1296. // Find the end of the gun barrel
  1297. let gx =
  1298. this.offset * Math.cos(this.direction + this.angle + this.body.facing) +
  1299. (1.5 * this.length - this.width * this.settings.size / 2) * Math.cos(this.angle + this.body.facing);
  1300. let gy =
  1301. this.offset * Math.sin(this.direction + this.angle + this.body.facing) +
  1302. (1.5 * this.length - this.width * this.settings.size / 2) * Math.sin(this.angle + this.body.facing);
  1303. // Shoot, multiple times in a tick if needed
  1304. while (shootPermission && this.cycle >= 1) {
  1305. this.fire(gx, gy, sk);
  1306. // Figure out if we may still shoot
  1307. shootPermission = (this.countsOwnKids) ?
  1308. this.countsOwnKids > this.children.length
  1309. : (this.body.maxChildren) ?
  1310. this.body.maxChildren > this.body.children.length
  1311. : true;
  1312. // Cycle down
  1313. this.cycle -= 1;
  1314. }
  1315. } // If we're not shooting, only cycle up to where we'll have the proper firing delay
  1316. } else if (this.cycle > !this.waitToCycle - this.delay) {
  1317. this.cycle = !this.waitToCycle - this.delay;
  1318. }
  1319. }
  1320. }
  1321.  
  1322. syncChildren() {
  1323. if (this.syncsSkills) {
  1324. let self = this;
  1325. let i = 0;
  1326. const length = this.children.length;
  1327. for (; i < length; i++) {
  1328. //for (let instance of this.children) {
  1329. let instance = this.children[i];
  1330. instance.define({
  1331. BODY: self.interpret(),
  1332. SKILL: self.getSkillRaw(),
  1333. });
  1334. instance.refreshBodyAttributes();
  1335. }
  1336. }
  1337. }
  1338.  
  1339. fire(gx, gy, sk) {
  1340. // Recoil
  1341. this.lastShot.time = util.time();
  1342. this.lastShot.power = 3 * Math.log(Math.sqrt(sk.spd) + this.trueRecoil + 1) + 1;
  1343. this.motion += this.lastShot.power;
  1344. // Find inaccuracy
  1345. let ss, sd;
  1346. do {
  1347. ss = ran.gauss(0, Math.sqrt(this.settings.shudder));
  1348. } while (Math.abs(ss) >= this.settings.shudder * 2);
  1349. do {
  1350. sd = ran.gauss(0, this.settings.spray * this.settings.shudder);
  1351. } while (Math.abs(sd) >= this.settings.spray / 2);
  1352. sd *= Math.PI / 180;
  1353. // Find speed
  1354. let s = new Vector(
  1355. ((this.negRecoil) ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.cos(this.angle + this.body.facing + sd),
  1356. ((this.negRecoil) ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.sin(this.angle + this.body.facing + sd)
  1357. );
  1358. // Boost it if we should
  1359. if (this.body.velocity.length) {
  1360. let extraBoost =
  1361. Math.max(0, s.x * this.body.velocity.x + s.y * this.body.velocity.y) / this.body.velocity.length / s.length;
  1362. if (extraBoost) {
  1363. let len = s.length;
  1364. s.x += this.body.velocity.length * extraBoost * s.x / len;
  1365. s.y += this.body.velocity.length * extraBoost * s.y / len;
  1366. }
  1367. }
  1368. // Create the bullet
  1369. /*
  1370. var o = new Entity({
  1371. x: this.body.x + this.body.size * gx - s.x,
  1372. y: this.body.y + this.body.size * gy - s.y,
  1373. }, this.master.master);
  1374. let jumpAhead = this.cycle - 1;
  1375. if (jumpAhead) {
  1376. o.x += s.x * this.cycle / jumpAhead;
  1377. o.y += s.y * this.cycle / jumpAhead;
  1378. }
  1379. o.velocity = s;
  1380. this.bulletInit(o);
  1381. o.coreSize = o.SIZE;
  1382. */
  1383. let o = new Entity({
  1384. x: this.body.x + this.body.size * gx - s.x,
  1385. y: this.body.y + this.body.size * gy - s.y,
  1386. }, this.master.master);
  1387. this.bulletInit(o);
  1388. o.x = this.body.x + this.body.size * gx - s.x;
  1389. o.y = this.body.y + this.body.size * gy - s.y;
  1390. o.coreSize = o.SIZE;
  1391. o.velocity = s;
  1392. }
  1393.  
  1394. bulletInit(o) {
  1395. // Define it by its natural properties
  1396. //this.bulletTypes.forEach(type => o.define(type));
  1397. const length = this.bulletTypes.length;
  1398. let i = 0;
  1399. for (; i < length; i++) {
  1400. o.define(this.bulletTypes[i]);
  1401. }
  1402. // Pass the gun attributes
  1403. o.define({
  1404. BODY: this.interpret(),
  1405. SKILL: this.getSkillRaw(),
  1406. SIZE: this.body.size * this.width * this.settings.size / 2 ,
  1407. LABEL: this.master.label + ((this.label) ? ' ' + this.label : '') + ' ' + o.label,
  1408. });
  1409. o.color = this.body.master.color;
  1410. // Keep track of it and give it the function it needs to deutil.log itself upon death
  1411. if (this.countsOwnKids) {
  1412. o.parent = this;
  1413. this.children.push(o);
  1414. } else if (this.body.maxChildren) {
  1415. o.parent = this.body;
  1416. this.body.children.push(o);
  1417. this.children.push(o);
  1418. }
  1419. o.source = this.body;
  1420. o.facing = o.velocity.direction;
  1421. // Necromancers.
  1422. if (this.calculator == 'necro') {
  1423. let oo = o;
  1424. o.necro = host => {
  1425. let shootPermission = (this.countsOwnKids) ?
  1426. this.countsOwnKids > this.children.length *
  1427. ((this.bulletStats === 'master') ? this.body.skill.rld : this.bulletStats.rld)
  1428. : (this.body.maxChildren) ?
  1429. this.body.maxChildren > this.body.children.length *
  1430. ((this.bulletStats === 'master') ? this.body.skill.rld : this.bulletStats.rld)
  1431. : true;
  1432. if (shootPermission) {
  1433. let save = {
  1434. facing: host.facing,
  1435. size: host.SIZE,
  1436. };
  1437. host.define(Class.genericEntity);
  1438. this.bulletInit(host);
  1439. host.team = oo.master.master.team;
  1440. host.master = oo.master;
  1441. host.color = oo.color;
  1442. host.facing = save.facing;
  1443. host.SIZE = save.size;
  1444. host.health.amount = host.health.max;
  1445. return true;
  1446. }
  1447. return false;
  1448. };
  1449. }
  1450. // Otherwise
  1451. o.refreshBodyAttributes();
  1452. o.life();
  1453. }
  1454.  
  1455. getTracking() {
  1456. return {
  1457. speed: c.runSpeed * ((this.bulletStats == 'master') ? this.body.skill.spd : this.bulletStats.spd) *
  1458. this.settings.maxSpeed *
  1459. this.natural.SPEED,
  1460. range: Math.sqrt((this.bulletStats == 'master') ? this.body.skill.spd : this.bulletStats.spd) *
  1461. this.settings.range *
  1462. this.natural.RANGE,
  1463. };
  1464. }
  1465.  
  1466. interpret() {
  1467. let sizeFactor = this.master.size/this.master.SIZE;
  1468. let shoot = this.settings;
  1469. let sk = (this.bulletStats == 'master') ? this.body.skill : this.bulletStats;
  1470. // Defaults
  1471. let out = {
  1472. SPEED: shoot.maxSpeed * sk.spd,
  1473. HEALTH: shoot.health * sk.str,
  1474. RESIST: shoot.resist + sk.rst,
  1475. DAMAGE: shoot.damage * sk.dam,
  1476. PENETRATION: Math.max(1, shoot.pen * sk.pen),
  1477. RANGE: shoot.range / Math.sqrt(sk.spd),
  1478. DENSITY: shoot.density * sk.pen * sk.pen / sizeFactor,
  1479. PUSHABILITY: 1 / sk.pen,
  1480. HETERO: 3 - 2.8 * sk.ghost,
  1481. };
  1482. // Special cases
  1483. switch (this.calculator) {
  1484. case 'thruster':
  1485. this.trueRecoil = this.settings.recoil * Math.sqrt(sk.rld * sk.spd);
  1486. break;
  1487. case 'sustained':
  1488. out.RANGE = shoot.range;
  1489. break;
  1490. case 'swarm':
  1491. out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1));
  1492. out.HEALTH /= shoot.pen * sk.pen;
  1493. break;
  1494. case 'trap':
  1495. case 'block':
  1496. out.PUSHABILITY = 1 / Math.pow(sk.pen, 0.5);
  1497. out.RANGE = shoot.range;
  1498. break;
  1499. case 'necro':
  1500. case 'drone':
  1501. out.PUSHABILITY = 1;
  1502. out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1));
  1503. out.HEALTH = (shoot.health * sk.str + sizeFactor) / Math.pow(sk.pen, 0.8);
  1504. out.DAMAGE = shoot.damage * sk.dam * Math.sqrt(sizeFactor) * shoot.pen * sk.pen;
  1505. out.RANGE = shoot.range * Math.sqrt(sizeFactor);
  1506. break;
  1507. }
  1508. // Go through and make sure we respect its natural properties
  1509. for (let property in out) {
  1510. if (this.natural[property] == null || !out.hasOwnProperty(property)) continue;
  1511. out[property] *= this.natural[property];
  1512. }
  1513. return out;
  1514. }
  1515. }
  1516. // Define entities
  1517. var minimap = [];
  1518. var views = [];
  1519. var entitiesToAvoid = [];
  1520. 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; }); };
  1521. const grid = new hshg.HSHG();
  1522. var entitiesIdLog = 0;
  1523. var entities = [];
  1524. const purgeEntities = () => { entities = entities.filter(e => { return !e.isGhost; }); };
  1525.  
  1526. var bringToLife = (() => {
  1527. let remapTarget = (i, ref, self) => {
  1528. if (i.target == null || (!i.main && !i.alt)) return undefined;
  1529. return {
  1530. x: i.target.x + ref.x - self.x,
  1531. y: i.target.y + ref.y - self.y,
  1532. };
  1533. };
  1534. let passer = (a, b, acceptsFromTop) => {
  1535. return index => {
  1536. if (a != null && a[index] != null && (b[index] == null || acceptsFromTop)) {
  1537. b[index] = a[index];
  1538. }
  1539. };
  1540. };
  1541. return my => {
  1542. // Size
  1543. if (my.SIZE !== my.coreSize) my.coreSize = my.SIZE;
  1544. // Think
  1545. let faucet = (my.settings.independent || my.source == null || my.source === my) ? {} : my.source.control;
  1546. let b = {
  1547. target: remapTarget(faucet, my.source, my),
  1548. goal: undefined,
  1549. fire: faucet.fire,
  1550. main: faucet.main,
  1551. alt: faucet.alt,
  1552. power: undefined,
  1553. };
  1554. // Seek attention
  1555. if (my.settings.attentionCraver && !faucet.main && my.range) {
  1556. my.range -= 1;
  1557. }
  1558. // So we start with my master's thoughts and then we filter them down through our control stack
  1559. for (let AI of my.controllers) {
  1560. let a = AI.think(b);
  1561. let passValue = passer(a, b, AI.acceptsFromTop);
  1562. passValue('target');
  1563. passValue('goal');
  1564. passValue('fire');
  1565. passValue('main');
  1566. passValue('alt');
  1567. passValue('power');
  1568. }
  1569. my.control.target = (b.target == null) ? my.control.target : b.target;
  1570. my.control.goal = b.goal;
  1571. my.control.fire = b.fire;
  1572. my.control.main = b.main;
  1573. my.control.alt = b.alt;
  1574. my.control.power = (b.power == null) ? 1 : b.power;
  1575. // React
  1576. my.move();
  1577. my.face();
  1578. // Handle guns and turrets if we've got them
  1579. for (let gun of my.guns) {
  1580. gun.live();
  1581. }
  1582. for (let turret of my.turrets) {
  1583. turret.life();
  1584. }
  1585. if (my.skill.maintain()) my.refreshBodyAttributes();
  1586. //if (my.invisible[1]) {
  1587. // my.alpha = Math.max(0.01, my.alpha - my.invisible[1]);
  1588. // if (!(my.velocity.x * my.velocity.x + my.velocity.y * my.velocity.y < 0.15 * 0.15) || my.damageRecieved)
  1589. // my.alpha = Math.min(1, my.alpha + my.invisible[0]);
  1590. //} else my.alpha = 1;
  1591. };
  1592. })();
  1593.  
  1594. class HealthType {
  1595. constructor(health, type, resist = 0) {
  1596. this.max = health;
  1597. this.amount = health;
  1598. this.type = type;
  1599. this.resist = resist;
  1600. this.regen = 0;
  1601. }
  1602.  
  1603. set(health, regen = 0) {
  1604. this.amount = (this.max) ? this.amount / this.max * health : health;
  1605. this.max = health;
  1606. this.regen = regen;
  1607. }
  1608.  
  1609. display() {
  1610. return this.amount / this.max;
  1611. }
  1612.  
  1613. getDamage(amount, capped = true) {
  1614. switch (this.type) {
  1615. case 'dynamic':
  1616. return (capped) ? (
  1617. Math.min(amount * this.permeability, this.amount)
  1618. ) : (
  1619. amount * this.permeability
  1620. );
  1621. case 'static':
  1622. return (capped) ? (
  1623. Math.min(amount, this.amount)
  1624. ) : (
  1625. amount
  1626. );
  1627. }
  1628. }
  1629.  
  1630. regenerate(boost = false) {
  1631. boost /= 2;
  1632. let cons = 5;
  1633. switch (this.type) {
  1634. case 'static':
  1635. if (this.amount >= this.max || !this.amount) break;
  1636. this.amount += cons * (this.max / 10 / 60 / 2.5 + boost);
  1637. break;
  1638. case 'dynamic':
  1639. let r = util.clamp(this.amount / this.max, 0, 1);
  1640. if (!r) {
  1641. this.amount = 0.0001;
  1642. }
  1643. if (r === 1) {
  1644. this.amount = this.max;
  1645. } else {
  1646. 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);
  1647. }
  1648. break;
  1649. }
  1650. this.amount = util.clamp(this.amount, 0, this.max);
  1651. }
  1652.  
  1653. get permeability() {
  1654. switch(this.type) {
  1655. case 'static': return 1;
  1656. case 'dynamic': return (this.max) ? util.clamp(this.amount / this.max, 0, 1) : 0;
  1657. }
  1658. }
  1659.  
  1660. get ratio() {
  1661. return (this.max) ? util.clamp(1 - Math.pow(this.amount / this.max - 1, 4), 0, 1) : 0;
  1662. }
  1663. }
  1664.  
  1665. class Entity {
  1666. constructor(position, master = this) {
  1667. this.cache = [0, 0, 0, 0, 0, 0];
  1668. this.defaultset = false;
  1669. this.isGhost = false;
  1670. this.killCount = { solo: 0, assists: 0, bosses: 0, killers: [], };
  1671. this.creationTime = (new Date()).getTime();
  1672. // Inheritance
  1673. this.master = master;
  1674. this.source = this;
  1675. this.parent = this;
  1676. this.control = {
  1677. target: new Vector(0, 0),
  1678. goal: new Vector(0, 0),
  1679. main: false,
  1680. alt: false,
  1681. fire: false,
  1682. power: 0,
  1683. };
  1684. this.isInGrid = false;
  1685. this.removeFromGrid = () => { if (this.isInGrid) { grid.removeObject(this); this.isInGrid = false; } };
  1686. this.addToGrid = () => {
  1687. if (!this.isInGrid && this.bond == null) {
  1688. grid.addObject(this); this.isInGrid = true;
  1689. }
  1690. };
  1691.  
  1692. this.activation = (() => {
  1693. let active = true;
  1694. let timer = ran.irandom(15);
  1695. return {
  1696. update: () => {
  1697. if (this.triggerFunctions.UPDATE != null) {
  1698. this.triggerFunctions.UPDATE(this);
  1699. }
  1700. if (this.isDead()) return 0;
  1701. // Check if I'm in anybody's view
  1702. if (!active) {
  1703. this.removeFromGrid();
  1704. // Remove bullets and swarm
  1705. if (this.settings.diesAtRange) this.kill();
  1706. // Still have limited update cycles but do it much more slowly.
  1707. if (!(timer--)) active = true;
  1708. } else {
  1709. this.addToGrid();
  1710. timer = 15;
  1711. active = views.some(v => v.check(this, 0.6));
  1712. }
  1713. },
  1714. check: () => { return active; }
  1715. };
  1716. })();
  1717. this.autoOverride = false;
  1718. this.controllers = [];
  1719. this.blend = {
  1720. color: '#FFFFFF',
  1721. amount: 0,
  1722. };
  1723. // Objects
  1724. this.skill = new Skill();
  1725. this.health = new HealthType(1, 'static', 0);
  1726. this.shield = new HealthType(0, 'dynamic');
  1727. this.guns = [];
  1728. this.turrets = [];
  1729. this.upgrades = [];
  1730. this.settings = {};
  1731. this.aiSettings = {};
  1732. this.children = [];
  1733. // Define it
  1734. this.SIZE = 1;
  1735. this.define(Class.genericEntity);
  1736. // Initalize physics and collision
  1737. this.facing = 0;
  1738. this.vfacing = 0;
  1739. this.range = 0;
  1740. this.damageRecieved = 0;
  1741. this.stepRemaining = 1;
  1742. this.collisionArray = [];
  1743. this.invuln = false;
  1744. this.alpha = 1;
  1745. // Physics 2.0 stuff
  1746. this.x = position.x;
  1747. this.y = position.y;
  1748. this.velocity = new Vector(0, 0);
  1749. this.accel = new Vector(0, 0);
  1750. this.impulse = new Vector(0, 0);
  1751. this.maxSpeed = 0;
  1752. this.maxAccel = 0;
  1753. this.resistance = 0.13;
  1754. this.impulseZero = 0.01;
  1755. this.knockbackLimit = 125;
  1756. // Get a new unique id
  1757. this.id = entitiesIdLog++;
  1758. this.team = this.id;
  1759. this.team = master.team;
  1760. // This is for collisions
  1761. this.updateAABB = () => {};
  1762. this.getAABB = (() => {
  1763. let data = {}, savedSize = 0;
  1764. let getLongestEdge = (x1, y1, x2, y2) => {
  1765. return Math.max(
  1766. Math.abs(x2 - x1),
  1767. Math.abs(y2 - y1)
  1768. );
  1769. };
  1770. this.updateAABB = active => {
  1771. if (this.bond != null) return 0;
  1772. if (!active) { data.active = false; return 0; }
  1773. // Get bounds
  1774. let x1 = Math.min(this.x, this.x + this.velocity.x + this.accel.x) - this.realSize - 5;
  1775. let y1 = Math.min(this.y, this.y + this.velocity.y + this.accel.y) - this.realSize - 5;
  1776. let x2 = Math.max(this.x, this.x + this.velocity.x + this.accel.x) + this.realSize + 5;
  1777. let y2 = Math.max(this.y, this.y + this.velocity.y + this.accel.y) + this.realSize + 5;
  1778. // Size check
  1779. let size = getLongestEdge(x1, y1, x2, y1);
  1780. let sizeDiff = savedSize / size;
  1781. // Update data
  1782. data = {
  1783. min: [x1, y1],
  1784. max: [x2, y2],
  1785. active: true,
  1786. size: size,
  1787. };
  1788. // Update grid if needed
  1789. if (sizeDiff > Math.SQRT2 || sizeDiff < Math.SQRT1_2) {
  1790. this.removeFromGrid(); this.addToGrid();
  1791. savedSize = data.size;
  1792. }
  1793. };
  1794. return () => { return data; };
  1795. })();
  1796. this.updateAABB(true);
  1797. // note, these dont have to be defined but having them as null
  1798. // can make things more obvious to what trigger functions actually exist
  1799. this.triggerFunctions = {
  1800. UPDATE: (myself) => null, // takes in parameter (myself) which will be the entity its being called on
  1801. 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
  1802. };
  1803. entities.push(this); // everything else
  1804. //views.forEach(v => v.add(this));
  1805. let i = 0;
  1806. const length = views.length;
  1807. for (; i < length; i++) {
  1808. views[i].add(this);
  1809. }
  1810. }
  1811.  
  1812. life() { bringToLife(this); }
  1813.  
  1814. addController(newIO) {
  1815. if (Array.isArray(newIO)) {
  1816. this.controllers = newIO.concat(this.controllers);
  1817. } else {
  1818. this.controllers.unshift(newIO);
  1819. }
  1820. }
  1821.  
  1822. define(set, mockupDefine = false) {
  1823. if (set.PARENT != null) {
  1824. for (let i=0; i<set.PARENT.length; i++) {
  1825. this.define(set.PARENT[i]);
  1826. }
  1827. }
  1828. if (set.index != null) {
  1829. this.index = set.index;
  1830. }
  1831. if (set.NAME != null) {
  1832. this.name = set.NAME;
  1833. }
  1834. if (set.LABEL != null) {
  1835. this.label = set.LABEL;
  1836. }
  1837. if (set.TYPE != null) {
  1838. this.type = set.TYPE;
  1839. }
  1840. if (set.SHAPE != null) {
  1841. this.shape = (typeof set.SHAPE === 'number') ? set.SHAPE : 0;
  1842. this.shapeData = set.SHAPE;
  1843. }
  1844. /*
  1845. if (set.CUSTOMSHAPE != null && set.CUSTOMSHAPE.length > 0) {
  1846. this.customshape = set.CUSTOMSHAPE;
  1847. console.log(this.customshape);
  1848. }
  1849. */
  1850. if (set.COLOR != null) {
  1851. this.color = set.COLOR;
  1852. }
  1853. if (set.CONTROLLERS != null) {
  1854. let toAdd = [];
  1855. //set.CONTROLLERS.forEach((ioName) => {
  1856. // toAdd.push(eval('new io_' + ioName + '(this)'));
  1857. //});
  1858. let i = 0;
  1859. const length = set.CONTROLLERS.length;
  1860. for (; i < length; i++) {
  1861. toAdd.push(eval('new io_' + set.CONTROLLERS[i] + '(this)'));
  1862. }
  1863. this.addController(toAdd);
  1864. }
  1865. if (set.POISON != null) {
  1866. this.poison = set.POISON
  1867. }
  1868. if (set.POISONED != null) {
  1869. this.poisoned = set.POISONED
  1870. }
  1871. if (set.POISON_TO_APPLY != null) {
  1872. this.poisonToApply = set.POISON_TO_APPLY
  1873. }
  1874. if (set.SHOWPOISON != null) {
  1875. this.showpoison = set.SHOWPOISON
  1876. }
  1877. if (set.MOTION_TYPE != null) {
  1878. this.motionType = set.MOTION_TYPE;
  1879. }
  1880. if (set.FACING_TYPE != null) {
  1881. this.facingType = set.FACING_TYPE;
  1882. }
  1883. if (set.DRAW_HEALTH != null) {
  1884. this.settings.drawHealth = set.DRAW_HEALTH;
  1885. }
  1886. if (set.DRAW_SELF != null) {
  1887. this.settings.drawShape = set.DRAW_SELF;
  1888. }
  1889. if (set.DAMAGE_EFFECTS != null) {
  1890. this.settings.damageEffects = set.DAMAGE_EFFECTS;
  1891. }
  1892. if (set.RATIO_EFFECTS != null) {
  1893. this.settings.ratioEffects = set.RATIO_EFFECTS;
  1894. }
  1895. if (set.MOTION_EFFECTS != null) {
  1896. this.settings.motionEffects = set.MOTION_EFFECTS;
  1897. }
  1898. if (set.ACCEPTS_SCORE != null) {
  1899. this.settings.acceptsScore = set.ACCEPTS_SCORE;
  1900. }
  1901. if (set.GIVE_KILL_MESSAGE != null) {
  1902. this.settings.givesKillMessage = set.GIVE_KILL_MESSAGE;
  1903. }
  1904. if (set.CAN_GO_OUTSIDE_ROOM != null) {
  1905. this.settings.canGoOutsideRoom = set.CAN_GO_OUTSIDE_ROOM;
  1906. }
  1907. if (set.HITS_OWN_TYPE != null) {
  1908. this.settings.hitsOwnType = set.HITS_OWN_TYPE;
  1909. }
  1910. if (set.DIE_AT_LOW_SPEED != null) {
  1911. this.settings.diesAtLowSpeed = set.DIE_AT_LOW_SPEED;
  1912. }
  1913. if (set.DIE_AT_RANGE != null) {
  1914. this.settings.diesAtRange = set.DIE_AT_RANGE;
  1915. }
  1916. if (set.INDEPENDENT != null) {
  1917. this.settings.independent = set.INDEPENDENT;
  1918. }
  1919. if (set.PERSISTS_AFTER_DEATH != null) {
  1920. this.settings.persistsAfterDeath = set.PERSISTS_AFTER_DEATH;
  1921. }
  1922. if (set.CLEAR_ON_MASTER_UPGRADE != null) {
  1923. this.settings.clearOnMasterUpgrade = set.CLEAR_ON_MASTER_UPGRADE;
  1924. }
  1925. if (set.HEALTH_WITH_LEVEL != null) {
  1926. this.settings.healthWithLevel = set.HEALTH_WITH_LEVEL;
  1927. }
  1928. if (set.ACCEPTS_SCORE != null) {
  1929. this.settings.acceptsScore = set.ACCEPTS_SCORE;
  1930. }
  1931. if (set.OBSTACLE != null) {
  1932. this.settings.obstacle = set.OBSTACLE;
  1933. }
  1934. if (set.NECRO != null) {
  1935. this.settings.isNecromancer = set.NECRO;
  1936. }
  1937. if (set.AUTO_UPGRADE != null) {
  1938. this.settings.upgrading = set.AUTO_UPGRADE;
  1939. }
  1940. if (set.HAS_NO_RECOIL != null) {
  1941. this.settings.hasNoRecoil = set.HAS_NO_RECOIL;
  1942. }
  1943. if (set.CRAVES_ATTENTION != null) {
  1944. this.settings.attentionCraver = set.CRAVES_ATTENTION;
  1945. }
  1946. if (set.BROADCAST_MESSAGE != null) {
  1947. this.settings.broadcastMessage = (set.BROADCAST_MESSAGE === '') ? undefined : set.BROADCAST_MESSAGE;
  1948. }
  1949. if (set.DAMAGE_CLASS != null) {
  1950. this.settings.damageClass = set.DAMAGE_CLASS;
  1951. }
  1952. if (set.BUFF_VS_FOOD != null) {
  1953. this.settings.buffVsFood = set.BUFF_VS_FOOD;
  1954. }
  1955. if (set.CAN_BE_ON_LEADERBOARD != null) {
  1956. this.settings.leaderboardable = set.CAN_BE_ON_LEADERBOARD;
  1957. }
  1958. if (set.INTANGIBLE != null) {
  1959. this.intangibility = set.INTANGIBLE;
  1960. }
  1961. if (set.IS_SMASHER != null) {
  1962. this.settings.reloadToAcceleration = set.IS_SMASHER;
  1963. }
  1964. if (set.STAT_NAMES != null) {
  1965. this.settings.skillNames = set.STAT_NAMES;
  1966. }
  1967. if (set.AI != null) {
  1968. this.aiSettings = set.AI;
  1969. }
  1970. if (set.DANGER != null) {
  1971. this.dangerValue = set.DANGER;
  1972. }
  1973. if (set.VARIES_IN_SIZE != null) {
  1974. this.settings.variesInSize = set.VARIES_IN_SIZE;
  1975. this.squiggle = (this.settings.variesInSize) ? ran.randomRange(0.8, 1.2) : 1;
  1976. }
  1977. if (set.RESET_UPGRADES) {
  1978. this.upgrades = [];
  1979. }
  1980. if (set.ALPHA != null) this.alpha = set.ALPHA;
  1981. if (set.INVISIBLE != null) this.invisible = [
  1982. set.INVISIBLE[0],
  1983. set.INVISIBLE[1]
  1984. ];
  1985. if (set.UPGRADES_TIER_1 != null) {
  1986. //set.UPGRADES_TIER_1.forEach((e) => {
  1987. // this.upgrades.push({class: e, level: c.TIER_1, index: e.index});
  1988. //});
  1989. let i = 0;
  1990. const length = set.UPGRADES_TIER_1.length;
  1991. for (; i < length; i++) {
  1992. this.upgrades.push({class: set.UPGRADES_TIER_1[i], level: c.TIER_1, index: set.UPGRADES_TIER_1[i].index});
  1993. }
  1994. }
  1995. if (set.UPGRADES_TIER_2 != null) {
  1996. //set.UPGRADES_TIER_2.forEach((e) => {
  1997. // this.upgrades.push({class: e, level: c.TIER_2, index: e.index});
  1998. //});
  1999. let i = 0;
  2000. const length = set.UPGRADES_TIER_2.length;
  2001. for (; i < length; i++) {
  2002. this.upgrades.push({class: set.UPGRADES_TIER_2[i], level: c.TIER_2, index: set.UPGRADES_TIER_2[i].index});
  2003. }
  2004. }
  2005. if (set.UPGRADES_TIER_3 != null) {
  2006. //set.UPGRADES_TIER_3.forEach((e) => {
  2007. // this.upgrades.push({class: e, level: c.TIER_3, index: e.index});
  2008. //});
  2009. let i = 0;
  2010. const length = set.UPGRADES_TIER_3.length;
  2011. for (; i < length; i++) {
  2012. this.upgrades.push({class: set.UPGRADES_TIER_3[i], level: c.TIER_3, index: set.UPGRADES_TIER_3[i].index});
  2013. }
  2014. }
  2015. if (set.SIZE != null) {
  2016. this.SIZE = set.SIZE * this.squiggle;
  2017. if (this.coreSize == null) { this.coreSize = this.SIZE; }
  2018. }
  2019. if (set.SKILL != null && set.SKILL != []) {
  2020. if (set.SKILL.length != 10) {
  2021. throw('Inappropiate skill raws.');
  2022. }
  2023. this.skill.set(set.SKILL);
  2024. }
  2025. if (set.LEVEL != null) {
  2026. if (set.LEVEL === -1) {
  2027. this.skill.reset();
  2028. }
  2029. while (this.skill.level < c.SKILL_CHEAT_CAP && this.skill.level < set.LEVEL) {
  2030. this.skill.score += this.skill.levelScore;
  2031. this.skill.maintain();
  2032. }
  2033. this.refreshBodyAttributes();
  2034. }
  2035. if (set.SKILL_CAP != null && set.SKILL_CAP != []) {
  2036. if (set.SKILL_CAP.length != 10) {
  2037. throw('Inappropiate skill caps.');
  2038. }
  2039. this.skill.setCaps(set.SKILL_CAP);
  2040. }
  2041. if (set.VALUE != null) {
  2042. this.skill.score = Math.max(this.skill.score, set.VALUE * this.squiggle);
  2043. }
  2044. if (set.ALT_ABILITIES != null) {
  2045. this.abilities = set.ALT_ABILITIES;
  2046. }
  2047. if (set.GUNS != null) {
  2048. let newGuns = [];
  2049. //set.GUNS.forEach((gundef) => {
  2050. // newGuns.push(new Gun(this, gundef));
  2051. //});
  2052. let i = 0;
  2053. const length = set.GUNS.length;
  2054. for (; i < length; i++) {
  2055. newGuns.push(new Gun(this, set.GUNS[i]));
  2056. }
  2057. this.guns = newGuns;
  2058. }
  2059. if (set.MAX_CHILDREN != null) {
  2060. this.maxChildren = set.MAX_CHILDREN;
  2061. }
  2062. if (set.FOOD != null) {
  2063. if (set.FOOD.LEVEL != null) {
  2064. this.foodLevel = set.FOOD.LEVEL;
  2065. this.foodCountup = 0;
  2066. }
  2067. }
  2068. if (set.BODY != null) {
  2069. if (set.BODY.ACCELERATION != null) {
  2070. this.ACCELERATION = set.BODY.ACCELERATION;
  2071. }
  2072. if (set.BODY.SPEED != null) {
  2073. this.SPEED = set.BODY.SPEED;
  2074. }
  2075. if (set.BODY.HEALTH != null) {
  2076. this.HEALTH = set.BODY.HEALTH;
  2077. }
  2078. if (set.BODY.RESIST != null) {
  2079. this.RESIST = set.BODY.RESIST;
  2080. }
  2081. if (set.BODY.SHIELD != null) {
  2082. this.SHIELD = set.BODY.SHIELD;
  2083. }
  2084. if (set.BODY.REGEN != null) {
  2085. this.REGEN = set.BODY.REGEN;
  2086. }
  2087. if (set.BODY.DAMAGE != null) {
  2088. this.DAMAGE = set.BODY.DAMAGE;
  2089. }
  2090. if (set.BODY.PENETRATION != null) {
  2091. this.PENETRATION = set.BODY.PENETRATION;
  2092. }
  2093. if (set.BODY.FOV != null) {
  2094. this.FOV = set.BODY.FOV;
  2095. }
  2096. if (set.BODY.RANGE != null) {
  2097. this.RANGE = set.BODY.RANGE;
  2098. }
  2099. if (set.BODY.SHOCK_ABSORB != null) {
  2100. this.SHOCK_ABSORB = set.BODY.SHOCK_ABSORB;
  2101. }
  2102. if (set.BODY.DENSITY != null) {
  2103. this.DENSITY = set.BODY.DENSITY;
  2104. }
  2105. if (set.BODY.STEALTH != null) {
  2106. this.STEALTH = set.BODY.STEALTH;
  2107. }
  2108. if (set.BODY.PUSHABILITY != null) {
  2109. this.PUSHABILITY = set.BODY.PUSHABILITY;
  2110. }
  2111. if (set.BODY.HETERO != null) {
  2112. this.heteroMultiplier = set.BODY.HETERO;
  2113. }
  2114. this.refreshBodyAttributes();
  2115. }
  2116. if (set.TURRETS != null) {
  2117. let o;
  2118. let i = 0;
  2119. let j = 0;
  2120. const lengthi = this.turrets.length;
  2121. const lengthj = set.TURRETS.length;
  2122. //this.turrets.forEach(o => o.destroy());
  2123. for(; i < lengthi; i++) {
  2124. this.turrets[i].destroy();
  2125. }
  2126. this.turrets = [];
  2127. for (; j < lengthj; j++) {
  2128. let def = set.TURRETS[j];
  2129. o = new Entity(this, this.master);
  2130. ((Array.isArray(def.TYPE)) ? def.TYPE : [def.TYPE]).forEach(type => o.define(type));
  2131. o.bindToMaster(def.POSITION, this);
  2132. }
  2133. }
  2134. if (set.TRIGGERS != null && mockupDefine === false) {
  2135. let entries = Object.entries(set.TRIGGERS);
  2136. for (let i = 0; i < entries.length; i++) {
  2137. let entry = entries[i];
  2138. if (typeof entry[1] === 'function') {
  2139. if (this.triggerFunctions[entry[0]] != null) {
  2140. this.triggerFunctions[entry[0]] = entry[1];
  2141. } else {
  2142. util.error('Unknown trigger function ' + entry[0] + ' being defined by definition ' + set.LABEL);
  2143. }
  2144. } else {
  2145. util.error('Trigger function ' + entry[0] + ' being defined by definition ' + set.LABEL + ' is not a function!');
  2146. }
  2147. // so if i had update or something thats setup,
  2148. // now i can call that if i have a place for it like
  2149. // inside the objects activation update the update trigger
  2150. // function is called if its actually defined,
  2151. // you could also split up update into two triggers,
  2152. // AlwaysUpdate to always update even when the object died and
  2153. // Update for when it isnt dead, though right now updates set to when
  2154. // it isnt dead. anyway lets go to exports.explosion in definitions
  2155. // to see these things in action!
  2156. }
  2157. }
  2158. if (set.mockup != null) {
  2159. this.mockup = set.mockup;
  2160. }
  2161. }
  2162.  
  2163. refreshBodyAttributes() {
  2164. if (this.cache === undefined) {
  2165. this.cache = [0, 0, 0, 0, 0, 0];
  2166. }
  2167.  
  2168. if (this.defaultset === false || this.skill.changed === true) {
  2169. let speedReduce = Math.pow(this.size / (this.coreSize || this.SIZE), 1);
  2170.  
  2171. this.acceleration = c.runSpeed * this.ACCELERATION / speedReduce;
  2172. if (this.settings.reloadToAcceleration) this.acceleration *= this.skill.acl;
  2173. this.cache[0] = this.ACCELERATION;
  2174.  
  2175. this.topSpeed = (c.runSpeed * this.SPEED * this.skill.mob / speedReduce) * 2.58853046592;
  2176. if (this.settings.reloadToAcceleration) this.topSpeed /= Math.sqrt(this.skill.acl);
  2177. this.cache[1] = this.SPEED;
  2178.  
  2179. this.health.set(
  2180. (((this.settings.healthWithLevel) ? 2 * this.skill.level : 0) + this.HEALTH) * this.skill.hlt
  2181. );
  2182.  
  2183. this.health.resist = 1 - 1 / Math.max(1, this.RESIST + this.skill.brst);
  2184.  
  2185. this.shield.set(
  2186. (((this.settings.healthWithLevel) ? 0.6 * this.skill.level : 0) + this.SHIELD) * this.skill.shi,
  2187. Math.max(0, ((((this.settings.healthWithLevel) ? 0.006 * this.skill.level : 0) + 1) * this.REGEN) * this.skill.rgn)
  2188. );
  2189.  
  2190. this.damage = this.DAMAGE * this.skill.atk;
  2191.  
  2192. this.penetration = this.PENETRATION + 1.5 * (this.skill.brst + 0.8 * (this.skill.atk - 1));
  2193.  
  2194. if (!this.settings.dieAtRange || !this.range) {
  2195. this.range = this.RANGE;
  2196. }
  2197.  
  2198. if (this.size < 12) {
  2199. this.fov = this.FOV * 400 * Math.sqrt((1 / (0.1 * this.size))) * (0.9 + 0.0001 * (this.size / 4));
  2200. } else {
  2201. this.fov = this.FOV * 400 * Math.sqrt((1 * (0.2 * this.size))) * (0.9 + 0.0001 * (this.size / 4));
  2202. }
  2203.  
  2204. this.density = (1 + 0.08 * this.skill.level) * this.DENSITY;
  2205.  
  2206. this.stealth = this.STEALTH;
  2207.  
  2208. this.pushability = this.PUSHABILITY;
  2209.  
  2210. this.defaultset = true;
  2211. this.skill.changed = false;
  2212. }
  2213.  
  2214.  
  2215. /*
  2216. let speedReduce = Math.pow(this.size / (this.coreSize || this.SIZE), 1);
  2217.  
  2218. if (this.ACCELERATION != this.cache[0]) {
  2219. this.acceleration = c.runSpeed * this.ACCELERATION / speedReduce;
  2220. if (this.settings.reloadToAcceleration) this.acceleration *= this.skill.acl;
  2221. this.cache[0] = this.ACCELERATION;
  2222. }
  2223.  
  2224. if (this.SPEED != this.cache[1]) {
  2225. this.topSpeed = (c.runSpeed * this.SPEED * this.skill.mob / speedReduce) * 1.14866039425;
  2226. if (this.settings.reloadToAcceleration) this.topSpeed /= Math.sqrt(this.skill.acl);
  2227. this.cache[1] = this.SPEED;
  2228. }
  2229. */
  2230.  
  2231. /*
  2232. if (this.HEALTH != this.cache[2]) {
  2233. this.health.set(
  2234. (((this.settings.healthWithLevel) ? 2 * this.skill.level : 0) + this.HEALTH) * this.skill.hlt
  2235. );
  2236. this.health.resist = 1 - 1 / Math.max(1, this.RESIST + this.skill.brst);
  2237. this.cache[2] = this.HEALTH;
  2238. }
  2239.  
  2240. if (this.SHIELD != this.cache[3]) {
  2241. this.shield.set(
  2242. (((this.settings.healthWithLevel) ? 0.6 * this.skill.level : 0) + this.SHIELD) * this.skill.shi,
  2243. Math.max(0, ((((this.settings.healthWithLevel) ? 0.006 * this.skill.level : 0) + 1) * this.REGEN) * this.skill.rgn)
  2244. );
  2245. this.cache[3] = this.SHIELD;
  2246. }
  2247.  
  2248. if (this.skill.atk != this.cache[4]) {
  2249. this.damage = this.DAMAGE * this.skill.atk;
  2250. this.cache[4] = this.skill.atk;
  2251. }
  2252.  
  2253. if (this.skill.brst != this.cache[5]) {
  2254. this.penetration = this.PENETRATION + 1.5 * (this.skill.brst + 0.8 * (this.skill.atk - 1));
  2255. this.cache[5] = this.skill.brst;
  2256. }
  2257.  
  2258. if (!this.settings.dieAtRange || !this.range) {
  2259. this.range = this.RANGE;
  2260. }
  2261.  
  2262. this.fov = this.FOV * 250 * Math.sqrt(this.size) * (1 + 0.003 * this.skill.level);
  2263.  
  2264. this.density = (1 + 0.08 * this.skill.level) * this.DENSITY;
  2265.  
  2266. this.stealth = this.STEALTH;
  2267.  
  2268. this.pushability = this.PUSHABILITY;
  2269. */
  2270. }
  2271.  
  2272. bindToMaster(position, bond) {
  2273. this.bond = bond;
  2274. this.source = bond;
  2275. this.bond.turrets.push(this);
  2276. this.skill = this.bond.skill;
  2277. this.label = this.bond.label + ' ' + this.label;
  2278. // It will not be in collision calculations any more nor shall it be seen.
  2279. this.removeFromGrid();
  2280. this.settings.drawShape = false;
  2281. // Get my position.
  2282. this.bound = {};
  2283. this.bound.size = position[0] / 20;
  2284. let _off = new Vector(position[1], position[2]);
  2285. this.bound.angle = position[3] * Math.PI / 180;
  2286. this.bound.direction = _off.direction;
  2287. this.bound.offset = _off.length / 10;
  2288. this.bound.arc = position[4] * Math.PI / 180;
  2289. // Figure out how we'll be drawn.
  2290. this.bound.layer = position[5];
  2291. // Initalize.
  2292. this.facing = this.bond.facing + this.bound.angle;
  2293. this.facingType = 'bound';
  2294. this.motionType = 'bound';
  2295. this.move();
  2296. }
  2297.  
  2298. get size() {
  2299. if (this.bond == null) return (this.coreSize || this.SIZE) * (1 + this.skill.level / 45);
  2300. return this.bond.size * this.bound.size;
  2301. }
  2302.  
  2303. get mass() {
  2304. return this.density * (this.size * this.size + 1);
  2305. }
  2306.  
  2307. get realSize() {
  2308. if (Array.isArray(this.shape)) {
  2309. return this.size * ((Math.abs(1) > lazyRealSizes.length) ? 1 : lazyRealSizes[Math.abs(1)]);
  2310. } else {
  2311. return this.size * ((Math.abs(this.shape) > lazyRealSizes.length) ? 1 : lazyRealSizes[Math.abs(this.shape)]);
  2312. }
  2313. }
  2314.  
  2315. get m_x() {
  2316. return (this.velocity.x + this.accel.x) / roomSpeed;
  2317. }
  2318. get m_y() {
  2319. return (this.velocity.y + this.accel.y) / roomSpeed;
  2320. }
  2321.  
  2322. camera(tur = false) {
  2323. return {
  2324. type: 0 + tur * 0x01 + this.settings.drawHealth * 0x02 + (this.type === 'tank') * 0x04,
  2325. id: this.id,
  2326. index: this.index,
  2327. x: this.x,
  2328. y: this.y ,
  2329. vx: this.velocity.x,
  2330. vy: this.velocity.y,
  2331. size: this.size,
  2332. rsize: this.realSize,
  2333. status: 1,
  2334. health: this.health.display(),
  2335. shield: this.shield.display(),
  2336. facing: this.facing,
  2337. vfacing: this.vfacing,
  2338. twiggle: this.facingType === 'autospin' || (this.facingType === 'locksFacing' && this.control.alt),
  2339. layer: (this.bond != null) ? this.bound.layer :
  2340. (this.type === 'wall') ? 11 :
  2341. (this.type === 'food') ? 10 :
  2342. (this.type === 'tank') ? 5 :
  2343. (this.type === 'crasher') ? 1 :
  2344. 0,
  2345. color: this.color,
  2346. name: this.name,
  2347. score: this.skill.score,
  2348. guns: this.guns.map(gun => gun.getLastShot()),
  2349. turrets: this.turrets.map(turret => turret.camera(true)),
  2350. alpha: this.alpha
  2351. };
  2352. }
  2353.  
  2354. skillUp(stat) {
  2355. let suc = this.skill.upgrade(stat);
  2356. if (suc) {
  2357. this.refreshBodyAttributes();
  2358. //this.guns.forEach(function(gun) {
  2359. // gun.syncChildren();
  2360. //});
  2361. let i = 0;
  2362. const length = this.guns.length;
  2363. for (; i < length; i++) {
  2364. this.guns[i].syncChildren();
  2365. }
  2366. }
  2367. return suc;
  2368. }
  2369.  
  2370. upgrade(number) {
  2371. if (number < this.upgrades.length && this.skill.level >= this.upgrades[number].level) {
  2372. let saveMe = this.upgrades[number].class;
  2373. this.upgrades = [];
  2374. this.define(saveMe);
  2375. this.sendMessage('You have upgraded to ' + this.label + '.');
  2376. let ID = this.id;
  2377. //entities.forEach(instance => {
  2378. // if (instance.settings.clearOnMasterUpgrade && instance.master.id === ID) {
  2379. // instance.kill();
  2380. // }
  2381. //});
  2382. let i = 0;
  2383. const length = entities.length;
  2384. for (; i < length; i++) {
  2385. if (entities[i].settings.clearOnMasterUpgrade && entities[i].master.id === ID) {
  2386. entities[i].kill();
  2387. }
  2388. }
  2389. this.skill.update();
  2390. this.skill.changed = true;
  2391. this.refreshBodyAttributes();
  2392. }
  2393. }
  2394.  
  2395. damageMultiplier() {
  2396. switch (this.type) {
  2397. case 'swarm':
  2398. return 0.25 + 1.5 * util.clamp(this.range / (this.RANGE + 1), 0, 1);
  2399. default: return 1;
  2400. }
  2401. }
  2402.  
  2403. move() {
  2404. let g = {
  2405. x: this.control.goal.x - this.x,
  2406. y: this.control.goal.y - this.y,
  2407. },
  2408. gactive = (g.x !== 0 || g.y !== 0),
  2409. engine = {
  2410. x: 0,
  2411. y: 0,
  2412. },
  2413. a = this.acceleration / roomSpeed;
  2414. switch (this.motionType) {
  2415. case 'glide':
  2416. this.maxSpeed = this.topSpeed;
  2417. this.damp = 0.05;
  2418. break;
  2419. case 'motor':
  2420. this.maxSpeed = 0;
  2421. if (this.topSpeed) {
  2422. this.damp = a / this.topSpeed;
  2423. }
  2424. if (gactive) {
  2425. let len = Math.sqrt(g.x * g.x + g.y * g.y);
  2426. engine = {
  2427. x: a * g.x / len,
  2428. y: a * g.y / len,
  2429. };
  2430. }
  2431. break;
  2432. case 'swarm':
  2433. this.maxSpeed = this.topSpeed;
  2434. let l = util.getDistance({ x: 0, y: 0, }, g) + 1;
  2435. if (gactive && l > this.size) {
  2436. let desiredxspeed = (this.topSpeed) * g.x / l,
  2437. desiredyspeed = (this.topSpeed) * g.y / l,
  2438. turning = Math.sqrt(((this.topSpeed) * Math.max(1, this.range) + 1) / a);
  2439. engine = {
  2440. x: ((desiredxspeed - this.velocity.x) / Math.max(5, turning)) * 1.2145,
  2441. y: ((desiredyspeed - this.velocity.y) / Math.max(5, turning)) * 1.2145,
  2442. };
  2443. } else {
  2444. if (this.velocity.length < this.topSpeed) {
  2445. engine = {
  2446. x: this.velocity.x * a / 40,
  2447. y: this.velocity.y * a / 40,
  2448. };
  2449. }
  2450. }
  2451. break;
  2452. case 'chase':
  2453. if (gactive) {
  2454. let l = util.getDistance({ x: 0, y: 0, }, g);
  2455. if (l > this.size * 2) {
  2456. this.maxSpeed = this.topSpeed;
  2457. let desiredxspeed = this.topSpeed * g.x / l,
  2458. desiredyspeed = this.topSpeed * g.y / l;
  2459. engine = {
  2460. x: (desiredxspeed - this.velocity.x) * a,
  2461. y: (desiredyspeed - this.velocity.y) * a,
  2462. };
  2463. } else {
  2464. this.maxSpeed = 0;
  2465. }
  2466. } else {
  2467. this.maxSpeed = 0;
  2468. }
  2469. break;
  2470. case 'drift':
  2471. this.maxSpeed = 0;
  2472. engine = {
  2473. x: g.x * a,
  2474. y: g.y * a,
  2475. };
  2476. break;
  2477. case 'bound':
  2478. let bound = this.bound, ref = this.bond;
  2479. this.x = ref.x + ref.size * bound.offset * Math.cos(bound.direction + bound.angle + ref.facing);
  2480. this.y = ref.y + ref.size * bound.offset * Math.sin(bound.direction + bound.angle + ref.facing);
  2481. this.bond.velocity.x += bound.size * this.accel.x;
  2482. this.bond.velocity.y += bound.size * this.accel.y;
  2483. this.firingArc = [ref.facing + bound.angle, bound.arc / 2];
  2484. nullVector(this.accel);
  2485. this.blend = ref.blend;
  2486. break;
  2487. }
  2488. this.accel.x += engine.x * this.control.power;
  2489. this.accel.y += engine.y * this.control.power;
  2490. }
  2491.  
  2492. face() {
  2493. let t = this.control.target,
  2494. tactive = (t.x !== 0 || t.y !== 0),
  2495. oldFacing = this.facing;
  2496. switch(this.facingType) {
  2497. case 'autospin':
  2498. this.facing += 0.02 / roomSpeed;
  2499. break;
  2500. case 'turnWithSpeed':
  2501. this.facing += this.velocity.length / 90 * Math.PI / roomSpeed;
  2502. break;
  2503. case 'withMotion':
  2504. this.facing = this.velocity.direction;
  2505. break;
  2506. case 'smoothWithMotion':
  2507. case 'looseWithMotion':
  2508. this.facing += util.loopSmooth(this.facing, this.velocity.direction, 4 / roomSpeed);
  2509. break;
  2510. case 'withTarget':
  2511. case 'toTarget':
  2512. this.facing = Math.atan2(t.y, t.x);
  2513. break;
  2514. case 'locksFacing':
  2515. if (!this.control.alt) this.facing = Math.atan2(t.y, t.x);
  2516. break;
  2517. case 'looseWithTarget':
  2518. case 'looseToTarget':
  2519. case 'smoothToTarget':
  2520. this.facing += util.loopSmooth(this.facing, Math.atan2(t.y, t.x), 1 / roomSpeed);
  2521. break;
  2522. case 'bound':
  2523. let givenangle;
  2524. if (this.control.main) {
  2525. givenangle = Math.atan2(t.y, t.x);
  2526. let diff = util.angleDifference(givenangle, this.firingArc[0]);
  2527. if (Math.abs(diff) >= this.firingArc[1]) {
  2528. givenangle = this.firingArc[0];// - util.clamp(Math.sign(diff), -this.firingArc[1], this.firingArc[1]);
  2529. }
  2530. } else {
  2531. givenangle = this.firingArc[0];
  2532. }
  2533. this.facing += util.loopSmooth(this.facing, givenangle, 4 / roomSpeed);
  2534. break;
  2535. }
  2536. // Loop
  2537. while (this.facing < 0) {
  2538. this.facing += 2 * Math.PI;
  2539. }
  2540. while (this.facing > 2 * Math.PI) {
  2541. this.facing -= 2 * Math.PI;
  2542. }
  2543. this.vfacing = util.angleDifference(oldFacing, this.facing) * roomSpeed;
  2544. }
  2545.  
  2546. takeSelfie() {
  2547. this.flattenedPhoto = null;
  2548. this.photo = (this.settings.drawShape) ? this.camera() : this.photo = undefined;
  2549. }
  2550.  
  2551. physics() {
  2552. if (this.accel.x == null || this.velocity.x == null) {
  2553. util.error('Void Error!');
  2554. util.error(this.collisionArray);
  2555. util.error(this.label);
  2556. util.error(this);
  2557. nullVector(this.accel); nullVector(this.velocity);
  2558. }
  2559. // Apply acceleration
  2560. this.velocity.x += this.accel.x * timestep;
  2561. this.velocity.y += this.accel.y * timestep;
  2562. // Reset acceleration
  2563. //if (!this.accel <= 0) {
  2564. nullVector(this.accel);
  2565. //}
  2566. // Apply motion
  2567. this.stepRemaining = 1;
  2568. this.x += this.stepRemaining * this.velocity.x / roomSpeed;
  2569. this.y += this.stepRemaining * this.velocity.y / roomSpeed;
  2570. }
  2571.  
  2572. friction() {
  2573. var motion = this.velocity.length, excess = motion - this.maxSpeed;
  2574. if (excess > 0 && this.damp) {
  2575. var k = this.damp * timestep * 0.97, drag = excess / (k + 1), finalVelocity = this.maxSpeed + drag;
  2576. this.velocity.x = ((finalVelocity * this.velocity.x / motion)) * 0.97;
  2577. this.velocity.y = ((finalVelocity * this.velocity.y / motion)) * 0.97;
  2578. }
  2579. }
  2580.  
  2581. confinementToTheseEarthlyShackles() {
  2582. if (this.x == null || this.x == null) {
  2583. util.error('Void Error!');
  2584. util.error(this.collisionArray);
  2585. util.error(this.label);
  2586. util.error(this);
  2587. nullVector(this.accel); nullVector(this.velocity);
  2588. return 0;
  2589. }
  2590. if (!this.settings.canGoOutsideRoom) {
  2591. this.accel.x -= Math.min(this.x - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; //roomSpeed for next 3
  2592. this.accel.x -= Math.max(this.x + this.realSize - room.width - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
  2593. this.accel.y -= Math.min(this.y - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
  2594. this.accel.y -= Math.max(this.y + this.realSize - room.height - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed;
  2595. }
  2596. if (room.gameMode === 'tdm' && this.type !== 'food') {
  2597. let loc = { x: this.x, y: this.y, };
  2598. if (
  2599. (this.team !== -1 && room.isIn('bas1', loc)) ||
  2600. (this.team !== -2 && room.isIn('bas2', loc)) ||
  2601. (this.team !== -3 && room.isIn('bas3', loc)) ||
  2602. (this.team !== -4 && room.isIn('bas4', loc)) ||
  2603. (this.team !== -5 && room.isIn('bas5', loc)) ||
  2604. (this.team !== -6 && room.isIn('bas6', loc))
  2605.  
  2606. ) { this.kill(); }
  2607. }
  2608. }
  2609.  
  2610. contemplationOfMortality() {
  2611. if (this.invuln) {
  2612. this.damageRecieved = 0;
  2613. return 0;
  2614. }
  2615. // Life-limiting effects
  2616. if (this.settings.diesAtRange) {
  2617. this.range -= 1 / roomSpeed;
  2618. if (this.range < 0) {
  2619. this.kill();
  2620. }
  2621. }
  2622. if (this.settings.diesAtLowSpeed) {
  2623. if (!this.collisionArray.length && this.velocity.length < this.topSpeed / 2) {
  2624. this.health.amount -= this.health.getDamage(1 / roomSpeed);
  2625. }
  2626. }
  2627. // Shield regen and damage
  2628. if (this.shield.max) {
  2629. if (this.damageRecieved !== 0) {
  2630. let shieldDamage = this.shield.getDamage(this.damageRecieved);
  2631. this.damageRecieved -= shieldDamage;
  2632. this.shield.amount -= shieldDamage;
  2633. }
  2634. }
  2635. // Health damage
  2636. if (this.damageRecieved !== 0) {
  2637. let healthDamage = this.health.getDamage(this.damageRecieved);
  2638. this.blend.amount = 1;
  2639. this.health.amount -= healthDamage;
  2640. }
  2641. this.damageRecieved = 0;
  2642.  
  2643. // Check for death
  2644. if (this.isDead()) {
  2645. // Initalize message arrays
  2646. let killers = [], killTools = [], notJustFood = false;
  2647. // If I'm a tank, call me a nameless player
  2648. let name = (this.master.name == '') ?
  2649. (this.master.type === 'tank') ?
  2650. "a nameless player's " + this.label :
  2651. (this.master.type === 'miniboss') ?
  2652. "a visiting " + this.label :
  2653. util.addArticle(this.label)
  2654. :
  2655. this.master.name + "'s " + this.label;
  2656. // Calculate the jackpot
  2657. let jackpot = Math.ceil(util.getJackpot(this.skill.score) / this.collisionArray.length);
  2658. // Now for each of the things that kill me...
  2659. /*
  2660. this.collisionArray.forEach(instance => {
  2661. if (instance.type === 'wall') return 0;
  2662. if (instance.master.settings.acceptsScore) { // If it's not food, give its master the score
  2663. if (instance.master.type === 'tank' || instance.master.type === 'miniboss') notJustFood = true;
  2664. instance.master.skill.score += jackpot;
  2665. killers.push(instance.master); // And keep track of who killed me
  2666. } else if (instance.settings.acceptsScore) {
  2667. instance.skill.score += jackpot;
  2668. }
  2669. killTools.push(instance); // Keep track of what actually killed me
  2670. });
  2671. */
  2672.  
  2673. let i = 0;
  2674. const length = this.collisionArray.length;
  2675. for (; i < length; i++) {
  2676. //for (let instance of this.collisionArray) {
  2677. let instance = this.collisionArray[i];
  2678. if (instance.type === 'wall') return 0;
  2679. if (instance.master.settings.acceptsScore) { // If it's not food, give its master the score
  2680. if (instance.master.type === 'tank' || instance.master.type === 'miniboss') notJustFood = true;
  2681. instance.master.skill.score += jackpot;
  2682. killers.push(instance.master); // And keep track of who killed me
  2683. } else if (instance.settings.acceptsScore) {
  2684. instance.skill.score += jackpot;
  2685. }
  2686. killTools.push(instance); // Keep track of what actually killed me
  2687. }
  2688. // Remove duplicates
  2689. killers = killers.filter((elem, index, self) => { return index == self.indexOf(elem); });
  2690. // If there's no valid killers (you were killed by food), change the message to be more passive
  2691. let killText = (notJustFood) ? '' : "You have been killed by ",
  2692. dothISendAText = this.settings.givesKillMessage;
  2693. killers.forEach(instance => {
  2694. this.killCount.killers.push(instance.index);
  2695. if (this.type === 'tank') {
  2696. if (killers.length > 1) instance.killCount.assists++; else instance.killCount.solo++;
  2697. } else if (this.type === "miniboss") instance.killCount.bosses++;
  2698. });
  2699. // Add the killers to our death message, also send them a message
  2700. if (notJustFood) {
  2701. killers.forEach(instance => {
  2702. if (instance.master.type !== 'food' && instance.master.type !== 'crasher') {
  2703. killText += (instance.name == '') ? (killText == '') ? 'An unnamed player' : 'an unnamed player' : instance.name;
  2704. killText += ' and ';
  2705. }
  2706. // Only if we give messages
  2707. if (dothISendAText) {
  2708. instance.sendMessage('You killed ' + name + ((killers.length > 1) ? ' (with some help).' : '.'));
  2709. }
  2710. });
  2711. // Prepare the next part of the next
  2712. killText = killText.slice(0, -4);
  2713. killText += 'killed you with ';
  2714. }
  2715. // Broadcast
  2716. if (this.settings.broadcastMessage) sockets.broadcast(this.settings.broadcastMessage);
  2717. // Add the implements to the message
  2718. killTools.forEach((instance) => {
  2719. killText += util.addArticle(instance.label) + ' and ';
  2720. });
  2721. // Prepare it and clear the collision array.
  2722. killText = killText.slice(0, -5);
  2723. if (killText === 'You have been kille') killText = 'You have died a stupid death';
  2724. this.sendMessage(killText + '.');
  2725. // If I'm the leader, broadcast it:
  2726. if (this.id === room.topPlayerID) {
  2727. let usurptText = (this.name === '') ? 'The leader': this.name;
  2728. if (notJustFood) {
  2729. usurptText += ' has been usurped by';
  2730. killers.forEach(instance => {
  2731. usurptText += ' ';
  2732. usurptText += (instance.name === '') ? 'an unnamed player' : instance.name;
  2733. usurptText += ' and';
  2734. });
  2735. usurptText = usurptText.slice(0, -4);
  2736. usurptText += '!';
  2737. } else {
  2738. usurptText += ' fought a polygon... and the polygon won.';
  2739. }
  2740. sockets.broadcast(usurptText);
  2741. }
  2742. // Kill it
  2743. return 1;
  2744. }
  2745. return 0;
  2746. }
  2747.  
  2748. protect() {
  2749. entitiesToAvoid.push(this); this.isProtected = true;
  2750. }
  2751.  
  2752. sendMessage(message) { } // Dummy
  2753.  
  2754. kill() {
  2755. this.health.amount = -1;
  2756. }
  2757.  
  2758. spawn(...args) {
  2759. let e = new Entity(...args);
  2760. return e;
  2761. }
  2762.  
  2763. destroy() {
  2764. if (this.triggerFunctions.DEATH != null) {
  2765. this.triggerFunctions.DEATH(this);
  2766. }
  2767. // Remove from the protected entities list
  2768. if (this.isProtected) util.remove(entitiesToAvoid, entitiesToAvoid.indexOf(this));
  2769. // Remove from minimap
  2770. let i = minimap.findIndex(entry => { return entry[0] === this.id; });
  2771. if (i != -1) util.remove(minimap, i);
  2772. // Remove this from views
  2773. //views.forEach(v => v.remove(this));
  2774. let v = 0;
  2775. const length = views.length;
  2776. for (; v < length; v++) {
  2777. views[v].remove(this);
  2778. }
  2779. // Remove from parent lists if needed
  2780. if (this.parent != null) util.remove(this.parent.children, this.parent.children.indexOf(this));
  2781. // Kill all of its children
  2782. let ID = this.id;
  2783. let instance = entities.find(o => o.source.id === this.id);
  2784. if (instance !== undefined) {
  2785. if (instance.source.id === this.id) {
  2786. if (instance.settings.persistsAfterDeath) {
  2787. instance.source = instance;
  2788. } else {
  2789. instance.kill();
  2790. }
  2791. }
  2792. if (instance.parent && instance.parent.id === this.id) {
  2793. instance.parent = null;
  2794. }
  2795. if (instance.master.id === this.id) {
  2796. instance.kill();
  2797. instance.master = instance;
  2798. }
  2799. }
  2800. // Remove everything bound to it
  2801. if (this.turrets.length > 0) {
  2802. for (let t of this.turrets) {
  2803. t.destroy();
  2804. }
  2805. }
  2806. // Remove from the collision grid
  2807. this.removeFromGrid();
  2808. this.isGhost = true;
  2809. }
  2810.  
  2811. isDead() {
  2812. return this.health.amount <= 0;
  2813. }
  2814. }
  2815.  
  2816. /*** SERVER SETUP ***/
  2817. // Make a speed monitor
  2818. var logs = (() => {
  2819. let logger = (() => {
  2820. // The two basic functions
  2821. function set(obj) {
  2822. obj.time = util.time();
  2823. }
  2824. function mark(obj) {
  2825. obj.data.push(util.time() - obj.time);
  2826. }
  2827. function record(obj) {
  2828. let o = util.averageArray(obj.data);
  2829. obj.data = [];
  2830. return o;
  2831. }
  2832. function sum(obj) {
  2833. let o = util.sumArray(obj.data);
  2834. obj.data = [];
  2835. return o;
  2836. }
  2837. function tally(obj) {
  2838. obj.count++;
  2839. }
  2840. function count(obj) {
  2841. let o = obj.count;
  2842. obj.count = 0;
  2843. return o;
  2844. }
  2845. // Return the logger creator
  2846. return () => {
  2847. let internal = {
  2848. data: [],
  2849. time: util.time(),
  2850. count: 0,
  2851. };
  2852. // Return the new logger
  2853. return {
  2854. set: () => set(internal),
  2855. mark: () => mark(internal),
  2856. record: () => record(internal),
  2857. sum: () => sum(internal),
  2858. count: () => count(internal),
  2859. tally: () => tally(internal),
  2860. };
  2861. };
  2862. })();
  2863. // Return our loggers
  2864. return {
  2865. entities: logger(),
  2866. collide: logger(),
  2867. network: logger(),
  2868. minimap: logger(),
  2869. misc2: logger(),
  2870. misc3: logger(),
  2871. physics: logger(),
  2872. life: logger(),
  2873. selfie: logger(),
  2874. master: logger(),
  2875. activation: logger(),
  2876. loops: logger(),
  2877. };
  2878. })();
  2879.  
  2880. // Essential server requires
  2881. var express = require('express'),
  2882. http = require('http'),
  2883. url = require('url'),
  2884. WebSocket = require('ws'),
  2885. app = express(),
  2886. fs = require('fs'),
  2887. exportDefinitionsToClient = (() => {
  2888. function rounder(val) {
  2889. if (Math.abs(val) < 0.00001) val = 0;
  2890. return +val.toPrecision(6);
  2891. }
  2892. // Define mocking up functions
  2893. function getMockup(e, positionInfo) {
  2894. return {
  2895. index: e.index,
  2896. name: e.label,
  2897. x: rounder(e.x),
  2898. y: rounder(e.y),
  2899. color: e.color,
  2900. shape: e.shapeData,
  2901. //customshape: e.customshape,
  2902. size: rounder(e.size),
  2903. realSize: rounder(e.realSize),
  2904. facing: rounder(e.facing),
  2905. layer: e.layer,
  2906. statnames: e.settings.skillNames,
  2907. position: positionInfo,
  2908. guns: e.guns.map(function(gun) {
  2909. return {
  2910. offset: rounder(gun.offset),
  2911. direction: rounder(gun.direction),
  2912. length: rounder(gun.length),
  2913. width: rounder(gun.width),
  2914. aspect: rounder(gun.aspect),
  2915. angle: rounder(gun.angle),
  2916. };
  2917. }),
  2918. turrets: e.turrets.map(function(t) {
  2919. let out = getMockup(t, {});
  2920. out.sizeFactor = rounder(t.bound.size);
  2921. out.offset = rounder(t.bound.offset);
  2922. out.direction = rounder(t.bound.direction);
  2923. out.layer = rounder(t.bound.layer);
  2924. out.angle = rounder(t.bound.angle);
  2925. return out;
  2926. }),
  2927. };
  2928. }
  2929. function getDimensions(entities) {
  2930. /* Ritter's Algorithm (Okay it got serious modified for how we start it)
  2931. * 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..
  2932. */
  2933. let endpoints = [];
  2934. let pointDisplay = [];
  2935. let pushEndpoints = function(model, scale, focus={ x: 0, y: 0 }, rot=0) {
  2936. let s = Math.abs(model.shape);
  2937. let z = (Math.abs(s) > lazyRealSizes.length) ? 1 : lazyRealSizes[Math.abs(s)];
  2938. if (z === 1) { // Body (octagon if circle)
  2939. for (let i=0; i<2; i+=0.5) {
  2940. endpoints.push({x: focus.x + scale * Math.cos(i*Math.PI), y: focus.y + scale * Math.sin(i*Math.PI)});
  2941. }
  2942. } else { // Body (otherwise vertices)
  2943. for (let i=(s%2)?0:Math.PI/s; i<s; i++) {
  2944. let theta = (i / s) * 2 * Math.PI;
  2945. endpoints.push({x: focus.x + scale * z * Math.cos(theta), y: focus.y + scale * z * Math.sin(theta)});
  2946. }
  2947. }
  2948. function makeGunModel(gun) {
  2949. let h = (gun.aspect > 0) ? scale * gun.width / 2 * gun.aspect : scale * gun.width / 2;
  2950. let r = Math.atan2(h, scale * gun.length) + rot;
  2951. let l = Math.sqrt(scale * scale * gun.length * gun.length + h * h);
  2952. let x = focus.x + scale * gun.offset * Math.cos(gun.direction + gun.angle + rot);
  2953. let y = focus.y + scale * gun.offset * Math.sin(gun.direction + gun.angle + rot);
  2954. endpoints.push({
  2955. x: x + l * Math.cos(gun.angle + r),
  2956. y: y + l * Math.sin(gun.angle + r),
  2957. });
  2958. endpoints.push({
  2959. x: x + l * Math.cos(gun.angle - r),
  2960. y: y + l * Math.sin(gun.angle - r),
  2961. });
  2962. pointDisplay.push({
  2963. x: x + l * Math.cos(gun.angle + r),
  2964. y: y + l * Math.sin(gun.angle + r),
  2965. });
  2966. pointDisplay.push({
  2967. x: x + l * Math.cos(gun.angle - r),
  2968. y: y + l * Math.sin(gun.angle - r),
  2969. });
  2970. }
  2971. //model.guns.forEach();
  2972. for (let gun of model.guns) {
  2973. makeGunModel(gun);
  2974. }
  2975.  
  2976. function makeTurretModel(turret) {
  2977. pushEndpoints(
  2978. turret, turret.bound.size,
  2979. { x: turret.bound.offset * Math.cos(turret.bound.angle), y: turret.bound.offset * Math.sin(turret.bound.angle) },
  2980. turret.bound.angle
  2981. );
  2982. }
  2983. //model.turrets.forEach();
  2984. for (let turret of model.turrets) {
  2985. makeTurretModel(turret);
  2986. }
  2987. };
  2988. pushEndpoints(entities, 1);
  2989. // 2) Find their mass center
  2990. let massCenter = { x: 0, y: 0 };
  2991. /*endpoints.forEach(function(point) {
  2992. massCenter.x += point.x;
  2993. massCenter.y += point.y;
  2994. });
  2995. massCenter.x /= endpoints.length;
  2996. massCenter.y /= endpoints.length;*/
  2997. // 3) Choose three different points (hopefully ones very far from each other)
  2998. let chooseFurthestAndRemove = function(furthestFrom) {
  2999. let index = 0;
  3000. if (furthestFrom != -1) {
  3001. let list = new goog.structs.PriorityQueue();
  3002. let d;
  3003. for (let i=0; i<endpoints.length; i++) {
  3004. let thisPoint = endpoints[i];
  3005. d = Math.pow(thisPoint.x - furthestFrom.x, 2) + Math.pow(thisPoint.y - furthestFrom.y, 2) + 1;
  3006. list.enqueue(1/d, i);
  3007. }
  3008. index = list.dequeue();
  3009. }
  3010. let output = endpoints[index];
  3011. endpoints.splice(index, 1);
  3012. return output;
  3013. };
  3014. let point1 = chooseFurthestAndRemove(massCenter); // Choose the point furthest from the mass center
  3015. let point2 = chooseFurthestAndRemove(point1); // And the point furthest from that
  3016. // And the point which maximizes the area of our triangle (a loose look at this one)
  3017. let chooseBiggestTriangleAndRemove = function(point1, point2) {
  3018. let list = new goog.structs.PriorityQueue();
  3019. let index = 0;
  3020. let a;
  3021. for (let i=0; i<endpoints.length; i++) {
  3022. let thisPoint = endpoints[i];
  3023. a = Math.pow(thisPoint.x - point1.x, 2) + Math.pow(thisPoint.y - point1.y, 2) +
  3024. Math.pow(thisPoint.x - point2.x, 2) + Math.pow(thisPoint.y - point2.y, 2);
  3025. /* We need neither to calculate the last part of the triangle
  3026. * (because it's always the same) nor divide by 2 to get the
  3027. * actual area (because we're just comparing it)
  3028. */
  3029. list.enqueue(1/a, i);
  3030. }
  3031. index = list.dequeue();
  3032. let output = endpoints[index];
  3033. endpoints.splice(index, 1);
  3034. return output;
  3035. };
  3036. let point3 = chooseBiggestTriangleAndRemove(point1, point2);
  3037. // 4) Define our first enclosing circle as the one which seperates these three furthest points
  3038. function circleOfThreePoints(p1, p2, p3) {
  3039. let x1 = p1.x;
  3040. let y1 = p1.y;
  3041. let x2 = p2.x;
  3042. let y2 = p2.y;
  3043. let x3 = p3.x;
  3044. let y3 = p3.y;
  3045. let denom =
  3046. x1 * (y2 - y3) -
  3047. y1 * (x2 - x3) +
  3048. x2 * y3 -
  3049. x3 * y2;
  3050. let xy1 = x1*x1 + y1*y1;
  3051. let xy2 = x2*x2 + y2*y2;
  3052. let xy3 = x3*x3 + y3*y3;
  3053. let x = ( // Numerator
  3054. xy1 * (y2 - y3) +
  3055. xy2 * (y3 - y1) +
  3056. xy3 * (y1 - y2)
  3057. ) / (2 * denom);
  3058. let y = ( // Numerator
  3059. xy1 * (x3 - x2) +
  3060. xy2 * (x1 - x3) +
  3061. xy3 * (x2 - x1)
  3062. ) / (2 * denom);
  3063. let r = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
  3064. let r2 = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2));
  3065. let r3 = Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2));
  3066. if (r != r2 || r != r3) {
  3067. //util.log('somethings fucky');
  3068. }
  3069. return { x: x, y: y, radius: r };
  3070. }
  3071. let c = circleOfThreePoints(point1, point2, point3);
  3072. pointDisplay = [
  3073. { x: rounder(point1.x), y: rounder(point1.y), },
  3074. { x: rounder(point2.x), y: rounder(point2.y), },
  3075. { x: rounder(point3.x), y: rounder(point3.y), },
  3076. ];
  3077. let centerOfCircle = { x: c.x, y: c.y };
  3078. let radiusOfCircle = c.radius;
  3079. // 5) Check to see if we enclosed everything
  3080. function checkingFunction() {
  3081. for(var i=endpoints.length; i>0; i--) {
  3082. // Select the one furthest from the center of our circle and remove it
  3083. point1 = chooseFurthestAndRemove(centerOfCircle);
  3084. let vectorFromPointToCircleCenter = new Vector(centerOfCircle.x - point1.x, centerOfCircle.y - point1.y);
  3085. // 6) If we're still outside of this circle build a new circle which encloses the old circle and the new point
  3086. if (vectorFromPointToCircleCenter.length > radiusOfCircle) {
  3087. pointDisplay.push({ x: rounder(point1.x), y: rounder(point1.y), });
  3088. // Define our new point as the far side of the cirle
  3089. let dir = vectorFromPointToCircleCenter.direction;
  3090. point2 = {
  3091. x: centerOfCircle.x + radiusOfCircle * Math.cos(dir),
  3092. y: centerOfCircle.y + radiusOfCircle * Math.sin(dir),
  3093. };
  3094. break;
  3095. }
  3096. }
  3097. // False if we checked everything, true if we didn't
  3098. return !!endpoints.length;
  3099. }
  3100. while (checkingFunction()) { // 7) Repeat until we enclose everything
  3101. centerOfCircle = {
  3102. x: (point1.x + point2.x) / 2,
  3103. y: (point1.y + point2.y) / 2,
  3104. };
  3105. radiusOfCircle = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)) / 2;
  3106. }
  3107. // 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
  3108. return {
  3109. middle: { x: rounder(centerOfCircle.x), y: 0 },
  3110. axis: rounder(radiusOfCircle * 2),
  3111. points: pointDisplay,
  3112. };
  3113. }
  3114. // Save them
  3115. let mockupData = [];
  3116. for (let k in Class) {
  3117. try {
  3118. if (!Class.hasOwnProperty(k)) continue;
  3119. let type = Class[k];
  3120. // Create a reference entities which we'll then take an image of.
  3121. let temptank = new Entity({x: 0, y: 0});
  3122. temptank.define(type, true);
  3123. temptank.name = type.LABEL; // Rename it (for the upgrades menu).
  3124. // Fetch the mockup.
  3125. type.mockup = {
  3126. body: temptank.camera(true),
  3127. position: getDimensions(temptank),
  3128. };
  3129. // This is to pass the size information about the mockup that we didn't have until we created the mockup
  3130. type.mockup.body.position = type.mockup.position;
  3131. // Add the new data to the thing.
  3132. mockupData.push(getMockup(temptank, type.mockup.position));
  3133. // Kill the reference entities.
  3134. temptank.destroy();
  3135. } catch(error) {
  3136. util.error(error);
  3137. util.error(k);
  3138. util.error(Class[k]);
  3139. }
  3140. }
  3141. // Remove them
  3142. purgeEntities();
  3143. // Build the function to return
  3144. let writeData = JSON.stringify(mockupData);
  3145. return loc => {
  3146. util.log('Preparing definition export.');
  3147. fs.writeFileSync(loc, writeData, 'utf8', (err) => {
  3148. if (err) return util.error(err);
  3149. });
  3150. util.log('Mockups written to ' + loc + '!');
  3151. };
  3152. })(),
  3153. generateVersionControlHash = (() => {
  3154. let crypto = require('crypto');
  3155. let write = (() => {
  3156. let hash = [null, null];
  3157. return (loc, data, numb) => {
  3158. // The callback is executed on reading completion
  3159. hash[numb] = crypto.createHash('sha1').update(data).digest('hex');
  3160. if (hash[0] && hash[1]) {
  3161. let finalHash = hash[0] + hash[1];
  3162. util.log('Client hash generated. Hash is "' + finalHash + '".');
  3163. // Write the hash to a place the client can read it.
  3164. fs.writeFileSync(loc, finalHash, 'utf8', err => {
  3165. if (err) return util.error(err);
  3166. });
  3167. util.log('Hash written to ' + loc + '!');
  3168. }
  3169. };
  3170. })();
  3171. return loc => {
  3172. let path1 = __dirname + '/../client/js/app.js';
  3173. let path2 = __dirname + '/lib/definitions.js';
  3174. util.log('Building client version hash, reading from ' + path1 + ' and ' + path2 + '...');
  3175. // Read the client application
  3176. fs.readFile(path1, 'utf8', (err, data) => {
  3177. if (err) return util.error(err);
  3178. util.log('app.js complete.');
  3179. write(loc, data, 0);
  3180. });
  3181. fs.readFile(path2, 'utf8', (err, data) => {
  3182. if (err) return util.error(err);
  3183. util.log('definitions.js complete.');
  3184. write(loc, data, 1);
  3185. });
  3186. };
  3187. })();
  3188.  
  3189. // Give the client upon request
  3190. exportDefinitionsToClient(__dirname + '/../client/json/mockups.json');
  3191. generateVersionControlHash(__dirname + '/../client/api/vhash');
  3192. if (c.servesStatic) app.use(express.static(__dirname + '/../client'));
  3193.  
  3194. // Websocket behavior
  3195. const sockets = (() => {
  3196. const protocol = require('./lib/fasttalk');
  3197. let clients = [], players = [], bannedIPs = [], suspiciousIPs = [], connectedIPs = [],
  3198. bannedNames = [
  3199. 'FREE_FOOD_LUCARIO',
  3200. 'FREE FOOD'
  3201. ];
  3202. return {
  3203. broadcast: message => {
  3204. for (let socket of clients) {
  3205. socket.talk('m', message);
  3206. };
  3207. },
  3208. connect: (() => {
  3209. // Define shared functions
  3210. // Closing the socket
  3211. function close(socket) {
  3212. // Free the IP
  3213. let n = connectedIPs.findIndex(w => { return w.ip === socket.ip; });
  3214. if (n !== -1) {
  3215. util.log(socket.ip + " disconnected.");
  3216. util.remove(connectedIPs, n);
  3217. }
  3218. // Free the token
  3219. if (socket.key != '') {
  3220. keys.push(socket.key);
  3221. util.log("Token freed.");
  3222. }
  3223. // Figure out who the player was
  3224. let player = socket.player,
  3225. index = players.indexOf(player);
  3226. // Remove the player if one was created
  3227. if (index != -1) {
  3228. // Kill the body if it exists
  3229. if (player.body != null) {
  3230. player.body.invuln = false;
  3231. timer.setTimeout(() => {
  3232. player.body.kill();
  3233. }, '', '10000m');
  3234. }
  3235. // Disconnect everything
  3236. util.log('[INFO] User ' + player.name + ' disconnected!');
  3237. util.remove(players, index);
  3238. } else {
  3239. util.log('[INFO] A player disconnected before entering the game.');
  3240. }
  3241. // Free the view
  3242. util.remove(views, views.indexOf(socket.view));
  3243. // Remove the socket
  3244. util.remove(clients, clients.indexOf(socket));
  3245. util.log('[INFO] Socket closed. Views: ' + views.length + '. Clients: ' + clients.length + '.');
  3246. }
  3247. // Banning
  3248. function ban(socket) {
  3249. if (bannedIPs.findIndex(ip => { return ip === socket.ip; }) === -1) {
  3250. bannedIPs.push(socket.ip);
  3251. } // No need for duplicates
  3252. socket.terminate();
  3253. util.warn(socket.ip + ' banned!');
  3254. }
  3255. // Being kicked
  3256. function kick(socket, reason = 'No reason given.') {
  3257. let n = suspiciousIPs.findIndex(n => { return n.ip === socket.ip; });
  3258. if (n === -1) {
  3259. suspiciousIPs.push({ ip: socket.ip, warns: 1, });
  3260. util.warn(reason + ' Kicking. 1 warning.');
  3261. } else {
  3262. suspiciousIPs[n].warns++;
  3263. util.warn(reason + ' Kicking. ' + suspiciousIPs[n].warns + ' warnings.');
  3264. if (suspiciousIPs[n].warns >= c.socketWarningLimit) {
  3265. ban(socket);
  3266. }
  3267. }
  3268. socket.lastWords('K');
  3269. }
  3270. // Handle incoming messages
  3271. function incoming(message, socket) {
  3272. // Only accept binary
  3273. if (!(message instanceof ArrayBuffer)) { socket.kick('Non-binary packet.'); return 1; }
  3274. // Decode it
  3275. let m = protocol.decode(message);
  3276. // Make sure it looks legit
  3277. if (m === -1) { socket.kick('Malformed packet.'); return 1; }
  3278. // Log the message request
  3279. socket.status.requests++;
  3280. // Remember who we are
  3281. let player = socket.player;
  3282. // Handle the request
  3283. switch (m.shift()) {
  3284. case 'k': { // key verification
  3285. //if (m.length !== 1) { socket.kick('Ill-sized key request.'); return 1; }
  3286. // Get data
  3287. let key = m[0];
  3288. // Verify it
  3289. if (typeof key !== 'string') { socket.kick('Weird key offered.'); return 1; }
  3290. if (key.length > 64) { socket.kick('Overly-long key offered.'); return 1; }
  3291. if (socket.status.verified) { socket.kick('Duplicate player spawn attempt.'); return 1; }
  3292. // Otherwise proceed to check if it's available.
  3293. if (keys.indexOf(key) != -1) {
  3294. // Save the key
  3295. socket.key = key.substr(0, 64);
  3296. // Make it unavailable
  3297. util.remove(keys, keys.indexOf(key));
  3298. socket.verified = true;
  3299. // Proceed
  3300. socket.talk('w', true);
  3301. util.log('[INFO] A socket was verified with the token: '); util.log(key);
  3302. util.log('Clients: ' + clients.length);
  3303. } else {
  3304. // If not, kick 'em (nicely)
  3305. if (c.tokenReq == true) {
  3306. util.log('[INFO] Invalid player verification attempt.');
  3307. socket.lastWords('w', false);
  3308. } else {
  3309. socket.talk('w', true);
  3310. util.log('[INFO] A socket was verified with no token');
  3311. util.log('Clients: ' + clients.length);
  3312. }
  3313. }
  3314. } break;
  3315. case 's': { // spawn request
  3316. if (!socket.status.deceased) { socket.kick('Trying to spawn while already alive.'); return 1; }
  3317. if (m.length !== 2) { socket.kick('Ill-sized spawn request.'); return 1; }
  3318. // Get data
  3319. let name = m[0].replace(c.BANNED_CHARACTERS_REGEX, '');
  3320. let needsRoom = m[1];
  3321. // Verify it
  3322. if (typeof name != 'string') { socket.kick('Bad spawn request.'); return 1; }
  3323. if (encodeURI(name).split(/%..|./).length > 48) { socket.kick('Overly-long name.'); return 1; }
  3324. if (needsRoom !== 0 && needsRoom !== 1) { socket.kick('Bad spawn request.'); return 1; }
  3325. // Bring to life
  3326. socket.status.deceased = false;
  3327. // Define the player.
  3328. if (players.indexOf(socket.player) != -1) { util.remove(players, players.indexOf(socket.player)); }
  3329. // Free the old view
  3330. if (views.indexOf(socket.view) != -1) { util.remove(views, views.indexOf(socket.view)); socket.makeView(); }
  3331. socket.player = socket.spawn(name);
  3332. // Give it the room state
  3333. if (needsRoom) {
  3334. socket.talk(
  3335. 'R',
  3336. room.width,
  3337. room.height,
  3338. JSON.stringify(c.ROOM_SETUP),
  3339. JSON.stringify(util.serverStartTime),
  3340. roomSpeed
  3341. );
  3342. }
  3343. // Start the update rhythm immediately
  3344. socket.update(0);
  3345. // Log it
  3346. util.log('[INFO] ' + (m[0]) + (needsRoom ? ' joined' : ' rejoined') + ' the game! Players: ' + players.length);
  3347. } break;
  3348. case 'S': { // clock syncing
  3349. if (m.length !== 1) { socket.kick('Ill-sized sync packet.'); return 1; }
  3350. // Get data
  3351. let synctick = m[0];
  3352. // Verify it
  3353. if (typeof synctick !== 'number') { socket.kick('Weird sync packet.'); return 1; }
  3354. // Bounce it back
  3355. socket.talk('S', synctick, util.time());
  3356. } break;
  3357. case 'p': { // ping
  3358. if (m.length !== 1) { socket.kick('Ill-sized ping.'); return 1; }
  3359. // Get data
  3360. let ping = m[0];
  3361. // Verify it
  3362. if (typeof ping !== 'number') { socket.kick('Weird ping.'); return 1; }
  3363. // Pong
  3364. socket.talk('p', m[0]); // Just pong it right back
  3365. socket.status.lastHeartbeat = util.time();
  3366. } break;
  3367. case 'd': { // downlink
  3368. if (m.length !== 1) { socket.kick('Ill-sized downlink.'); return 1; }
  3369. // Get data
  3370. let time = m[0];
  3371. // Verify data
  3372. if (typeof time !== 'number') { socket.kick('Bad downlink.'); return 1; }
  3373. // The downlink indicates that the client has received an update and is now ready to receive more.
  3374. socket.status.receiving = 0;
  3375. socket.camera.ping = util.time() - time;
  3376. socket.camera.lastDowndate = util.time();
  3377. // Schedule a new update cycle
  3378. // Either fires immediately or however much longer it's supposed to wait per the config.
  3379. socket.update(Math.max(0, (1000 / c.networkUpdateFactor) - (util.time() - socket.camera.lastUpdate)));
  3380. } break;
  3381. case 'C': { // command packet
  3382. if (m.length !== 3) { socket.kick('Ill-sized command packet.'); return 1; }
  3383. // Get data
  3384. let target = {
  3385. x: m[0],
  3386. y: m[1],
  3387. },
  3388. commands = m[2];
  3389. // Verify data
  3390. if (typeof target.x !== 'number' || typeof target.y !== 'number' || typeof commands !== 'number') { socket.kick('Weird downlink.'); return 1; }
  3391. if (commands > 255 || target.x !== Math.round(target.x) || target.y !== Math.round(target.y)) { socket.kick('Malformed command packet.'); return 1; }
  3392. // Put the new target in
  3393. player.target = target;
  3394. // Process the commands
  3395. let val = [false, false, false, false, false, false, false, false];
  3396. for (let i=7; i>=0; i--) {
  3397. if (commands >= Math.pow(2, i)) {
  3398. commands -= Math.pow(2, i);
  3399. val[i] = true;
  3400. }
  3401. }
  3402. player.command.up = val[0];
  3403. player.command.down = val[1];
  3404. player.command.left = val[2];
  3405. player.command.right = val[3];
  3406. player.command.lmb = val[4];
  3407. player.command.mmb = val[5];
  3408. player.command.rmb = val[6];
  3409. // Update the thingy
  3410. socket.timeout.set(commands);
  3411. } break;
  3412. case 't': { // player toggle
  3413. if (m.length !== 1) { socket.kick('Ill-sized toggle.'); return 1; }
  3414. // Get data
  3415. let given = '',
  3416. tog = m[0];
  3417. // Verify request
  3418. if (typeof tog !== 'number') { socket.kick('Weird toggle.'); return 1; }
  3419. // Decipher what we're supposed to do.
  3420. switch (tog) {
  3421. case 0: given = 'autospin'; break;
  3422. case 1: given = 'autofire'; break;
  3423. case 2: given = 'override'; break;
  3424. // Kick if it sent us shit.
  3425. default: socket.kick('Bad toggle.'); return 1;
  3426. }
  3427. // Apply a good request.
  3428. if (player.command != null && player.body != null) {
  3429. player.command[given] = !player.command[given];
  3430. // Send a message.
  3431. player.body.sendMessage(given.charAt(0).toUpperCase() + given.slice(1) + ((player.command[given]) ? ' enabled.' : ' disabled.'));
  3432. }
  3433. } break;
  3434. case 'U': { // upgrade request
  3435. if (m.length !== 1) { socket.kick('Ill-sized upgrade request.'); return 1; }
  3436. // Get data
  3437. let number = m[0];
  3438. // Verify the request
  3439. if (typeof number != 'number' || number < 0) { socket.kick('Bad upgrade request.'); return 1; }
  3440. // Upgrade it
  3441. if (player.body != null) {
  3442. player.body.upgrade(number); // Ask to upgrade
  3443. }
  3444. } break;
  3445. case 'x': { // skill upgrade request
  3446. if (m.length !== 1) { socket.kick('Ill-sized skill request.'); return 1; }
  3447. let number = m[0], stat = '';
  3448. // Verify the request
  3449. if (typeof number != 'number') { socket.kick('Weird stat upgrade request.'); return 1; }
  3450. // Decipher it
  3451. switch (number) {
  3452. case 0: stat = 'atk'; break;
  3453. case 1: stat = 'hlt'; break;
  3454. case 2: stat = 'spd'; break;
  3455. case 3: stat = 'str'; break;
  3456. case 4: stat = 'pen'; break;
  3457. case 5: stat = 'dam'; break;
  3458. case 6: stat = 'rld'; break;
  3459. case 7: stat = 'mob'; break;
  3460. case 8: stat = 'rgn'; break;
  3461. case 9: stat = 'shi'; break;
  3462. default: socket.kick('Unknown stat upgrade request.'); return 1;
  3463. }
  3464. // Apply it
  3465. if (player.body != null) {
  3466. player.body.skillUp(stat); // Ask to upgrade a stat
  3467. }
  3468. } break;
  3469. case 'L': { // level up cheat
  3470. if (m.length !== 0) { socket.kick('Ill-sized level-up request.'); return 1; }
  3471. // cheatingbois
  3472. //if (socket.key ==='ttoken1' || socket.key ==='treebacon' || socket.key ==='ttoken3' || socket.key ==='ttoken4' || socket.key ==='ttoken5'|| socket.key === 'syncinussecrettoken' || socket.key === 'williamsecrettoken') {
  3473. if (player.body != null) { if (player.body.skill.level < c.SKILL_CHEAT_CAP || ((socket.key === 'testk' || socket.key ==='testl') && player.body.skill.level < 45)) {
  3474. player.body.skill.score += player.body.skill.levelScore;
  3475. player.body.skill.maintain();
  3476. player.body.refreshBodyAttributes();
  3477. } } //}
  3478. } break;
  3479. case '0': { // testbed cheat
  3480. if (m.length !== 0) { socket.kick('no BT for you >:c'); return 1; }
  3481. // cheatingbois
  3482. 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') {
  3483. player.body.define(Class.testbed);
  3484. } }
  3485. } break;
  3486. case 'K': { // teleport cheat
  3487. 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 ) {
  3488. player.body.x = player.body.x + player.body.control.target.x
  3489. player.body.y = player.body.y + player.body.control.target.y}
  3490. } break;
  3491. case 'B': {
  3492. if (socket.key === 'ttoken1' || socket.key === 'atlantissecrettoken') {
  3493. player.body.define(Class.bigboi);
  3494. }
  3495. } break;
  3496. case 'X': {
  3497. if (socket.key === 'ttoken1') {
  3498. player.body.define(Class.chungus);
  3499. }
  3500. } break;
  3501. case 'z': { // leaderboard desync report
  3502. if (m.length !== 0) { socket.kick('Ill-sized level-up request.'); return 1; }
  3503. // Flag it to get a refresh on the next cycle
  3504. socket.status.needsFullLeaderboard = true;
  3505. } break;
  3506. default: socket.kick('Bad packet index.');
  3507. }
  3508. }
  3509. // Monitor traffic and handle inactivity disconnects
  3510. function traffic(socket) {
  3511. let strikes = 0;
  3512. // This function will be called in the slow loop
  3513. return () => {
  3514. // Kick if it's d/c'd
  3515. if (util.time() - socket.status.lastHeartbeat > c.maxHeartbeatInterval) {
  3516. socket.kick('Heartbeat lost.'); return 0;
  3517. }
  3518. // Add a strike if there's more than 50 requests in a second
  3519. if (socket.status.requests > 50) {
  3520. strikes++;
  3521. } else {
  3522. strikes = 0;
  3523. }
  3524. // Kick if we've had 3 violations in a row
  3525. if (strikes > 3) {
  3526. socket.kick('Socket traffic volume violation!'); return 0;
  3527. }
  3528. // Reset the requests
  3529. socket.status.requests = 0;
  3530. };
  3531. }
  3532. // Make a function to spawn new players
  3533. const spawn = (() => {
  3534. // Define guis
  3535. let newgui = (() => {
  3536. // This is because I love to cheat
  3537. // Define a little thing that should automatically keep
  3538. // track of whether or not it needs to be updated
  3539. function floppy(value = null) {
  3540. let flagged = true;
  3541. return {
  3542. // The update method
  3543. update: (newValue) => {
  3544. let eh = false;
  3545. if (value == null) { eh = true; }
  3546. else {
  3547. if (typeof newValue != typeof value) { eh = true; }
  3548. // Decide what to do based on what type it is
  3549. switch (typeof newValue) {
  3550. case 'number':
  3551. case 'string': {
  3552. if (newValue !== value) { eh = true; }
  3553. } break;
  3554. case 'object': {
  3555. if (Array.isArray(newValue)) {
  3556. if (newValue.length !== value.length) { eh = true; }
  3557. else {
  3558. for (let i=0, len=newValue.length; i<len; i++) {
  3559. if (newValue[i] !== value[i]) eh = true;
  3560. }
  3561. }
  3562. break;
  3563. }
  3564. } // jshint ignore:line
  3565. default:
  3566. util.error(newValue);
  3567. throw new Error('Unsupported type for a floppyvar!');
  3568. }
  3569. }
  3570. // Update if neeeded
  3571. if (eh) {
  3572. flagged = true;
  3573. value = newValue;
  3574. }
  3575. },
  3576. // The return method
  3577. publish: () => {
  3578. if (flagged && value != null) {
  3579. flagged = false;
  3580. return value;
  3581. }
  3582. },
  3583. };
  3584. }
  3585. // This keeps track of the skills container
  3586. function container(player) {
  3587. let vars = [],
  3588. skills = player.body.skill,
  3589. out = [],
  3590. statnames = ['atk', 'hlt', 'spd', 'str', 'pen', 'dam', 'rld', 'mob', 'rgn', 'shi'];
  3591. // Load everything (b/c I'm too lazy to do it manually)
  3592. let i = 0;
  3593. const length = statnames.length;
  3594.  
  3595. for (; i < length; i++) {
  3596. vars.push(floppy());
  3597. vars.push(floppy());
  3598. vars.push(floppy());
  3599. }
  3600.  
  3601. /*
  3602. statnames.forEach(a => {
  3603. vars.push(floppy());
  3604. vars.push(floppy());
  3605. vars.push(floppy());
  3606. });
  3607. */
  3608.  
  3609. return {
  3610. update: () => {
  3611. let needsupdate = false, i = 0;
  3612. // Update the things
  3613. /*
  3614. statnames.forEach(a => {
  3615. vars[i++].update(skills.title(a));
  3616. vars[i++].update(skills.cap(a));
  3617. vars[i++].update(skills.cap(a, true));
  3618. });
  3619. */
  3620.  
  3621.  
  3622. let j = 0;
  3623. const length = statnames.length;
  3624. for (; j < length; j++) {
  3625. vars[i++].update(skills.title(statnames[j]));
  3626. vars[i++].update(skills.cap(statnames[j]));
  3627. vars[i++].update(skills.cap(statnames[j], true));
  3628. }
  3629.  
  3630. /* This is a for and not a find because we need
  3631. * each floppy cyles or if there's multiple changes
  3632. * (there will be), we'll end up pushing a bunch of
  3633. * excessive updates long after the first and only
  3634. * needed one as it slowly hits each updated value
  3635. */
  3636. for (let e of vars) { if (e.publish() != null) needsupdate = true; }
  3637. if (needsupdate) {
  3638. // Update everything
  3639. for (let a of statnames) {
  3640. out.push(skills.title(a));
  3641. out.push(skills.cap(a));
  3642. out.push(skills.cap(a, true));
  3643. }
  3644. }
  3645. },
  3646. /* The reason these are seperate is because if we can
  3647. * can only update when the body exists, we might have
  3648. * a situation where we update and it's non-trivial
  3649. * so we need to publish but then the body dies and so
  3650. * we're forever sending repeated data when we don't
  3651. * need to. This way we can flag it as already sent
  3652. * regardless of if we had an update cycle.
  3653. */
  3654. publish: () => {
  3655. if (out.length) { let o = out.splice(0, out.length); out = []; return o; }
  3656. },
  3657. };
  3658. }
  3659. // This makes a number for transmission
  3660. function getstuff(s) {
  3661. let val = 0;
  3662. val += 0x1 * s.amount('atk');
  3663. val += 0x10 * s.amount('hlt');
  3664. val += 0x100 * s.amount('spd');
  3665. val += 0x1000 * s.amount('str');
  3666. val += 0x10000 * s.amount('pen');
  3667. val += 0x100000 * s.amount('dam');
  3668. val += 0x1000000 * s.amount('rld');
  3669. val += 0x10000000 * s.amount('mob');
  3670. val += 0x100000000 * s.amount('rgn');
  3671. val += 0x1000000000 * s.amount('shi');
  3672. return val.toString(36);
  3673. }
  3674. // These are the methods
  3675. function update(gui) {
  3676. let b = gui.master.body;
  3677. // We can't run if we don't have a body to look at
  3678. if (!b) return 0;
  3679. gui.bodyid = b.id;
  3680. // Update most things
  3681. gui.fps.update(Math.min(1, global.fps / roomSpeed / 1000 * 60));
  3682. gui.color.update(gui.master.teamColor);
  3683. gui.label.update(b.index);
  3684. gui.score.update(b.skill.score);
  3685. gui.points.update(b.skill.points);
  3686. // Update the upgrades
  3687. let upgrades = [];
  3688. //b.upgrades.forEach(function(e) {
  3689. // if (b.skill.level >= e.level) {
  3690. // upgrades.push(e.index);
  3691. // }
  3692. //});
  3693. let i = 0;
  3694. const length = b.upgrades.length;
  3695. for (; i < length; i++) {
  3696. if (b.skill.level >= b.upgrades[i].level) {
  3697. upgrades.push(b.upgrades[i].index);
  3698. }
  3699. }
  3700.  
  3701. gui.upgrades.update(upgrades);
  3702. // Update the stats and skills
  3703. gui.stats.update();
  3704. gui.skills.update(getstuff(b.skill));
  3705. // Update physics
  3706. gui.accel.update(b.acceleration);
  3707. gui.topspeed.update(b.topSpeed);
  3708. }
  3709. function publish(gui) {
  3710. let o = {
  3711. fps: gui.fps.publish(),
  3712. label: gui.label.publish(),
  3713. score: gui.score.publish(),
  3714. points: gui.points.publish(),
  3715. upgrades: gui.upgrades.publish(),
  3716. color: gui.color.publish(),
  3717. statsdata: gui.stats.publish(),
  3718. skills: gui.skills.publish(),
  3719. accel: gui.accel.publish(),
  3720. top: gui.topspeed.publish(),
  3721. };
  3722. // Encode which we'll be updating and capture those values only
  3723. let oo = [0];
  3724. if (o.fps != null) { oo[0] += 0x0001; oo.push(o.fps || 1); }
  3725. if (o.label != null) { oo[0] += 0x0002;
  3726. oo.push(o.label);
  3727. oo.push(o.color || gui.master.teamColor);
  3728. oo.push(gui.bodyid);
  3729. }
  3730. if (o.score != null) { oo[0] += 0x0004; oo.push(o.score); }
  3731. if (o.points != null) { oo[0] += 0x0008; oo.push(o.points); }
  3732. if (o.upgrades != null) { oo[0] += 0x0010; oo.push(o.upgrades.length, ...o.upgrades); }
  3733. if (o.statsdata != null){ oo[0] += 0x0020; oo.push(...o.statsdata); }
  3734. if (o.skills != null) { oo[0] += 0x0040; oo.push(o.skills); }
  3735. if (o.accel != null) { oo[0] += 0x0080; oo.push(o.accel); }
  3736. if (o.top != null) { oo[0] += 0x0100; oo.push(o.top); }
  3737. // Output it
  3738. return oo;
  3739. }
  3740. // This is the gui creator
  3741. return (player) => {
  3742. // This is the protected gui data
  3743. let gui = {
  3744. master: player,
  3745. fps: floppy(),
  3746. label: floppy(),
  3747. score: floppy(),
  3748. points: floppy(),
  3749. upgrades: floppy(),
  3750. color: floppy(),
  3751. skills: floppy(),
  3752. topspeed: floppy(),
  3753. accel: floppy(),
  3754. stats: container(player),
  3755. bodyid: -1,
  3756. };
  3757. // This is the gui itself
  3758. return {
  3759. update: () => update(gui),
  3760. publish: () => publish(gui),
  3761. };
  3762. };
  3763. })();
  3764. // Define the entities messaging function
  3765. function messenger(socket, content) {
  3766. socket.talk('m', content);
  3767. }
  3768. // The returned player definition function
  3769. return (socket, name) => {
  3770. let player = {}, loc = {};
  3771. // Find the desired team (if any) and from that, where you ought to spawn
  3772. player.team = socket.rememberedTeam;
  3773. switch (room.gameMode) {
  3774. case "tdm": {
  3775. // Count how many others there are
  3776. let census = [1, 1, 1, 1, 1, 1], scoreCensus = [1, 1, 1, 1, 1, 1];
  3777. players.forEach(p => {
  3778. census[p.team - 1]++;
  3779. if (p.body != null) { scoreCensus[p.team - 1] += p.body.skill.score; }
  3780. });
  3781. //let i = 0;
  3782. //const length = players.length;
  3783. //for (; i < length; i++) {
  3784. // census[players[i].team - 1]++;
  3785. // if (players[i].body != null) { scoreCensus[players[i].team - 1] += players[i].body.skill.score; }
  3786. //}
  3787. let possiblities = [];
  3788. for (let i=0, m=0; i<6; i++) {
  3789. let v = Math.round(1000000 * (room['bas'+(i+1)].length + 1) / (census[i] + 1) / scoreCensus[i]);
  3790. if (v > m) {
  3791. m = v; possiblities = [i];
  3792. }
  3793. if (v == m) { possiblities.push(i); }
  3794. }
  3795. // Choose from one of the least ones
  3796. if (player.team == null) { player.team = ran.choose(possiblities) + 1; }
  3797. // Make sure you're in a base
  3798. if (room['bas' + player.team].length) do { loc = room.randomType('bas' + player.team); } while (dirtyCheck(loc, 50));
  3799. else do { loc = room.gaussInverse(5); } while (dirtyCheck(loc, 50));
  3800. } break;
  3801. default: do { loc = room.gaussInverse(5); } while (dirtyCheck(loc, 50));
  3802. }
  3803. socket.rememberedTeam = player.team;
  3804. // Create and bind a body for the player host
  3805. let body = new Entity(loc);
  3806. body.protect();
  3807. body.define(Class.basic); // Start as a basic tank, normally Class.basic
  3808. body.name = name; // Define the name
  3809. // Dev hax
  3810. if (socket.key === 'testl' || socket.key === 'testk') {
  3811. body.define({ CAN_BE_ON_LEADERBOARD: false, });
  3812. }
  3813. body.addController(new io_listenToPlayer(body, player)); // Make it listen
  3814. body.sendMessage = content => messenger(socket, content); // Make it speak
  3815. body.invuln = true; // Make it safe
  3816. player.body = body;
  3817. // Decide how to color and team the body
  3818. switch (room.gameMode) {
  3819. case "tdm": {
  3820. body.team = -player.team;
  3821. body.color = [10, 11, 12, 15, 13, 2][player.team - 1];
  3822. } break;
  3823. default: {
  3824. body.color = (c.RANDOM_COLORS) ?
  3825. ran.choose([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) : 12; // red
  3826. }
  3827. }
  3828. // Decide what to do about colors when sending updates and stuff
  3829. player.teamColor = (!c.RANDOM_COLORS && room.gameMode === 'ffa') ? 10 : body.color; // blue
  3830. // Set up the targeting structure
  3831. player.target = {
  3832. x: 0,
  3833. y: 0
  3834. };
  3835. // Set up the command structure
  3836. player.command = {
  3837. up: false,
  3838. down: false,
  3839. left: false,
  3840. right: false,
  3841. lmb: false,
  3842. mmb: false,
  3843. rmb: false,
  3844. autofire: false,
  3845. autospin: false,
  3846. override: false,
  3847. autoguide: false,
  3848. };
  3849. // Set up the recording commands
  3850. player.records = (() => {
  3851. let begin = util.time();
  3852. return () => {
  3853. return [
  3854. player.body.skill.score,
  3855. Math.floor((util.time() - begin) / 1000),
  3856. player.body.killCount.solo,
  3857. player.body.killCount.assists,
  3858. player.body.killCount.bosses,
  3859. player.body.killCount.killers.length,
  3860. ...player.body.killCount.killers
  3861. ];
  3862. };
  3863. })();
  3864. // Set up the player's gui
  3865. player.gui = newgui(player);
  3866. // Save the the player
  3867. player.socket = socket;
  3868. players.push(player);
  3869. // Focus on the new player
  3870. socket.camera.x = body.x; socket.camera.y = body.y; socket.camera.fov = 2000;
  3871. // Mark it as spawned
  3872. socket.status.hasSpawned = true;
  3873. body.sendMessage('You have spawned! Welcome to the game.');
  3874. body.sendMessage('You will be invulnerable until you move or shoot.');
  3875. // Move the client camera
  3876. socket.talk('c', socket.camera.x, socket.camera.y, socket.camera.fov);
  3877. return player;
  3878. };
  3879. })();
  3880. // Make a function that will make a function that will send out world updates
  3881. const eyes = (() => {
  3882. // Define how to prepare data for submission
  3883. function flatten(data) {
  3884. let output = [data.type]; // We will remove the first entry in the persepective method
  3885. if (data.type & 0x01) {
  3886. output.push(
  3887. // 1: facing
  3888. data.facing,
  3889. // 2: layer
  3890. data.layer
  3891. );
  3892. } else {
  3893. output.push(
  3894. // 1: id
  3895. data.id,
  3896. // 2: index
  3897. data.index,
  3898. // 3: x
  3899. data.x,
  3900. // 4: y
  3901. data.y,
  3902. // 5: vx
  3903. data.vx,
  3904. // 6: vy
  3905. data.vy,
  3906. // 7: size
  3907. data.size,
  3908. // 8: facing
  3909. data.facing,
  3910. // 9: vfacing
  3911. data.vfacing,
  3912. // 10: twiggle
  3913. data.twiggle,
  3914. // 11: layer
  3915. data.layer,
  3916. // 12: color
  3917. data.color,
  3918. // 13: health
  3919. Math.ceil(255 * data.health),
  3920. // 14: shield
  3921. Math.round(255 * data.shield),
  3922. // 15: alpha
  3923. Math.round(255 * data.alpha)
  3924. );
  3925. if (data.type & 0x04) {
  3926. output.push(
  3927. // 15: name
  3928. data.name,
  3929. // 16: score
  3930. data.score
  3931. );
  3932. }
  3933. }
  3934. // Add the gun data to the array
  3935. let gundata = [data.guns.length];
  3936. //data.guns.forEach(lastShot => {
  3937. // gundata.push(lastShot.time, lastShot.power);
  3938. //});
  3939. let i = 0;
  3940. const lengthg = data.guns.length;
  3941. for (; i < lengthg; i++) {
  3942. gundata.push(data.guns[i].time, data.guns[i].power);
  3943. }
  3944. output.push(...gundata);
  3945. // For each turret, add their own output
  3946. let turdata = [data.turrets.length];
  3947. let j = 0;
  3948. const lengtht = data.turrets.length;
  3949. for (; j < lengtht; j++) {
  3950. turdata.push(...flatten(data.turrets[j]));
  3951. }
  3952. //data.turrets.forEach(turret => { turdata.push(...flatten(turret)); });
  3953. // Push all that to the array
  3954. output.push(...turdata);
  3955. // Return it
  3956. return output;
  3957. }
  3958. function perspective(e, player, data) {
  3959. if (player.body != null) {
  3960. if (player.body.id === e.master.id) {
  3961. data = data.slice(); // So we don't mess up references to the original
  3962. // Set the proper color if it's on our team
  3963. // And make it force to our mouse if it ought to
  3964. if (player.command.autospin) {
  3965. data[10] = 1;
  3966. }
  3967. }
  3968. }
  3969. return data;
  3970. }
  3971. function check(camera, obj) {
  3972. return Math.abs(obj.x - camera.x) < camera.fov * 0.6 + 1.5 * obj.size + 100 &&
  3973. Math.abs(obj.y - camera.y) < camera.fov * 0.6 * 0.5625 + 1.5 * obj.size + 100;
  3974. }
  3975. // The actual update world function
  3976. return socket => {
  3977. let lastVisibleUpdate = 0;
  3978. let nearby = [];
  3979. let x = -1000;
  3980. let y = -1000;
  3981. let fov = 0;
  3982. let o = {
  3983. add: e => { if (check(socket.camera, e)) nearby.push(e); },
  3984. remove: e => { let i = nearby.indexOf(e); if (i !== -1) util.remove(nearby, i); },
  3985. 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; },
  3986. gazeUpon: () => {
  3987. logs.network.set();
  3988. let player = socket.player,
  3989. camera = socket.camera;
  3990. // If nothing has changed since the last update, wait (approximately) until then to update
  3991. let rightNow = room.lastCycle;
  3992. if (rightNow === camera.lastUpdate) {
  3993. socket.update(5 + room.cycleSpeed - util.time() + rightNow);
  3994. return 1;
  3995. }
  3996. // ...elseeeeee...
  3997. // Update the record.
  3998. camera.lastUpdate = rightNow;
  3999. // Get the socket status
  4000. socket.status.receiving++;
  4001. // Now prepare the data to emit
  4002. let setFov = camera.fov;
  4003. // If we are alive, update the camera
  4004. if (player.body != null) {
  4005. // But I just died...
  4006. if (player.body.isDead()) {
  4007. socket.status.deceased = true;
  4008. // Let the client know it died
  4009. socket.talk('F', ...player.records());
  4010. // Remove the body
  4011. player.body = null;
  4012. }
  4013. // I live!
  4014. else if (player.body.photo) {
  4015. // Update camera position and motion
  4016. camera.x = player.body.photo.x;
  4017. camera.y = player.body.photo.y;
  4018. camera.vx = player.body.photo.vx;
  4019. camera.vy = player.body.photo.vy;
  4020. // Get what we should be able to see
  4021. setFov = player.body.fov;
  4022. // Get our body id
  4023. player.viewId = player.body.id;
  4024. }
  4025. }
  4026. if (player.body == null) { // u dead bro
  4027. setFov = 2000;
  4028. }
  4029. // Smoothly transition view size
  4030. camera.fov += Math.max((setFov - camera.fov) / 30, setFov - camera.fov);
  4031. // Update my stuff
  4032. x = camera.x; y = camera.y; fov = camera.fov;
  4033. // Find what the user can see.
  4034. // Update which entities are nearby
  4035. if (camera.lastUpdate - lastVisibleUpdate > c.visibleListInterval) {
  4036. // Update our timer
  4037. lastVisibleUpdate = camera.lastUpdate;
  4038. // And update the nearby list
  4039. nearby = entities.map(e => { if (check(socket.camera, e)) return e; }).filter(e => { return e; });
  4040. }
  4041. // Look at our list of nearby entities and get their updates
  4042. let visible = nearby.map(function mapthevisiblerealm(e) {
  4043. if (e.photo) {
  4044. if (
  4045. Math.abs(e.x - x) < fov/2 + 1.5*e.size &&
  4046. Math.abs(e.y - y) < fov/2 * (9/16) + 1.5*e.size
  4047. ) {
  4048. // Grab the photo
  4049. if (!e.flattenedPhoto) e.flattenedPhoto = flatten(e.photo);
  4050. return perspective(e, player, e.flattenedPhoto);
  4051. }
  4052. }
  4053. }).filter(e => { return e; });
  4054. // Spread it for upload
  4055. let numberInView = visible.length,
  4056. view = [];
  4057. //visible.forEach(e => { view.push(...e); });
  4058. let i = 0;
  4059. const length = visible.length;
  4060. for (; i < length; i++) {
  4061. view.push(...visible[i]);
  4062. }
  4063. // Update the gui
  4064. player.gui.update();
  4065. // Send it to the player
  4066. socket.talk(
  4067. 'u',
  4068. rightNow,
  4069. camera.x,
  4070. camera.y,
  4071. setFov,
  4072. camera.vx,
  4073. camera.vy,
  4074. ...player.gui.publish(),
  4075. numberInView,
  4076. ...view
  4077. );
  4078. // Queue up some for the front util.log if needed
  4079. if (socket.status.receiving < c.networkFrontlog) {
  4080. socket.update(Math.max(
  4081. 0,
  4082. (1000 / c.networkUpdateFactor) - (camera.lastDowndate - camera.lastUpdate),
  4083. camera.ping / c.networkFrontlog
  4084. ));
  4085. } else {
  4086. socket.update(c.networkFallbackTime);
  4087. }
  4088. logs.network.mark();
  4089. },
  4090. };
  4091. views.push(o);
  4092. return o;
  4093. };
  4094. })();
  4095. // Make a function that will send out minimap
  4096. // and leaderboard updates. We'll also start
  4097. // the mm/lb updating loop here. It runs at 1Hz
  4098. // and also kicks inactive sockets
  4099. const broadcast = (() => {
  4100. // This is the public information we need for broadcasting
  4101. let readmap, readlb;
  4102. // Define fundamental functions
  4103. const getminimap = (() => {
  4104. // Build a map cleaner
  4105. let cleanmapreader = (() => {
  4106. function flattener() {
  4107. let internalmap = [];
  4108. // Define the flattener
  4109. function flatten(data) {
  4110. // In case it's all filtered away, we'll still have something to work with
  4111. if (data == null) data = [];
  4112. let out = [data.length];
  4113. // Push it flat
  4114. //data.forEach(d => out.push(...d));
  4115. let i = 0;
  4116. let length = data.length;
  4117. for (; i < length; i++) {
  4118. out.push(...data[i]);
  4119. }
  4120. return out;
  4121. }
  4122. // Make a test function
  4123. function challenge(value, challenger) {
  4124. return value[1] === challenger[0] &&
  4125. value[2] === challenger[1] &&
  4126. value[3] === challenger[2];
  4127. }
  4128. // Return our functions
  4129. return {
  4130. update: (data) => {
  4131. // Flag all old data as to be removed
  4132. //internalmap.forEach(e => e[0] = -1);
  4133. let y = 0;
  4134. const rlength = internalmap.length;
  4135. for (; y < rlength; y++) {
  4136. internalmap[y][0] = -1;
  4137. }
  4138. // Round all the old data
  4139. data = data.map(d => {
  4140. return [
  4141. Math.round(255 * util.clamp(d[0] / room.width, 0, 1)),
  4142. Math.round(255 * util.clamp(d[1] / room.height, 0, 1)),
  4143. d[2]
  4144. ];
  4145. });
  4146. // Add new data and stabilze existing data, then emove old data
  4147. let j = 0;
  4148. const length = data.length;
  4149. for (; j < length; j++) {
  4150. // Find if it's already there
  4151. let i = internalmap.findIndex(e => { return challenge(e, data[j]); });
  4152. if (i === -1) { // if not add it
  4153. internalmap.push([1, ...data[j]]);
  4154. } else { // if so, flag it as stable
  4155. internalmap[i][0] = 0;
  4156. }
  4157. }
  4158. // Export all new and old data
  4159. let ex = internalmap.filter(e => e[0] !== 0);
  4160. // Remove outdated data
  4161. internalmap = internalmap.filter(e => e[0] !== -1);
  4162. // Flatten the exports
  4163. let f = flatten(ex);
  4164. return f;
  4165. },
  4166. exportall: () => {
  4167. // Returns a flattened version of the map with blanket add requests
  4168. return flatten(internalmap.map(e => { return [1, e[1], e[2], e[3]]; }));
  4169. },
  4170. };
  4171. }
  4172. // Define the function
  4173. return (room.gameMode === 'ffa') ?
  4174. // ffa function builder
  4175. (() => {
  4176. // Make flatteners
  4177. let publicmap = flattener();
  4178. // Return the function
  4179. return () => {
  4180. // Updates
  4181. let clean = publicmap.update(minimap.map(function(entry) {
  4182. return [entry[1], entry[2], (entry[4] === 'miniboss') ? entry[3] : 17];
  4183. }));
  4184. let full = publicmap.exportall();
  4185. // Reader
  4186. return (team, everything = false) => { return (everything) ? full : clean; };
  4187. };
  4188. })() :
  4189. // tdm function builder
  4190. (() => {
  4191. // Make flatteners
  4192. let team1map = flattener();
  4193. let team2map = flattener();
  4194. let team3map = flattener();
  4195. let team4map = flattener();
  4196. let team5map = flattener();
  4197. let team6map = flattener();
  4198. // Return the function
  4199. return () => {
  4200. let clean = [
  4201. team1map.update(minimap.map(function(entry) {
  4202. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -1)) ? entry[3] : 17];
  4203. })),
  4204. team2map.update(minimap.map(function(entry) {
  4205. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -2)) ? entry[3] : 17];
  4206. })),
  4207. team3map.update(minimap.map(function(entry) {
  4208. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -3)) ? entry[3] : 17];
  4209. })),
  4210. team4map.update(minimap.map(function(entry) {
  4211. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -4)) ? entry[3] : 17];
  4212. })),
  4213. team5map.update(minimap.map(function(entry) {
  4214. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -4)) ? entry[3] : 17];
  4215. })),
  4216. team6map.update(minimap.map(function(entry) {
  4217. return [entry[1], entry[2], (entry[4] === 'miniboss' || (entry[4] === 'tank' && entry[5] === -4)) ? entry[3] : 17];
  4218. })),
  4219. ];
  4220. let full = [
  4221. team1map.exportall(),
  4222. team2map.exportall(),
  4223. team3map.exportall(),
  4224. team4map.exportall(),
  4225. team5map.exportall(),
  4226. team6map.exportall()
  4227.  
  4228. ];
  4229. // The reader
  4230. return (team, everything = false) => { return (everything) ? full[team-1] : clean[team-1]; };
  4231. };
  4232. })();
  4233. })();
  4234. // Return the builder function. This itself returns
  4235. // a reader for the map (will change based on team)
  4236. return () => {
  4237. // Update the minimap
  4238. let i = 0;
  4239. const length = entities.length;
  4240. for (; i < length; i++) {
  4241. let my = entities[i];
  4242. if (my.settings.drawShape && ran.dice(my.stealth * c.STEALTH)) {
  4243. let i = minimap.findIndex((entry) => {
  4244. return entry[0] === my.id;
  4245. });
  4246. if (i != -1) { // update position
  4247. minimap[i] = [my.id, my.x, my.y, my.color, my.type, my.team];
  4248. } else { // add position
  4249. minimap.push([my.id, my.x, my.y, my.color, my.type, my.team]);
  4250. }
  4251. }
  4252. }
  4253. // Clean the map and return the reader
  4254. return cleanmapreader();
  4255. };
  4256. })();
  4257. const getleaderboard = (() => {
  4258. let lb = { full: [], updates: [], };
  4259. // We'll reuse these lists over and over again
  4260. let list = new goog.structs.PriorityQueue();
  4261. // This puts things in the data structure
  4262. function listify(instance) {
  4263. if (
  4264. instance.settings.leaderboardable &&
  4265. instance.settings.drawShape &&
  4266. (
  4267. instance.type === 'tank' ||
  4268. instance.killCount.solo ||
  4269. instance.killCount.assists
  4270. )
  4271. ) {
  4272. list.enqueue(1/(instance.skill.score + 1), instance);
  4273. }
  4274. }
  4275. // Build a function to prepare for export
  4276. let flatten = (() => {
  4277. let leaderboard = {};
  4278. // Define our index manager
  4279. let indices = (() => {
  4280. let data = [], removed = [];
  4281. // Provide the index manager methods
  4282. return {
  4283. flag: () => {
  4284. //data.forEach(index => {
  4285. // index.status = -1;
  4286. //});
  4287. let i = 0;
  4288. const length = data.length;
  4289. for (; i < length; i++) {
  4290. data[i].status = -1;
  4291. }
  4292. if (data == null) { data = []; }
  4293. },
  4294. cull: () => {
  4295. removed = [];
  4296. data = data.filter(index => {
  4297. let doit = index.status === -1;
  4298. if (doit) removed.push(index.id);
  4299. return !doit;
  4300. });
  4301. return removed;
  4302. },
  4303. add: id => {
  4304. data.push({ id: id, status: 1, });
  4305. },
  4306. stabilize: id => {
  4307. data[data.findIndex(index => {
  4308. return index.id === id;
  4309. })].status = 0;
  4310. },
  4311. };
  4312. })();
  4313. // This processes it
  4314. let process = (() => {
  4315. // A helpful thing
  4316. function barcolor(entry) {
  4317. switch (entry.team) {
  4318. case -100: return entry.color;
  4319. case -1: return 10;
  4320. case -2: return 11;
  4321. case -3: return 12;
  4322. case -4: return 15;
  4323. case -5: return 13;
  4324. default: {
  4325. if (room.gameMode === 'tdm') return entry.color;
  4326. return 11;
  4327. }
  4328. }
  4329. }
  4330. // A shared (and protected) thing
  4331. function getfull(entry) {
  4332. return [
  4333. -entry.id,
  4334. Math.round(entry.skill.score),
  4335. entry.index,
  4336. entry.name,
  4337. entry.color,
  4338. barcolor(entry),
  4339. ];
  4340. }
  4341. return {
  4342. normal: entry => {
  4343. // Check if the entry is already there
  4344. let id = entry.id,
  4345. score = Math.round(entry.skill.score);
  4346. let lb = leaderboard['_' + id];
  4347. if (lb != null) {
  4348. // Unflag it for removal
  4349. indices.stabilize(id);
  4350. // Figure out if we need to update anything
  4351. if (lb.score !== score || lb.index !== entry.index) {
  4352. // If so, update our record first
  4353. lb.score = score;
  4354. lb.index = entry.index;
  4355. // Return it for broadcasting
  4356. return [
  4357. id,
  4358. score,
  4359. entry.index,
  4360. ];
  4361. }
  4362. } else {
  4363. // Record it
  4364. indices.add(id);
  4365. leaderboard['_' + id] = {
  4366. score: score,
  4367. name: entry.name,
  4368. index: entry.index,
  4369. color: entry.color,
  4370. bar: barcolor(entry),
  4371. };
  4372. // Return it for broadcasting
  4373. return getfull(entry);
  4374. }
  4375. },
  4376. full: entry => { return getfull(entry); },
  4377. };
  4378. })();
  4379. // The flattening functions
  4380. return data => {
  4381. // Start
  4382. indices.flag();
  4383. // Flatten the orders
  4384. let orders = data.map(process.normal).filter(e => { return e; }),
  4385. refresh = data.map(process.full).filter(e => { return e; }),
  4386. flatorders = [],
  4387. flatrefresh = [];
  4388. let lengtho = orders.length;
  4389. let lengthr = refresh.length;
  4390. let o = 0;
  4391. let r = 0;
  4392. for (; o < lengtho; o++) {
  4393. flatorders.push(...orders[o]);
  4394. }
  4395. for (; r < lengthr; r++) {
  4396. flatrefresh.push(...refresh[r]);
  4397. }
  4398. //rders.forEach(e => flatorders.push(...e));
  4399. //refresh.forEach(e => flatrefresh.push(...e));
  4400. // Find the stuff to remove
  4401. let removed = indices.cull();
  4402. // Make sure we sync the leaderboard
  4403. let i = 0;
  4404. const length = removed.length;
  4405. //for (; i < length; i++) {
  4406. // delete leaderboard['_' + removed[i]];
  4407. //}
  4408. removed.forEach(id => { delete leaderboard['_' + id]; });
  4409. return {
  4410. updates: [removed.length, ...removed, orders.length, ...flatorders],
  4411. full: [-1, refresh.length, ...flatrefresh], // The -1 tells the client it'll be a full refresh
  4412. };
  4413. };
  4414. })();
  4415. // The update function (returns a reader)
  4416. return () => {
  4417. list.clear();
  4418. // Sort everything
  4419. let i = 0;
  4420. const length = entities.length;
  4421. for (; i < length; i++) {
  4422. listify(entities[i]);
  4423. }
  4424. //entities.forEach(listify);
  4425. // Get the top ten
  4426. let topTen = [];
  4427. for (let i=0; i<10; i++) {
  4428. // Only if there's anything in the list of course
  4429. if (list.getCount()) {
  4430. topTen.push(list.dequeue());
  4431. } else {
  4432. break;
  4433. }
  4434. }
  4435. topTen = topTen.filter(e => { return e; });
  4436. room.topPlayerID = (topTen.length) ? topTen[0].id : -1;
  4437. // Remove empty values and process it
  4438. lb = flatten(topTen);
  4439. // Return the reader
  4440. return (full = false) => {
  4441. return full ? lb.full : lb.updates;
  4442. };
  4443. };
  4444. })();
  4445. // Define a 1 Hz update loop
  4446. function slowloop() {
  4447. // Build the minimap
  4448. logs.minimap.set();
  4449. readmap = getminimap();
  4450. // Build the leaderboard
  4451. readlb = getleaderboard();
  4452. logs.minimap.mark();
  4453. // Check sockets
  4454. let time = util.time();
  4455. for (let socket of clients) {
  4456. if (socket.timeout.check(time)) socket.kick('Kicked for inactivity.');
  4457. if (time - socket.statuslastHeartbeat > c.maxHeartbeatInterval) socket.kick('Lost heartbeat.');
  4458. }
  4459. }
  4460. // Start it
  4461. slowloop();
  4462. setInterval(slowloop, 1000);
  4463. // Give the broadcast method
  4464. return socket => {
  4465. // Make sure it's spawned first
  4466. if (socket.status.hasSpawned) {
  4467. let m = [0], lb = [0, 0];
  4468. m = readmap(socket.player.team, socket.status.needsFullMap);
  4469. socket.status.needsFullMap = false;
  4470. lb = readlb(socket.status.needsFullLeaderboard);
  4471. socket.status.needsFullLeaderboard = false;
  4472. // Don't broadcast if you don't need to
  4473. if (m !== [0] || lb !== [0, 0]) { socket.talk('b', ...m, ...lb); }
  4474. }
  4475. };
  4476. })();
  4477. // Build the returned function
  4478. // This function initalizes the socket upon connection
  4479. return (socket, req) => {
  4480. // Get information about the new connection and verify it
  4481. if (c.servesStatic || req.connection.remoteAddress === '::ffff:127.0.0.1' || req.connection.remoteAddress === '::1') {
  4482. socket.ip = req.headers['x-forwarded-for'];
  4483. // Make sure we're not banned...
  4484. if (bannedIPs.findIndex(ip => { return ip === socket.ip; }) !== -1) {
  4485. socket.terminate();
  4486. return 1;
  4487. }
  4488. // Make sure we're not already connected...
  4489. if (!c.servesStatic) {
  4490. let n = connectedIPs.findIndex(w => { return w.ip === socket.ip; });
  4491. if (n !== -1) {
  4492. // Don't allow more than 2
  4493. if (connectedIPs[n].number > 1) {
  4494. util.warn('Too many connections from the same IP. [' + socket.ip + ']');
  4495. socket.terminate();
  4496. return 1;
  4497. } else connectedIPs[n].number++;
  4498. } else connectedIPs.push({ ip: socket.ip, number: 1, });
  4499. }
  4500. } else {
  4501. // Don't let banned IPs connect.
  4502. util.warn(req.connection.remoteAddress);
  4503. util.warn(req.headers['x-forwarded-for']);
  4504. socket.terminate();
  4505. util.warn('Inappropiate connection request: header spoofing. Socket terminated.');
  4506. return 1;
  4507. }
  4508. util.log(socket.ip + ' is trying to connect...');
  4509. // Set it up
  4510. socket.binaryType = 'arraybuffer';
  4511. socket.key = '';
  4512. socket.player = { camera: {}, };
  4513. socket.timeout = (() => {
  4514. let mem = 0;
  4515. let timer = 0;
  4516. return {
  4517. set: val => { if (mem !== val) { mem = val; timer = util.time(); } },
  4518. check: time => { return timer && time - timer > c.maxHeartbeatInterval; },
  4519. };
  4520. })();
  4521. // Set up the status container
  4522. socket.status = {
  4523. verified: false,
  4524. receiving: 0,
  4525. deceased: true,
  4526. requests: 0,
  4527. hasSpawned: false,
  4528. needsFullMap: true,
  4529. needsFullLeaderboard: true,
  4530. lastHeartbeat: util.time(),
  4531. };
  4532. // Set up loops
  4533. socket.loops = (() => {
  4534. let nextUpdateCall = null; // has to be started manually
  4535. let trafficMonitoring = setInterval(() => traffic(socket), 1000);
  4536. let broadcastingGuiStuff = setInterval(() => broadcast(socket), 1500);
  4537. // Return the loop methods
  4538. return {
  4539. setUpdate: timeout => {
  4540. nextUpdateCall = timeout;
  4541. },
  4542. cancelUpdate: () => {
  4543. clearTimeout(nextUpdateCall);
  4544. },
  4545. terminate: () => {
  4546. clearTimeout(nextUpdateCall);
  4547. clearTimeout(trafficMonitoring);
  4548. clearTimeout(broadcastingGuiStuff);
  4549. },
  4550. };
  4551. })();
  4552. // Set up the camera
  4553. socket.camera = {
  4554. x: undefined,
  4555. y: undefined,
  4556. vx: 0,
  4557. vy: 0,
  4558. lastUpdate: util.time(),
  4559. lastDowndate: undefined,
  4560. fov: 2000,
  4561. };
  4562. // Set up the viewer
  4563. socket.makeView = () => { socket.view = eyes(socket); };
  4564. socket.makeView();
  4565. // Put the fundamental functions in the socket
  4566. socket.ban = () => ban(socket);
  4567. socket.kick = reason => kick(socket, reason);
  4568. socket.talk = (...message) => {
  4569. if (socket.readyState === socket.OPEN) {
  4570. socket.send(protocol.encode(message), { binary: true, });
  4571. }
  4572. };
  4573. socket.lastWords = (...message) => {
  4574. if (socket.readyState === socket.OPEN) {
  4575. socket.send(protocol.encode(message), { binary: true, }, () => timer.setTimeout(() => socket.terminate(), '', '1000m'));
  4576. }
  4577. };
  4578. socket.on('message', message => incoming(message, socket));
  4579. socket.on('close', () => { socket.loops.terminate(); close(socket); });
  4580. socket.on('error', e => { util.log('[ERROR]:'); util.error(e); });
  4581. // Put the player functions in the socket
  4582. socket.spawn = name => { return spawn(socket, name); };
  4583. // And make an update
  4584. socket.update = time => {
  4585. socket.loops.cancelUpdate();
  4586. socket.loops.setUpdate(setTimeout(() => { socket.view.gazeUpon(); }, time.toString()));
  4587. };
  4588. // Log it
  4589. clients.push(socket);
  4590. util.log('[INFO] New socket opened with ', socket.ip);
  4591. };
  4592. })(),
  4593. };
  4594. })();
  4595.  
  4596. /**** GAME SETUP ****/
  4597. // Define how the game lives
  4598. // The most important loop. Fast looping.
  4599.  
  4600. var gameloop = (() => {
  4601. // Collision stuff
  4602. let collide = (() => {
  4603. function simplecollide(my, n) {
  4604. let diff = (1 + util.getDistance(my, n) / 2) * roomSpeed;
  4605. let a = (my.intangibility) ? 1 : my.pushability,
  4606. b = (n.intangibility) ? 1 : n.pushability,
  4607. c = 0.05 * (my.x - n.x) / diff,
  4608. d = 0.05 * (my.y - n.y) / diff;
  4609. my.accel.x += a / (b + 0.3) * c;
  4610. my.accel.y += a / (b + 0.3) * d;
  4611. n.accel.x -= b / (a + 0.3) * c;
  4612. n.accel.y -= b / (a + 0.3) * d;
  4613. }
  4614. function firmcollide(my, n, buffer = 0) {
  4615. let item1 = { x: my.x + my.m_x, y: my.y + my.m_y, };
  4616. let item2 = { x: n.x + n.m_x, y: n.y + n.m_y, };
  4617. let dist = util.getDistance(item1, item2);
  4618. let s1 = Math.max(my.velocity.length, my.topSpeed);
  4619. let s2 = Math.max(n.velocity.length, n.topSpeed);
  4620. let strike1, strike2;
  4621. if (buffer > 0 && dist <= my.realSize + n.realSize + buffer) {
  4622. let repel = (my.acceleration + n.acceleration) * (my.realSize + n.realSize + buffer - dist) / buffer / roomSpeed;
  4623. my.accel.x += (repel * (item1.x - item2.x) / dist) * 1.525;
  4624. my.accel.y += (repel * (item1.y - item2.y) / dist) * 1.525;
  4625. n.accel.x -= (repel * (item1.x - item2.x) / dist) * 1.525;
  4626. n.accel.y -= (repel * (item1.y - item2.y) / dist) * 1.525;
  4627. }
  4628. while (dist <= my.realSize + n.realSize && !(strike1 && strike2)) {
  4629. strike1 = false; strike2 = false;
  4630. if (my.velocity.length <= s1) {
  4631. my.velocity.x -= 0.05 * (item2.x - item1.x) / dist / roomSpeed;
  4632. my.velocity.y -= 0.05 * (item2.y - item1.y) / dist / roomSpeed;
  4633. } else { strike1 = true; }
  4634. if (n.velocity.length <= s2) {
  4635. n.velocity.x += 0.05 * (item2.x - item1.x) / dist / roomSpeed;
  4636. n.velocity.y += 0.05 * (item2.y - item1.y) / dist / roomSpeed;
  4637. } else { strike2 = true; }
  4638. item1 = { x: my.x + my.m_x, y: my.y + my.m_y, };
  4639. item2 = { x: n.x + n.m_x, y: n.y + n.m_y, };
  4640. dist = util.getDistance(item1, item2);
  4641. }
  4642. }
  4643. function reflectcollide(wall, bounce) {
  4644. let delt = new Vector(wall.x - bounce.x, wall.y - bounce.y);
  4645. let dist = delt.length;
  4646. let diff = wall.size + bounce.size - dist;
  4647. if (diff > 0) {
  4648. bounce.accel.x -= (diff * delt.x / dist) * 1.525;
  4649. bounce.accel.y -= (diff * delt.y / dist) * 1.525;
  4650. return 1;
  4651. }
  4652. return 0;
  4653. }
  4654. function advancedcollide(my, n, doDamage, doInelastic, nIsFirmCollide = false) {
  4655. // Prepare to check
  4656. let tock = Math.min(my.stepRemaining, n.stepRemaining),
  4657. combinedRadius = n.size + my.size,
  4658. motion = {
  4659. _me: new Vector(my.m_x, my.m_y),
  4660. _n: new Vector(n.m_x, n.m_y),
  4661. },
  4662. delt = new Vector(
  4663. tock * (motion._me.x - motion._n.x),
  4664. tock * (motion._me.y - motion._n.y)
  4665. ),
  4666. diff = new Vector(my.x - n.x, my.y - n.y),
  4667. dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length),
  4668. component = Math.max(0, dir.x * delt.x + dir.y * delt.y);
  4669.  
  4670.  
  4671. if (n.customshapes !== undefined) {
  4672. if (n.customshapes.length > 0) {
  4673. let centerX = n.x;
  4674. let centerY = n.y;
  4675. let points = [];
  4676. let edgevaluesx = [];
  4677. let edgevaluesy = [];
  4678. for (let point in n.customshapes) {
  4679. points.push(point);
  4680. }
  4681. for (let i = 0; i < points.length; i++) {
  4682. let xDifference;
  4683. let yDifference;
  4684. if (i + 1 <= points.length) {
  4685. xDifference = Math.abs(points[i][0] - points[i + 1][0]);
  4686. yDifference = Math.abs(points[i][1] - points[i + 1][1]);
  4687. let changeX = 1;
  4688. let changeY = 1;
  4689. if (points[i + 1][0] < points[i][0]) {
  4690. changeX = -1;
  4691. }
  4692. if (points[i + 1][1] < points[i][1]) {
  4693. changeY = -1;
  4694. }
  4695. for (let i = 0; i < xDifference; i++) {
  4696. edgevaluesx.push(points[i][0] + (i * changeX));
  4697. }
  4698. for (let j = 0; i < yDifference; j++) {
  4699. edgevaluesy.push(points[j][0] + (j * changeY));
  4700. }
  4701. }
  4702. }
  4703. }
  4704. }
  4705.  
  4706. if (component >= diff.length - combinedRadius) { // A simple check
  4707. // A more complex check
  4708. let goahead = false,
  4709. tmin = 1 - tock,
  4710. tmax = 1,
  4711. A = Math.pow(delt.x, 2) + Math.pow(delt.y, 2),
  4712. B = 2*delt.x*diff.x + 2*delt.y*diff.y,
  4713. C = Math.pow(diff.x, 2) + Math.pow(diff.y, 2) - Math.pow(combinedRadius, 2),
  4714. det = B * B - (4 * A * C),
  4715. t;
  4716.  
  4717. if (!A || det < 0 || C < 0) { // This shall catch mathematical errors
  4718. t = 0;
  4719. if (C < 0) { // We have already hit without moving
  4720. goahead = true;
  4721. }
  4722. } else {
  4723. let t1 = (-B - Math.sqrt(det)) / (2*A),
  4724. t2 = (-B + Math.sqrt(det)) / (2*A);
  4725. if (t1 < tmin || t1 > tmax) { // 1 is out of range
  4726. if (t2 < tmin || t2 > tmax) { // 2 is out of range;
  4727. t = false;
  4728. } else { // 1 is out of range but 2 isn't
  4729. t = t2; goahead = true;
  4730. }
  4731. } else { // 1 is in range
  4732. if (t2 >= tmin && t2 <= tmax) { // They're both in range!
  4733. t = Math.min(t1, t2); goahead = true; // That means it passed in and then out again. Let's use when it's going in
  4734. } else { // Only 1 is in range
  4735. t = t1; goahead = true;
  4736. }
  4737. }
  4738. }
  4739. if (goahead) {
  4740. // Add to record
  4741. my.collisionArray.push(n);
  4742. n.collisionArray.push(my);
  4743. if (t) { // Only if we still need to find the collision
  4744. // Step to where the collision occured
  4745. my.x += motion._me.x * t;
  4746. my.y += motion._me.y * t;
  4747. n.x += motion._n.x * t;
  4748. n.y += motion._n.y * t;
  4749.  
  4750. my.stepRemaining -= t;
  4751. n.stepRemaining -= t;
  4752.  
  4753. // Update things
  4754. diff = new Vector(my.x - n.x, my.y - n.y);
  4755. dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length);
  4756. component = Math.max(0, dir.x * delt.x + dir.y * delt.y);
  4757. }
  4758. let componentNorm = component / delt.length;
  4759. // Prepare some things
  4760. let reductionFactor = 1,
  4761. deathFactor = {
  4762. _me: 1,
  4763. _n: 1,
  4764. },
  4765. accelerationFactor = (delt.length) ? (
  4766. (combinedRadius / 4) / (Math.floor(combinedRadius / delt.length) + 1)
  4767. ) : (
  4768. 0.001
  4769. ),
  4770. depth = {
  4771. _me: util.clamp((combinedRadius - diff.length) / (2 * my.size), 0, 1), //1: I am totally within it
  4772. _n: util.clamp((combinedRadius - diff.length) / (2 * n.size), 0, 1), //1: It is totally within me
  4773. },
  4774. combinedDepth = {
  4775. up: depth._me * depth._n,
  4776. down: (1-depth._me) * (1-depth._n),
  4777. },
  4778. pen = {
  4779. _me: {
  4780. sqr: Math.pow(my.penetration, 2),
  4781. sqrt: Math.sqrt(my.penetration),
  4782. },
  4783. _n: {
  4784. sqr: Math.pow(n.penetration, 2),
  4785. sqrt: Math.sqrt(n.penetration),
  4786. },
  4787. },
  4788. savedHealthRatio = {
  4789. _me: my.health.ratio,
  4790. _n: n.health.ratio,
  4791. };
  4792. if (doDamage) {
  4793. let speedFactor = { // Avoid NaNs and infinities
  4794. _me: (my.maxSpeed) ? ( Math.pow(motion._me.length/my.maxSpeed, 0.25) ) : ( 1 ),
  4795. _n: (n.maxSpeed) ? ( Math.pow(motion._n.length/n.maxSpeed, 0.25) ) : ( 1 ),
  4796. };
  4797.  
  4798. let bail = false;
  4799. if (my.shape === n.shape && my.settings.isNecromancer && n.type === 'food') {
  4800. //bail = my.necro(n);
  4801. } else if (my.shape === n.shape && n.settings.isNecromancer && my.type === 'food') {
  4802. //bail = n.necro(my);
  4803. }
  4804. if (!bail) {
  4805. // Calculate base damage
  4806. let resistDiff = my.health.resist - n.health.resist,
  4807. damage = {
  4808. _me:
  4809. c.DAMAGE_CONSTANT *
  4810. my.damage *
  4811. (1 + resistDiff) *
  4812. (1 + n.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) *
  4813. ((my.settings.buffVsFood && n.settings.damageType === 1) ? 3 : 1 ) *
  4814. my.damageMultiplier() *
  4815. Math.min(2, Math.max(speedFactor._me, 1) * speedFactor._me),
  4816. _n:
  4817. c.DAMAGE_CONSTANT *
  4818. n.damage *
  4819. (1 - resistDiff) *
  4820. (1 + my.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) *
  4821. ((n.settings.buffVsFood && my.settings.damageType === 1) ? 3 : 1) *
  4822. n.damageMultiplier() *
  4823. Math.min(2, Math.max(speedFactor._n, 1) * speedFactor._n),
  4824. };
  4825. // Advanced damage calculations
  4826. if (my.settings.ratioEffects) {
  4827. damage._me *= Math.min(1, Math.pow(Math.max(my.health.ratio, my.shield.ratio), 1 / my.penetration));
  4828. }
  4829. if (n.settings.ratioEffects) {
  4830. damage._n *= Math.min(1, Math.pow(Math.max(n.health.ratio, n.shield.ratio), 1 / n.penetration));
  4831. }
  4832. if (my.settings.damageEffects) {
  4833. damage._me *=
  4834. accelerationFactor *
  4835. (1 + (componentNorm - 1) * (1 - depth._n) / my.penetration) *
  4836. (1 + pen._n.sqrt * depth._n - depth._n) / pen._n.sqrt;
  4837. }
  4838. if (n.settings.damageEffects) {
  4839. damage._n *=
  4840. accelerationFactor *
  4841. (1 + (componentNorm - 1) * (1 - depth._me) / n.penetration) *
  4842. (1 + pen._me.sqrt * depth._me - depth._me) / pen._me.sqrt;
  4843. }
  4844. // Find out if you'll die in this cycle, and if so how much damage you are able to do to the other target
  4845. let damageToApply = {
  4846. _me: damage._me,
  4847. _n: damage._n,
  4848. };
  4849. if (n.shield.max) {
  4850. damageToApply._me -= n.shield.getDamage(damageToApply._me);
  4851. }
  4852. if (my.shield.max) {
  4853. damageToApply._n -= my.shield.getDamage(damageToApply._n);
  4854. }
  4855. let stuff = my.health.getDamage(damageToApply._n, false);
  4856. deathFactor._me = (stuff > my.health.amount) ? my.health.amount / stuff : 1;
  4857. stuff = n.health.getDamage(damageToApply._me, false);
  4858. deathFactor._n = (stuff > n.health.amount) ? n.health.amount / stuff : 1;
  4859.  
  4860. reductionFactor = Math.min(deathFactor._me, deathFactor._n);
  4861.  
  4862. // Now apply it
  4863. my.damageRecieved += damage._n * deathFactor._n;
  4864. n.damageRecieved += damage._me * deathFactor._me;
  4865. }
  4866. }
  4867. /************* POISON ***********/
  4868. if (n.poison) {
  4869. my.poisoned = true
  4870. my.poisonedLevel = n.poisionToApply
  4871. my.poisonTime = 20
  4872. my.poisonedBy = n.master
  4873. }
  4874. if (my.poison) {
  4875. n.poisoned = true
  4876. n.poisonedLevel = my.poisionToApply
  4877. n.poisonTime = 20
  4878. n.poisonedBy = my.master
  4879. }
  4880. if (n.breakarmor) {
  4881. my.armorbroken = true
  4882. }
  4883. if (my.breakarmor) {
  4884. n.armorbroken = true
  4885. }
  4886. /************* DO MOTION ***********/
  4887. if (nIsFirmCollide < 0) {
  4888. nIsFirmCollide *= -0.5;
  4889. my.accel.x -= nIsFirmCollide * component * dir.x;
  4890. my.accel.y -= nIsFirmCollide * component * dir.y;
  4891. n.accel.x += nIsFirmCollide * component * dir.x;
  4892. n.accel.y += nIsFirmCollide * component * dir.y;
  4893. } else if (nIsFirmCollide > 0) {
  4894. n.accel.x += nIsFirmCollide * (component * dir.x + combinedDepth.up);
  4895. n.accel.y += nIsFirmCollide * (component * dir.y + combinedDepth.up);
  4896. } else {
  4897. // Calculate the impulse of the collision
  4898. let elasticity = 2 - 4 * Math.atan(my.penetration * n.penetration) / Math.PI;
  4899. if (doInelastic && my.settings.motionEffects && n.settings.motionEffects) {
  4900. elasticity *= savedHealthRatio._me / pen._me.sqrt + savedHealthRatio._n / pen._n.sqrt;
  4901. } else {
  4902. elasticity *= 2;
  4903. }
  4904. let spring = 2 * Math.sqrt(savedHealthRatio._me * savedHealthRatio._n) / roomSpeed,
  4905. elasticImpulse =
  4906. Math.pow(combinedDepth.down, 2) *
  4907. elasticity * component *
  4908. my.mass * n.mass / (my.mass + n.mass),
  4909. springImpulse =
  4910. c.KNOCKBACK_CONSTANT * spring * combinedDepth.up,
  4911. impulse = -(elasticImpulse + springImpulse) * (1 - my.intangibility) * (1 - n.intangibility),
  4912. force = {
  4913. x: impulse * dir.x,
  4914. y: impulse * dir.y,
  4915. },
  4916. modifiers = {
  4917. _me: c.KNOCKBACK_CONSTANT * my.pushability / my.mass * deathFactor._n,
  4918. _n: c.KNOCKBACK_CONSTANT * n.pushability / n.mass * deathFactor._me,
  4919. };
  4920. // Apply impulse as force
  4921. my.accel.x += modifiers._me * force.x * 1.6;
  4922. my.accel.y += modifiers._me * force.y * 1.6;
  4923. n.accel.x -= modifiers._n * force.x * 1.6;
  4924. n.accel.y -= modifiers._n * force.y * 1.6;
  4925. }
  4926. }
  4927. }
  4928. }
  4929. // The actual collision resolution function
  4930. return collision => {
  4931. // Pull the two objects from the collision grid
  4932. let instance = collision[0],
  4933. other = collision[1];
  4934. // Check for ghosts...
  4935. if (other.isGhost) {
  4936. util.error('GHOST FOUND');
  4937. util.error(other.label);
  4938. util.error('x: ' + other.x + ' y: ' + other.y);
  4939. util.error(other.collisionArray);
  4940. util.error('health: ' + other.health.amount);
  4941. util.warn('Ghost removed.');
  4942. if (grid.checkIfInHSHG(other)) {
  4943. util.warn('Ghost removed.'); grid.removeObject(other);
  4944. }
  4945. return 0;
  4946. }
  4947. if (instance.isGhost) {
  4948. util.error('GHOST FOUND');
  4949. util.error(instance.label);
  4950. util.error('x: ' + instance.x + ' y: ' + instance.y);
  4951. util.error(instance.collisionArray);
  4952. util.error('health: ' + instance.health.amount);
  4953. if (grid.checkIfInHSHG(instance)) {
  4954. util.warn('Ghost removed.'); grid.removeObject(instance);
  4955. }
  4956. return 0;
  4957. }
  4958. let delt = new Vector(instance.x - other.x, instance.y - other.y);
  4959. let dist = delt.length;
  4960. let diff = instance.size + other.size - dist;
  4961.  
  4962. if (diff > 0) {
  4963. if (!instance.activation.check() && !other.activation.check()) { util.warn('Tried to collide with an inactive instance.'); return 0; }
  4964. // Handle walls
  4965. if (instance.type === 'wall' || other.type === 'wall') {
  4966. let a = (instance.type === 'bullet' || other.type === 'bullet') ?
  4967. 1 + 10 / (Math.max(instance.velocity.length, other.velocity.length) + 10) :
  4968. 1;
  4969. if (instance.type === 'wall') advancedcollide(instance, other, false, false, a);
  4970. else advancedcollide(other, instance, false, false, a);
  4971. } else
  4972. // If they can firm collide, do that
  4973. if ((instance.type === 'crasher' && other.type === 'food') || (other.type === 'crasher' && instance.type === 'food')) {
  4974. firmcollide(instance, other);
  4975. } else
  4976. // Otherwise, collide normally if they're from different teams
  4977. if (instance.team !== other.team) {
  4978. advancedcollide(instance, other, true, true);
  4979. } else
  4980. // Ignore them if either has asked to be
  4981. if (instance.settings.hitsOwnType == 'never' || other.settings.hitsOwnType == 'never') {
  4982. // Do jack
  4983. } else
  4984. // Standard collision resolution
  4985. if (instance.settings.hitsOwnType === other.settings.hitsOwnType) {
  4986. switch (instance.settings.hitsOwnType) {
  4987. case 'push': advancedcollide(instance, other, false, false); break;
  4988. case 'hard': firmcollide(instance, other); break;
  4989. case 'hardWithBuffer': firmcollide(instance, other, 30); break;
  4990. case 'repel': simplecollide(instance, other); break;
  4991. }
  4992. }
  4993. }
  4994. };
  4995. })();
  4996. // Living stuff
  4997. function entitiesactivationloop(my) {
  4998. // Update collisions.
  4999. my.collisionArray = [];
  5000. // Activation
  5001. my.activation.update();
  5002. my.updateAABB(my.activation.check());
  5003. }
  5004. function entitiesliveloop (my) {
  5005. // Consider death.
  5006. if (my.contemplationOfMortality()) my.destroy();
  5007. else {
  5008. if (my.bond == null) {
  5009. // Resolve the physical behavior from the last collision cycle.
  5010. logs.physics.set();
  5011. my.physics();
  5012. logs.physics.mark();
  5013. }
  5014. if (my.activation.check()) {
  5015. logs.entities.tally();
  5016. // Think about my actions.
  5017. logs.life.set();
  5018. my.life();
  5019. logs.life.mark();
  5020. // Apply friction.
  5021. my.friction();
  5022. my.confinementToTheseEarthlyShackles();
  5023. logs.selfie.set();
  5024. my.takeSelfie();
  5025. logs.selfie.mark();
  5026. }
  5027. }
  5028. // Update collisions.
  5029. my.collisionArray = [];
  5030. }
  5031. let time;
  5032. // Return the loop function
  5033. return () => {
  5034. //let curTime = now();
  5035. //timestep = 0.007875 * (curTime - lastTime);
  5036. //if (timestep <= 0 || timestep > 7.875) {
  5037. // timestep = 0.007875;
  5038. //}
  5039. logs.loops.tally();
  5040. logs.master.set();
  5041. logs.activation.set();
  5042. for (var e of entities) {
  5043. entitiesactivationloop(e);
  5044. }
  5045. //entities.forEach(e => entitiesactivationloop(e));
  5046. logs.activation.mark();
  5047. // Do collisions
  5048. logs.collide.set();
  5049.  
  5050. if (entities.length > 1) {
  5051. // Load the grid
  5052. grid.update();
  5053. for (let collision of grid.queryForCollisionPairs()) {
  5054. collide(collision);
  5055. }
  5056. //grid.queryForCollisionPairs().forEach(collision => collide(collision));
  5057. }
  5058. logs.collide.mark();
  5059. // Do entities life
  5060. logs.entities.set();
  5061. for (var e of entities) {
  5062. entitiesliveloop(e);
  5063. }
  5064. logs.entities.mark();
  5065. logs.master.mark();
  5066. // Remove dead entities
  5067. purgeEntities();
  5068. room.lastCycle = util.time();
  5069. //lastTime = curTime;
  5070. };
  5071. //let expected = 1000 / c.gameSpeed / 30;
  5072. //let alphaFactor = (delta > expected) ? expected / delta : 1;
  5073. //roomSpeed = c.gameSpeed * alphaFactor;
  5074. //setTimeout(moveloop, 1000 / roomSpeed / 30 - delta);
  5075. })();
  5076. var funloop = (() => {
  5077. // Fun stuff, like RAINBOWS :D
  5078. function rainbow(my) {
  5079. let rainbow = [12, 2, 3, 11, 10, 14]
  5080. entities.forEach(function(element) {
  5081. if (element.rainbow) {
  5082. if (rainbow.indexOf(element.color) == -1 || element.color == undefined) {
  5083. element.color = 12
  5084. } else {
  5085. if (element.rainbow_back == false) {
  5086. element.color = rainbow[rainbow.indexOf(element.color) + 1]
  5087. } else {
  5088. element.color = rainbow[rainbow.indexOf(element.color) - 1]
  5089. }
  5090. }
  5091. if (element.color == 14) {
  5092. element.rainbow_back = true
  5093. }
  5094. if (element.color == 12) {
  5095. element.rainbow_back = false
  5096. }
  5097. }
  5098. }
  5099. )}
  5100. return () => {
  5101. // run the fun stuff :P
  5102. rainbow()
  5103. };
  5104. })();
  5105. // A less important loop. Runs at an actual 5Hz regardless of game speed.
  5106. var poisonLoop = (() => {
  5107. // Fun stuff, like RAINBOWS :D
  5108. function poison(my) {
  5109. entities.forEach(function(element) {
  5110. if (element.showpoison) {
  5111. let x = element.size + 10
  5112. let y = element.size + 10
  5113. Math.random() < 0.5 ? x *= -1 : x
  5114. Math.random() < 0.5 ? y *= -1 : y
  5115. Math.random() < 0.5 ? x *= Math.random() + 1 : x
  5116. Math.random() < 0.5 ? y *= Math.random() + 1 : y
  5117. var o = new Entity({
  5118. x: element.x + x,
  5119. y: element.y + y
  5120. })
  5121. o.define(Class['poisonEffect'])
  5122. }
  5123. if (element.poisoned && (element.type == 'tank' || element.type == 'crasher' || element.type == 'food' || element.type == 'minion' || element.type == 'drone' || element.type == 'swarm')) {
  5124. let x = element.size + 10
  5125. let y = element.size + 10
  5126. Math.random() < 0.5 ? x *= -1 : x
  5127. Math.random() < 0.5 ? y *= -1 : y
  5128. Math.random() < 0.5 ? x *= Math.random() + 1 : x
  5129. Math.random() < 0.5 ? y *= Math.random() + 1 : y
  5130. var o = new Entity({
  5131. x: element.x + x,
  5132. y: element.y + y
  5133. })
  5134. o.define(Class['poisonEffect'])
  5135.  
  5136. if (!element.invuln) {
  5137. element.health.amount -= element.health.max / (45 - element.poisonLevel)
  5138. element.shield.amount -= element.shield.max / (10 - element.poisonLevel)
  5139. }
  5140.  
  5141. element.poisonTime -= 1
  5142. if (element.poisonTime <= 0) element.poisoned = false
  5143.  
  5144. if (element.health.amount <= 0 && element.poisonedBy != undefined && element.poisonedBy.skill != undefined) {
  5145. element.poisonedBy.skill.score += Math.ceil(util.getJackpot(element.poisonedBy.skill.score));
  5146. element.poisonedBy.sendMessage('You killed ' + element.name + ' with poison.');
  5147. if (element.poisonedBy.name != null) {element.sendMessage('You have been killed by ' + element.poisonedBy.name + ' with poison.')
  5148. } else if (element.poisonedBy.name == null) {
  5149. element.sendMessage('You have been killed with poison.')
  5150. }
  5151. }
  5152. }
  5153. }
  5154. )}
  5155. return () => {
  5156. // run the poison
  5157. poison()
  5158. };
  5159. })();
  5160. var maintainloop = (() => {
  5161. // Place obstacles
  5162. function placeRoids() {
  5163. function placeRoid(type, entityClass) {
  5164. let x = 0;
  5165. let position;
  5166. do { position = room.randomType(type);
  5167. x++;
  5168. if (x>200) { util.warn("Could not place some roids."); return 0; }
  5169. } while (dirtyCheck(position, 10 + entityClass.SIZE));
  5170. let o = new Entity(position);
  5171. o.define(entityClass);
  5172. o.team = -101;
  5173. o.facing = ran.randomAngle();
  5174. o.protect();
  5175. o.life();
  5176. }
  5177. // Start placing them
  5178. let roidcount = room.roid.length * room.width * room.height / room.xgrid / room.ygrid / 50000 / 1.5;
  5179. let rockcount = room.rock.length * room.width * room.height / room.xgrid / room.ygrid / 250000 / 1.5;
  5180. let count = 0;
  5181. for (let i=Math.ceil(roidcount); i; i--) { count++; placeRoid('roid', ran.choose([Class.obstacle, Class.obstacle, Class.obstacle, Class.obstacle, Class.autoroid])); }
  5182. 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])); }
  5183. for (let i=Math.ceil(rockcount); i; i--) { count++; placeRoid('rock', ran.choose([Class.obstacle, Class.obstacle, Class.obstacle, Class.obstacle, Class.autoroid])); }
  5184. 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])); }
  5185. util.log('Placing ' + count + ' obstacles!');
  5186. }
  5187. placeRoids();
  5188. // Spawning functions
  5189. let spawnBosses = (() => {
  5190. let timer = 0;
  5191. let boss = (() => {
  5192. let i = 0,
  5193. names = [],
  5194. bois = [Class.egg],
  5195. n = 0,
  5196. begin = 'yo some shit is about to move to a lower position',
  5197. arrival = 'Something happened lol u should probably let Neph know this broke',
  5198. loc = 'norm';
  5199. let spawn = () => {
  5200. let spot, m = 0;
  5201. do {
  5202. spot = room.randomType(loc); m++;
  5203. } while (dirtyCheck(spot, 500) && m<30);
  5204. let o = new Entity(spot);
  5205. o.define(ran.choose(bois));
  5206. o.team = -100;
  5207. o.name = names[i++];
  5208. };
  5209. return {
  5210. prepareToSpawn: (classArray, number, nameClass, typeOfLocation = 'norm') => {
  5211. n = number;
  5212. bois = classArray;
  5213. loc = typeOfLocation;
  5214. names = ran.chooseBossName(nameClass, number);
  5215. i = 0;
  5216. if (n === 1) {
  5217. begin = 'A visitor is coming.';
  5218. arrival = names[0] + ' has arrived.';
  5219. } else {
  5220. begin = 'Visitors are coming.';
  5221. arrival = '';
  5222. for (let i=0; i<n-2; i++) arrival += names[i] + ', ';
  5223. arrival += names[n-2] + ' and ' + names[n-1] + ' have arrived.';
  5224. }
  5225. },
  5226. spawn: () => {
  5227. sockets.broadcast(begin);
  5228. for (let i=0; i<n; i++) {
  5229. setTimeout(spawn, ran.randomRange(3500, 5000));
  5230. }
  5231. // Wrap things up.
  5232. setTimeout(() => sockets.broadcast(arrival), 5000);
  5233. util.log('[SPAWN] ' + arrival);
  5234. },
  5235. };
  5236. })();
  5237. return census => {
  5238. if (timer > 500 && ran.dice(510 - timer)) {
  5239. util.log('[SPAWN] Preparing to spawn...');
  5240. timer = 0;
  5241. let choice = [];
  5242. switch (ran.chooseChance(1, 1, 1, 1)) {
  5243. case 0:
  5244. choice = [[Class.palisade], 1, 'castle', 'norm'];
  5245. sockets.broadcast('A strange trembling...');
  5246. break;
  5247. case 1:
  5248. sockets.broadcast('Rifles Unite...');
  5249. choice = [[Class.riflespin], 2, 'castle', 'norm'];
  5250. break;
  5251. case 2:
  5252. choice = [[Class.extradice], 3, 'castle', 'norm'];
  5253. sockets.broadcast('Theres a Disturbance in the air...');
  5254. break;
  5255.  
  5256. case 3:
  5257. choice = [[Class.elitesquare], 1, 'castle', 'norm'];
  5258. sockets.broadcast('The Squares are fed up...');
  5259. break;
  5260.  
  5261. }
  5262. boss.prepareToSpawn(...choice);
  5263. setTimeout(boss.spawn, 300);
  5264. // Set the timeout for the spawn functions
  5265. } else if (!census.miniboss) timer++;
  5266. };
  5267. })();
  5268.  
  5269.  
  5270.  
  5271. let spawnCrasher = census => {
  5272. if (ran.chance(1 - 0.8 * census.crasher / room.maxFood / room.nestFoodAmount)) {
  5273. let spot, i = 30;
  5274. do { spot = room.randomType('nest'); i--; if (!i) return 0; } while (dirtyCheck(spot, 100));
  5275. let type = (ran.dice(80)) ? ran.choose([Class.sentryGun, Class.sentrySwarm, Class.sentryTrap]) : Class.crasher;
  5276. let o = new Entity(spot);
  5277. o.define(type);
  5278. o.team = -100;
  5279. }
  5280. };
  5281.  
  5282. // The NPC function
  5283. let makenpcs = (() => {
  5284. // Make base protectors if needed.
  5285. /*let f = (loc, team) => {
  5286. let o = new Entity(loc);
  5287. o.define(Class.baseProtector);
  5288. o.team = -team;
  5289. o.color = [10, 11, 12, 15][team-1];
  5290. };
  5291. for (let i=1; i<5; i++) {
  5292. room['bas' + i].forEach((loc) => { f(loc, i); });
  5293. }*/
  5294. // Return the spawning function
  5295. let bots = [];
  5296. return () => {
  5297. let census = {
  5298. crasher: 0,
  5299. miniboss: 0,
  5300. tank: 0,
  5301. dodecagon: 0,
  5302. };
  5303. let npcs = entities.map(function npcCensus(instance) {
  5304. if (census[instance.type] != null) {
  5305. census[instance.type]++;
  5306. return instance;
  5307. }
  5308. }).filter(e => { return e; });
  5309. // Spawning
  5310. //spawnCrasher(census);
  5311. spawnBosses(census);
  5312. // Bots
  5313. if (bots.length < c.BOTS) {
  5314. let o = new Entity(room.random());
  5315. o.color = 17;
  5316. o.define(Class.bot);
  5317. o.define(Class.basic);
  5318. o.name += ran.chooseBotName();
  5319. o.refreshBodyAttributes();
  5320. o.color = 17;
  5321. bots.push(o);
  5322. }
  5323. // Remove dead ones
  5324. bots = bots.filter(e => { return !e.isDead(); });
  5325. };
  5326. })();
  5327. // The big food function
  5328. let makefood = (() => {
  5329. let food = [], foodSpawners = [];
  5330. // The two essential functions
  5331. function getFoodClass(level) {
  5332. let a = { };
  5333. switch (level) {
  5334. case 0: a = Class.egg; break;
  5335. case 1: a = Class.square; break;
  5336. case "e": a = Class.wiffle; break;
  5337. case 2: a = Class.triangle; break;
  5338. case 3: a = Class.pentagon; break;
  5339. case 4: a = Class.bigPentagon; break;
  5340. case 5: a = Class.hugePentagon; break;
  5341. case 6: a = Class.megaPentagon; break;
  5342. case "h": a = Class.hexagon; break;
  5343. case "d": a = Class.dodecagon; break;
  5344. default: throw('bad food level');
  5345. }
  5346. if (a !== {}) {
  5347. a.BODY.ACCELERATION = 0.015 / (a.FOOD.LEVEL + 1);
  5348. }
  5349. return a;
  5350. }
  5351. let placeNewFood = (position, scatter, level, allowInNest = false) => {
  5352. let o = nearest(food, position);
  5353. let mitosis = false;
  5354. let seed = false;
  5355. // Find the nearest food and determine if we can do anything with it
  5356. if (o != null) {
  5357. for (let i=50; i>0; i--) {
  5358. if (scatter == -1 || util.getDistance(position, o) < scatter) {
  5359. if (ran.dice((o.foodLevel + 1) * (o.foodLevel + 1))) {
  5360. mitosis = true; break;
  5361. } else {
  5362. seed = true; break;
  5363. }
  5364. }
  5365. }
  5366. }
  5367. // Decide what to do
  5368. if (scatter != -1 || mitosis || seed) {
  5369. // Splitting
  5370. if (o != null && (mitosis || seed) && room.isIn('nest', o) === allowInNest) {
  5371. let levelToMake = (mitosis) ? o.foodLevel : level,
  5372. place = {
  5373. x: o.x + o.size * Math.cos(o.facing),
  5374. y: o.y + o.size * Math.sin(o.facing),
  5375. };
  5376. let new_o = new Entity(place);
  5377. new_o.define(getFoodClass(levelToMake));
  5378. new_o.team = -100;
  5379. new_o.facing = o.facing + ran.randomRange(Math.PI/2, Math.PI);
  5380. food.push(new_o);
  5381. return new_o;
  5382. }
  5383. // Brand new
  5384. else if (room.isIn('nest', position) === allowInNest) {
  5385. if (!dirtyCheck(position, 20)) {
  5386. o = new Entity(position);
  5387. o.define(getFoodClass(level));
  5388. o.team = -100;
  5389. o.facing = ran.randomAngle();
  5390. food.push(o);
  5391. return o;
  5392. }
  5393. }
  5394. }
  5395. };
  5396. // Define foodspawners
  5397. class FoodSpawner {
  5398. constructor() {
  5399. this.foodToMake = Math.ceil(Math.abs(ran.gauss(0, room.scale.linear*80)));
  5400. this.size = Math.sqrt(this.foodToMake) * 25;
  5401.  
  5402. // Determine where we ought to go
  5403. let position = {}; let o;
  5404. do {
  5405. position = room.gaussRing(1/3, 20);
  5406. o = placeNewFood(position, this.size, 0);
  5407. } while (o == null);
  5408.  
  5409. // Produce a few more
  5410. for (let i=Math.ceil(Math.abs(ran.gauss(0, 4))); i<=0; i--) {
  5411. placeNewFood(o, this.size, 0);
  5412. }
  5413. // Set location
  5414. this.x = o.x;
  5415. this.y = o.y;
  5416. //util.debug('FoodSpawner placed at ('+this.x+', '+this.y+'). Set to produce '+this.foodToMake+' food.');
  5417. }
  5418. rot() {
  5419. if (--this.foodToMake < 0) {
  5420. //util.debug('FoodSpawner rotted, respawning.');
  5421. util.remove(foodSpawners, foodSpawners.indexOf(this));
  5422. foodSpawners.push(new FoodSpawner());
  5423. }
  5424. }
  5425. }
  5426. // Add them
  5427. foodSpawners.push(new FoodSpawner());
  5428. foodSpawners.push(new FoodSpawner());
  5429. foodSpawners.push(new FoodSpawner());
  5430. foodSpawners.push(new FoodSpawner());
  5431. // Food making functions
  5432. let makeGroupedFood = () => { // Create grouped food
  5433. // Choose a location around a spawner
  5434. let spawner = foodSpawners[ran.irandom(foodSpawners.length - 1)],
  5435. bubble = ran.gaussRing(spawner.size, 1/4);
  5436. placeNewFood({ x: spawner.x + bubble.x, y: spawner.y + bubble.y, }, -1, 0);
  5437. spawner.rot();
  5438. };
  5439. let makeDistributedFood = () => { // Distribute food everywhere
  5440. //util.debug('Creating new distributed food.');
  5441. let spot = {};
  5442. do { spot = room.gaussRing(1/2, 2); } while (room.isInNorm(spot));
  5443. placeNewFood(spot, 0.01 * room.width, 0);
  5444. };
  5445. let makeCornerFood = () => { // Distribute food in the corners
  5446. let spot = {};
  5447. do { spot = room.gaussInverse(5); } while (room.isInNorm(spot));
  5448. placeNewFood(spot, 0.05 * room.width, 0);
  5449. };
  5450. let makeNestFood = () => { // Make nest pentagons
  5451. let spot = room.randomType('nest');
  5452. placeNewFood(spot, 0.01 * room.width, 3, true);
  5453. };
  5454. // Return the full function
  5455. return () => {
  5456. // Find and understand all food
  5457. let census = {
  5458. [0]: 0, // Egg
  5459. [1]: 0, // Square
  5460. [2]: 0, // Triangle
  5461. [3]: 0, // Penta
  5462. [4]: 0, // Beta
  5463. [5]: 0, // Alpha
  5464. [6]: 0, // Gamma
  5465. [7]: 0,
  5466. tank: 0,
  5467. sum: 0,
  5468. };
  5469. let censusNest = {
  5470. [0]: 0, // Egg
  5471. [1]: 0, // Square
  5472. [2]: 0, // Triangle
  5473. [3]: 0, // Penta
  5474. [4]: 0, // Beta
  5475. [5]: 0, // Alpha
  5476. [6]: 0, // Gamma
  5477. [7]: 0,
  5478. sum: 0,
  5479. };
  5480. // Do the censusNest
  5481. food = entities.map(instance => {
  5482. try {
  5483. if (instance.type === 'tank') {
  5484. census.tank++;
  5485. } else if (instance.foodLevel > -1) {
  5486. if (room.isIn('nest', { x: instance.x, y: instance.y, })) { censusNest.sum++; censusNest[instance.foodLevel]++; }
  5487. else { census.sum++; census[instance.foodLevel]++; }
  5488. return instance;
  5489. }
  5490. } catch (err) { util.error(instance.label); util.error(err); instance.kill(); }
  5491. }).filter(e => { return e; });
  5492. // Sum it up
  5493. let maxFood = 1 + room.maxFood + 15 * census.tank;
  5494. let maxNestFood = 1 + room.maxFood * room.nestFoodAmount;
  5495. let foodAmount = census.sum;
  5496. let nestFoodAmount = censusNest.sum;
  5497. /*********** ROT OLD SPAWNERS **********/
  5498. //foodSpawners.forEach(spawner => { if (ran.chance(1 - foodAmount/maxFood)) spawner.rot(); });
  5499. let i = 0;
  5500. const length = foodSpawners.length;
  5501. for (; i < length; i++) {
  5502. if (ran.chance(1 - foodAmount / maxFood)) {
  5503. foodSpawners[i].rot();
  5504. }
  5505. }
  5506. /************** MAKE FOOD **************/
  5507. while (ran.chance(0.8 * (1 - foodAmount * foodAmount / maxFood / maxFood))) {
  5508. switch (ran.chooseChance(10, 2, 1)) {
  5509. case 0: makeGroupedFood(); break;
  5510. case 1: makeDistributedFood(); break;
  5511. case 2: makeCornerFood(); break;
  5512. }
  5513. }
  5514. while (ran.chance(0.5 * (1 - nestFoodAmount * nestFoodAmount / maxNestFood / maxNestFood))) makeNestFood();
  5515. /************* UPGRADE FOOD ************/
  5516. if (!food.length) return 0;
  5517. for (let i=Math.ceil(food.length / 100); i>0; i--) {
  5518. let o = food[ran.irandom(food.length - 1)], // A random food instance
  5519. oldId = -1000,
  5520. overflow, location;
  5521. // Bounce 6 times
  5522. for (let j=0; j<6; j++) {
  5523. overflow = 10;
  5524. // Find the nearest one that's not the last one
  5525. do { o = nearest(food, { x: ran.gauss(o.x, 30), y: ran.gauss(o.y, 30), });
  5526. } while (o.id === oldId && --overflow);
  5527. if (!overflow) continue;
  5528. // Configure for the nest if needed
  5529. let proportions = c.FOOD,
  5530. cens = census,
  5531. amount = foodAmount;
  5532. if (room.isIn('nest', o)) {
  5533. proportions = c.FOOD_NEST;
  5534. cens = censusNest;
  5535. amount = nestFoodAmount;
  5536. }
  5537. // Upgrade stuff
  5538. o.foodCountup += Math.ceil(Math.abs(ran.gauss(0, 10)));
  5539. while (o.foodCountup >= (o.foodLevel + 1) * 100) {
  5540. o.foodCountup -= (o.foodLevel + 1) * 100;
  5541. if (ran.chance(1 - cens[o.foodLevel + 1] / amount / proportions[o.foodLevel + 1])) {
  5542. o.define(getFoodClass(o.foodLevel + 1));
  5543. }
  5544. }
  5545. }
  5546. }
  5547. };
  5548. })();
  5549. // Define food and food spawning
  5550. return () => {
  5551. // Do stuff
  5552. makenpcs();
  5553. makefood();
  5554. // Regen health and update the grid
  5555. let i = 0;
  5556. const length = entities.length;
  5557. //for (var instance of entities) {
  5558. for (; i < length; i++) {
  5559. if (entities[i].shield.max) {
  5560. entities[i].shield.regenerate();
  5561. }
  5562. if (entities[i].health.amount) {
  5563. entities[i].health.regenerate(entities[i].shield.max && entities[i].shield.max === entities[i].shield.amount);
  5564. }
  5565. };
  5566. };
  5567. })();
  5568.  
  5569. // This is the checking loop. Runs at 1Hz.
  5570. var speedcheckloop = (() => {
  5571. let fails = 0;
  5572. // Return the function
  5573. return () => {
  5574. let activationtime = logs.activation.sum(),
  5575. collidetime = logs.collide.sum(),
  5576. movetime = logs.entities.sum(),
  5577. playertime = logs.network.sum(),
  5578. maptime = logs.minimap.sum(),
  5579. physicstime = logs.physics.sum(),
  5580. lifetime = logs.life.sum(),
  5581. selfietime = logs.selfie.sum();
  5582. let sum = logs.master.record();
  5583. let loops = logs.loops.count(),
  5584. active = logs.entities.count();
  5585. global.fps = (1000 / sum);
  5586. if (sum > 1000 / roomSpeed / global.fps) {
  5587. //fails++;
  5588. util.warn('~~ LOOPS: ' + loops + '. ENTITY #: ' + entities.length + '//' + Math.round(active/loops) + '. VIEW #: ' + views.length + '. BACKLOGGED :: ' + (sum * roomSpeed * 3).toFixed(3) + '%! ~~');
  5589. util.warn('Total activation time: ' + activationtime);
  5590. util.warn('Total collision time: ' + collidetime);
  5591. util.warn('Total cycle time: ' + movetime);
  5592. util.warn('Total player update time: ' + playertime);
  5593. util.warn('Total lb+minimap processing time: ' + maptime);
  5594. util.warn('Total entity physics calculation time: ' + physicstime);
  5595. util.warn('Total entity life+thought cycle time: ' + lifetime);
  5596. util.warn('Total entity selfie-taking time: ' + selfietime);
  5597. util.warn('Total time: ' + (activationtime + collidetime + movetime + playertime + maptime + physicstime + lifetime + selfietime));
  5598. if (fails > 60) {
  5599. util.error("FAILURE!");
  5600. //process.exit(1);
  5601. }
  5602. } else {
  5603. fails = 0;
  5604. }
  5605. };
  5606. })();
  5607.  
  5608.  
  5609. /** BUILD THE SERVERS **/
  5610. // Turn the server on
  5611. var server = http.createServer(app);
  5612. var websockets = (() => {
  5613. // Configure the websocketserver
  5614. let config = { server: server };
  5615. if (c.servesStatic) {
  5616. server.listen(c.port, function httpListening() {
  5617. util.log((new Date()) + ". Joint HTTP+Websocket server turned on, listening on port "+server.address().port + ".");
  5618. });
  5619. } else {
  5620. config.port = c.port;
  5621. util.log((new Date()) + 'Websocket server turned on, listening on port ' + c.port + '.');
  5622. }
  5623. // Build it
  5624. return new WebSocket.Server(config);
  5625. })().on('connection', sockets.connect);
  5626.  
  5627. var tickLengthMs = 1000 / 20;
  5628. var previousTick = now();
  5629. var actualTicks = 0;
  5630. var gameexecution = function () {
  5631. var current = now();
  5632.  
  5633. actualTicks++;
  5634. if (previousTick + tickLengthMs <= current) {
  5635. //var delta = (current - previousTick) / 1000;
  5636. var curTime = now();
  5637. timestep = 0.01575 * (curTime - lastTime);
  5638. if (timestep <= 0 || timestep > 7.875) {
  5639. timestep = 0.01575;
  5640. }
  5641.  
  5642. previousTick = current;
  5643.  
  5644. gameloop();
  5645.  
  5646. actualTicks = 0;
  5647. lastTime = curTime;
  5648. }
  5649.  
  5650. if (now() - previousTick < tickLengthMs - 50) {
  5651. timer.setTimeout(gameexecution, '', '50m');
  5652. } else {
  5653. //timer.setTimeout(gameexecution, '', '1m');
  5654. setImmediate(gameexecution);
  5655. }
  5656. }
  5657.  
  5658. // Bring it to life
  5659. gameexecution();
  5660. //setInterval(gameloop, room.cycleSpeed);
  5661. setInterval(maintainloop, 100);
  5662. setInterval(speedcheckloop, 1000);
  5663. setInterval(poisonLoop, room.cycleSpeed * 20)
  5664.  
  5665. // Graceful shutdown
  5666. let shutdownWarning = false;
  5667. if (process.platform === "win32") {
  5668. var rl = require("readline").createInterface({
  5669. input: process.stdin,
  5670. output: process.stdout
  5671. });
  5672. rl.on("SIGINT", () => {
  5673. process.emit("SIGINT");
  5674. });
  5675. }
  5676. process.on("SIGINT", () => {
  5677. if (!shutdownWarning) {
  5678. shutdownWarning = true;
  5679. sockets.broadcast("The server is shutting down.");
  5680. util.log('Server going down! Warning broadcasted.');
  5681. setTimeout(() => {
  5682. sockets.broadcast("Arena closed.");
  5683. util.log('Final warning broadcasted.');
  5684. setTimeout(() => {
  5685. util.warn('Process ended.');
  5686. process.exit();
  5687. }, 3000);
  5688. }, 17000);
  5689. }
  5690. });
  5691.  
  5692.  
  5693.  
  5694. const Eris = require('eris');
  5695. const bot = new Eris(process.env.bot_token);
  5696.  
  5697. bot.on('ready', () => {
  5698. console.log('\nBot ready!\n');
  5699. var canLogToDiscord = true
  5700. });
  5701.  
  5702. var unauth = '```patch\n- ERROR: UNATHORIZED USER```'
  5703. var mliststring = ''
  5704. var mliststring2 = ''
  5705. /*mlist.forEach(function(element) {
  5706. if (mliststring.length < 1970) {
  5707. mliststring += element += ', ';
  5708. } else {
  5709. mliststring2 += element += ', '
  5710. }
  5711. });*/
  5712.  
  5713. function parse(input) {
  5714. let out = input.split(" ");
  5715. return out
  5716. }
  5717.  
  5718. bot.on('messageCreate', (msg) => {
  5719. try {
  5720. if (msg.author.id != 452793079238361089) {
  5721. if (msg.content.startsWith("k!select ")) {
  5722. if (process.env.ISONGLITCH == undefined) {
  5723. let sendError = true
  5724. let lookfor = msg.content.split("k!select ").pop()
  5725. function thing(element) {
  5726. if (typeof element.sendMessage == "function" && element.name == lookfor) {
  5727. sendError = false
  5728. 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));
  5729. }
  5730. }
  5731. for (var element of entities) {
  5732. thing(element);
  5733. }
  5734. if (sendError) {
  5735. bot.createMessage(msg.channel.id, "Was unable to find an entity by that name");
  5736. }
  5737. }}
  5738. if (msg.content == 'k!ping') {
  5739. bot.createMessage(msg.channel.id, 'Pong!\n' + "\nConnections amount: " + global.extPlayers + "\nRunning on glitch: " + process.env.ISONGLITCH + "\nDirectory: " + __dirname + "\nFile name: " + __filename);
  5740. }
  5741. if (msg.content == 'k!list') {
  5742. //if (process.env.ISONGLITCH == undefined) {
  5743. bot.createMessage(msg.channel.id, mliststring);
  5744. bot.createMessage(msg.channel.id, mliststring2);
  5745. }//}
  5746. if (msg.content.includes('k!help')) {
  5747. if (process.env.ISONGLITCH == undefined) {
  5748. 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)');
  5749. }}
  5750. if (msg.content == 'k!kill') {
  5751. if (msg.author.id == 181829457852628993) {
  5752. console.log("\n SERVER TERMINATED BY AN AUTHORIZED USER \n")
  5753. bot.createMessage(msg.channel.id, 'Terminating.....');
  5754. process.emit("SIGINT")
  5755. } else {
  5756. console.log("Unauthorized user", msg.author.username, "tried to end server")
  5757. bot.createMessage(msg.channel.id, unauth);
  5758. }
  5759. }
  5760. if (msg.content.startsWith('k!broadcast')) {
  5761. if (msg.author.id == 181829457852628993 || 340270483838599168) {
  5762. if (process.env.ISONGLITCH == undefined) {
  5763. sockets.broadcast(msg.content.split("k!broadcast").pop() + " - " + msg.author.username)
  5764. bot.createMessage(msg.channel.id, 'Message Broadcasted and rendered.');
  5765. }} else {
  5766. console.log("Unauthorized user", msg.author.username, "tried to broadcast a message")
  5767. bot.createMessage(msg.channel.id, unauth);
  5768. }
  5769. }
  5770. if (msg.content.startsWith('k!query')) {
  5771. if (process.env.ISONGLITCH == undefined) {
  5772. let output = ''
  5773. var query = msg.content.split(">query ").pop()
  5774. try {
  5775. var botreturn = eval('Class.' + query);
  5776. for (var key in botreturn) {
  5777. if (output.length > 500) {console.log(output.length); bot.createMessage(msg.channel.id, output); output = ''}
  5778. output += String(key) + ': ' + eval('Class.' + query + '.' + String(key)) + '\n'
  5779. var returned = typeof eval('Class.' + query + '.' + String(key))
  5780. if (returned == 'object') {
  5781. for (var key2 in eval('Class.' + query + '.' + String(key))) {
  5782. if (key2 != 'remove') {
  5783. try {
  5784. output += "^ " + String(key2) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']') + '\n'
  5785. var returned = typeof eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']')
  5786. var returnedobj = eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']')
  5787. } catch(err) {
  5788. output += "^ " + String(key2) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2)) + '\n'
  5789. var returned = typeof eval('Class.' + query + '.' + String(key) + '.' + String(key2))
  5790. var returnedobj = eval('Class.' + query + '.' + String(key) + '.' + String(key2))
  5791. }
  5792. if (returned == 'object') {
  5793. for (var key3 in returnedobj) {
  5794. if (key3 != 'remove') {
  5795. try {
  5796. output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']' + '[' + String(key3) + ']') + '\n'
  5797. } catch(err) {
  5798. try {
  5799. output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '[' + String(key2) + ']' + '.' + String(key3)) + '\n'
  5800. } catch(err) {
  5801. try {
  5802. output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2) + '[' + String(key3) + ']') + '\n'
  5803. } catch(err) {
  5804. output += "^ ^ " + String(key3) + ': ' + eval('Class.' + query + '.' + String(key) + '.' + String(key2) + '.' + String(key3)) + '\n'
  5805. }
  5806. }
  5807. }
  5808. }
  5809. }
  5810. }
  5811. }
  5812. }
  5813. }
  5814. }
  5815. } catch(err) {
  5816. bot.createMessage(msg.channel.id, String(err));
  5817. }
  5818. bot.createMessage(msg.channel.id, output);
  5819. }}
  5820. /*if (msg.content.startsWith('k!summon ')) {
  5821. if (msg.author.id == 181829457852628993) {
  5822. var spawnClass = msg.content.split("k!summon ").pop().substr(0, 3)
  5823. console.log(msg.content.split("k!summon ").pop().substr(0, 3))
  5824. var type = msg.content.split(" ").pop()
  5825. if (spawnClass == 'bot') {
  5826. botSpawn = type
  5827. bot.createMessage(msg.channel.id, "Next bot will be a " + type);
  5828. } else if (spawnClass == 'food') {
  5829.  
  5830. } else {
  5831. bot.createMessage(msg.channel.id, "Was unable to complete request, unknown summon type: " + spawnClass);
  5832. }
  5833. } else {
  5834. console.log("Unauthorized user", msg.author.username, "tried to broadcast")
  5835. bot.createMessage(msg.channel.id, unauth);
  5836. }
  5837. }*/
  5838. if (msg.content == 'k!players') {
  5839. let output = ''
  5840. let outWillFail = true
  5841. //entities.forEach(function(element) {
  5842. //if (typeof element.sendMessage == "function" && element.name != '') {
  5843. // output += String(element.name + ' - ' + element.id + '\n')
  5844. // outWillFail = false
  5845. //}
  5846. //})
  5847.  
  5848. let i = 0;
  5849. const length = entities.length;
  5850. for (; i < length; i++) {
  5851. //for (let instance of entities) {
  5852. let instance = entities[i];
  5853. if (typeof instance.sendMessage == "function" && instance.name != '') {
  5854. output += String(instance.name + ' - ' + instance.id + '\n');
  5855. outWillFail = false;
  5856. }
  5857. }
  5858. if (!outWillFail) {
  5859. bot.createMessage(msg.channel.id, output)}
  5860. else {
  5861. bot.createMessage(msg.channel.id, "There are currently no players on the server")}}
  5862. if (msg.content.startsWith('k!stat ')) {
  5863. if (msg.author.id == 181829457852628993 || 340270483838599168 || 350336602956103683) {
  5864. let s_command = parse(msg.content)
  5865. let s_lookForId = s_command[1]
  5866. let s_statpath = s_command[2]
  5867. let s_newvalue = s_command[3]
  5868. entities.forEach(function(element) {
  5869. if (element.id == s_lookForId) {
  5870. try {
  5871. eval('element' + s_statpath + ' = ' + s_newvalue)
  5872. } catch(err) {
  5873. eval('element' + s_statpath + ' = "' + s_newvalue + '"')
  5874. }
  5875. bot.createMessage(msg.channel.id, "Value set to " + String(eval('element' + s_statpath)));
  5876. }})
  5877. } else {
  5878. bot.createMessage(msg.channel.id, unauth);
  5879. }}
  5880. if (msg.content.startsWith('k!define ')) {
  5881. let printerror = true
  5882. let command = parse(msg.content)
  5883. let inputid = command[1]
  5884. let inputclass = command[2]
  5885. if (msg.author.id == 181829457852628993 || 340270483838599168) {
  5886. if (eval(Class[inputclass]) != undefined) {
  5887. entities.filter(r => r.id == inputid)[0].define(Class[inputclass])
  5888. printerror = false
  5889. bot.createMessage(msg.channel.id, 'Defined user as Class.' + inputclass);
  5890. } else {
  5891. bot.createMessage(msg.channel.id, inputclass + ' is not a valid tank');
  5892. }
  5893. if (printerror) {
  5894. bot.createMessage(msg.channel.id, "Count find any users by the id: " + inputid);
  5895. }
  5896. } else {
  5897. bot.createMessage(msg.channel.id, unauth);
  5898. }}}
  5899. } catch(err) { // log the error in chat
  5900. bot.createMessage(msg.channel.id, String(err));
  5901. }});
  5902.  
  5903. bot.editStatus('online', {
  5904. name: 'Type k!help for commands!',
  5905. type: 0
  5906. });
  5907.  
  5908. bot.connect();exports.circleArea = function (radius){
  5909. return Math.pow(radius, 2) * Math.PI;
  5910. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement