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.
 
 
 

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