You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

406 lines
8.1 KiB

  1. let map = require('./map');
  2. let syncer = require('./syncer');
  3. let objects = require('../objects/objects');
  4. let spawners = require('./spawners');
  5. let physics = require('./physics');
  6. let resourceSpawner = require('./resourceSpawner');
  7. let spellCallbacks = require('../config/spells/spellCallbacks');
  8. let questBuilder = require('../config/quests/questBuilder');
  9. let events = require('../events/events');
  10. let scheduler = require('../misc/scheduler');
  11. let herbs = require('../config/herbs');
  12. let eventEmitter = require('../misc/events');
  13. const mods = require('../misc/mods');
  14. const transactions = require('../security/transactions');
  15. //Own helpers
  16. const { stageZoneIn, unstageZoneIn, clientAck } = require('./instancer/handshakes');
  17. module.exports = {
  18. instances: [],
  19. zoneId: -1,
  20. speed: consts.tickTime,
  21. //During regens, adds are placed in a queue
  22. addQueue: [],
  23. lastTime: 0,
  24. mapName: null,
  25. threadArgs: null,
  26. init: function (args) {
  27. const { zoneId, zoneName } = args;
  28. this.zoneName = zoneName;
  29. this.zoneId = zoneId;
  30. spellCallbacks.init();
  31. herbs.init();
  32. map.init(args);
  33. const fakeInstance = {
  34. objects,
  35. syncer,
  36. physics,
  37. zoneId,
  38. zoneName,
  39. spawners,
  40. questBuilder,
  41. events,
  42. map,
  43. scheduler,
  44. eventEmitter,
  45. resourceSpawner,
  46. zoneConfig: map.zoneConfig
  47. };
  48. this.instances.push(fakeInstance);
  49. spawners.init(fakeInstance);
  50. scheduler.init();
  51. map.create();
  52. if (map.mapFile.properties.isRandom) {
  53. if (!map.oldCollisionMap)
  54. map.oldCollisionMap = map.collisionMap;
  55. map.randomMap.init(fakeInstance);
  56. this.startRegen();
  57. } else
  58. _.log(`Ready: ${JSON.stringify(this.threadArgs, null, '\t')}`);
  59. map.clientMap.zoneId = this.zoneId;
  60. [resourceSpawner, syncer, objects, questBuilder, events].forEach(i => i.init(fakeInstance));
  61. this.tick();
  62. this.clientAck = clientAck;
  63. eventEmitter.on('removeObject', unstageZoneIn);
  64. process.send({
  65. method: 'onInitialized'
  66. });
  67. },
  68. startRegen: function (respawnMap, respawnPos) {
  69. this.addQueue = [];
  70. this.regenBusy = true;
  71. this.respawnMap = respawnMap;
  72. this.respawnPos = respawnPos;
  73. },
  74. queueMessage: function (msg) {
  75. this.unqueueMessage(msg);
  76. this.addQueue.push(msg);
  77. },
  78. unqueueMessage: function (msg) {
  79. this.addQueue.spliceWhere(q => q.obj.id === msg.obj.id);
  80. },
  81. tickRegen: function () {
  82. const { respawnPos, respawnMap } = this;
  83. //Ensure that all players are gone
  84. const players = objects.objects.filter(o => o.player);
  85. players.forEach(p => {
  86. if (p.destroyed)
  87. return;
  88. p.fireEvent('beforeRezone');
  89. p.destroyed = true;
  90. const simpleObj = p.getSimple(true, false, true);
  91. if (respawnPos) {
  92. const { x, y } = respawnPos;
  93. simpleObj.x = x;
  94. simpleObj.y = y;
  95. }
  96. process.send({
  97. method: 'rezone',
  98. id: p.serverId,
  99. args: {
  100. obj: simpleObj,
  101. newZone: respawnMap,
  102. keepPos: true
  103. }
  104. });
  105. });
  106. //Only objects and syncer should update if there are players
  107. if (players.length) {
  108. objects.update();
  109. syncer.update();
  110. return;
  111. }
  112. //Clear stuff
  113. spawners.reset();
  114. objects.objects.length = 0;
  115. objects.objects = [];
  116. events.stopAll();
  117. //Try a generation
  118. const isValid = map.randomMap.generate();
  119. if (!isValid)
  120. return;
  121. map.seed = _.getGuid();
  122. //If it succeeds, set regenBusy to false and reset vars
  123. this.regenBusy = false;
  124. this.respawnPos = null;
  125. this.respawnMap = null;
  126. this.addQueue.forEach(q => this.addObject(q));
  127. this.addQueue = [];
  128. _.log(`Ready: ${JSON.stringify(this.threadArgs, null, '\t')}`);
  129. },
  130. tick: function () {
  131. if (this.regenBusy) {
  132. this.tickRegen();
  133. setTimeout(this.tick.bind(this), this.speed);
  134. return;
  135. }
  136. events.update();
  137. objects.update();
  138. resourceSpawner.update();
  139. spawners.update();
  140. syncer.update();
  141. scheduler.update();
  142. mods.tick();
  143. setTimeout(this.tick.bind(this), this.speed);
  144. },
  145. addObject: function (msg) {
  146. if (this.regenBusy) {
  147. this.queueMessage(msg);
  148. return;
  149. }
  150. let obj = msg.obj;
  151. obj.serverId = obj.id;
  152. delete obj.id;
  153. let spawnPos = map.getSpawnPos(obj);
  154. let spawnEvent = {
  155. spawnPos: extend({}, spawnPos),
  156. changed: false
  157. };
  158. eventEmitter.emit('onBeforePlayerSpawn', { name: obj.name, instance: { physics } }, spawnEvent);
  159. //If a player is added, destroy any player objects with the same name
  160. const existing = objects.filter(o => o.player && o.name === msg.obj.name);
  161. existing.forEach(o => {
  162. o.destroyed = true;
  163. });
  164. if (spawnEvent.changed)
  165. msg.keepPos = false;
  166. if (msg.keepPos && (!physics.isValid(obj.x, obj.y) || !map.canPathFromPos(obj)))
  167. msg.keepPos = false;
  168. if (!msg.keepPos || !obj.has('x') || (map.mapFile.properties.isRandom && obj.zoneMapSeed !== map.seed)) {
  169. obj.x = spawnPos.x;
  170. obj.y = spawnPos.y;
  171. }
  172. if (map.seed)
  173. obj.zoneMapSeed = map.seed;
  174. else
  175. delete obj.zoneMapSeed;
  176. obj.spawn = map.spawn;
  177. stageZoneIn(msg);
  178. process.send({
  179. method: 'events',
  180. data: {
  181. getMap: [{
  182. obj: map.clientMap,
  183. to: [obj.serverId]
  184. }]
  185. }
  186. });
  187. },
  188. //This function fires when the player logs in the first time, not upon rezone
  189. onAddObject: function (obj) {
  190. if (obj.player) {
  191. obj.stats.onLogin();
  192. eventEmitter.emit('onAfterPlayerEnterZone', obj, { isTransfer: false });
  193. }
  194. questBuilder.obtain(obj);
  195. obj.fireEvent('afterMove');
  196. if (obj.dead) {
  197. obj.instance.syncer.queue('onDeath', {
  198. x: obj.x,
  199. y: obj.y
  200. }, [obj.serverId]);
  201. }
  202. },
  203. updateObject: function (msg) {
  204. let obj = objects.find(o => o.serverId === msg.id);
  205. if (!obj)
  206. return;
  207. let msgObj = msg.obj;
  208. let components = msgObj.components || [];
  209. delete msgObj.components;
  210. for (let p in msgObj)
  211. obj[p] = msgObj[p];
  212. let cLen = components.length;
  213. for (let i = 0; i < cLen; i++) {
  214. let c = components[i];
  215. let component = obj[c.type];
  216. for (let p in c)
  217. component[p] = c[p];
  218. }
  219. },
  220. queueAction: function (msg) {
  221. let obj = objects.find(o => o.serverId === msg.id);
  222. if (!obj)
  223. return;
  224. else if (msg.action.action === 'move') {
  225. let moveEntries = obj.actionQueue.filter(q => (q.action === 'move')).length;
  226. if (moveEntries >= 50)
  227. return;
  228. }
  229. obj.queue(msg.action);
  230. },
  231. performAction: function (msg) {
  232. let obj = null;
  233. let targetId = msg.action.data.targetId;
  234. if (!targetId)
  235. obj = objects.find(o => o.serverId === msg.id);
  236. else {
  237. obj = objects.find(o => o.id === targetId);
  238. if (obj) {
  239. let action = msg.action;
  240. if (!action.data)
  241. action.data = {};
  242. action.data.sourceId = msg.id;
  243. }
  244. }
  245. if (!obj)
  246. return;
  247. obj.performAction(msg.action);
  248. },
  249. removeObject: async function (msg) {
  250. if (this.regenBusy) {
  251. this.unqueueMessage(msg);
  252. return;
  253. }
  254. //We fire this event because even though an object might be destroyed already,
  255. // mods and modules might have staged events/actions we need to clear
  256. eventEmitter.emit('removeObject', { obj: msg.obj });
  257. let obj = msg.obj;
  258. obj = objects.find(o => o.serverId === obj.id);
  259. if (!obj) {
  260. //We should probably never reach this
  261. return;
  262. }
  263. if (obj.auth)
  264. await obj.auth.doSave();
  265. if (obj.player) {
  266. obj.fireEvent('beforeRezone');
  267. eventEmitter.emit('onAfterPlayerLeaveZone', obj);
  268. }
  269. obj.destroyed = true;
  270. if (msg.callbackId) {
  271. process.send({
  272. module: 'atlas',
  273. method: 'resolveCallback',
  274. msg: {
  275. id: msg.callbackId
  276. }
  277. });
  278. }
  279. },
  280. notifyOnceIdle: async function () {
  281. await transactions.returnWhenDone();
  282. process.send({
  283. method: 'onZoneIdle'
  284. });
  285. },
  286. forceSavePlayer: async function ({ playerId, callbackId }) {
  287. const player = objects.objects.find(o => o.serverId === playerId);
  288. if (!player?.auth) {
  289. await io.setAsync({
  290. key: new Date(),
  291. table: 'error',
  292. value: 'no auth found for forcesave ' + player?.name
  293. });
  294. return;
  295. }
  296. await player.auth.doSave();
  297. process.send({
  298. module: 'atlas',
  299. method: 'resolveCallback',
  300. msg: {
  301. id: callbackId
  302. }
  303. });
  304. },
  305. getPlayerCount: function ({ callbackId }) {
  306. process.send({
  307. module: 'atlas',
  308. method: 'resolveCallback',
  309. msg: {
  310. id: callbackId,
  311. result: {
  312. playerCount: objects.objects.filter(o => o.player !== undefined).length
  313. }
  314. }
  315. });
  316. }
  317. };