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.
 
 
 

387 lines
7.7 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.zoneMapSeed !== map.seed)) {
  164. obj.x = spawnPos.x;
  165. obj.y = spawnPos.y;
  166. }
  167. if (map.seed)
  168. obj.zoneMapSeed = map.seed;
  169. else
  170. delete obj.zoneMapSeed;
  171. obj.spawn = map.spawn;
  172. stageZoneIn(msg);
  173. process.send({
  174. method: 'events',
  175. data: {
  176. getMap: [{
  177. obj: map.clientMap,
  178. to: [obj.serverId]
  179. }]
  180. }
  181. });
  182. },
  183. //This function fires when the player logs in the first time, not upon rezone
  184. onAddObject: function (obj) {
  185. if (obj.player) {
  186. obj.stats.onLogin();
  187. eventEmitter.emit('onAfterPlayerEnterZone', obj, { isTransfer: false });
  188. }
  189. questBuilder.obtain(obj);
  190. obj.fireEvent('afterMove');
  191. if (obj.dead) {
  192. obj.instance.syncer.queue('onDeath', {
  193. x: obj.x,
  194. y: obj.y
  195. }, [obj.serverId]);
  196. }
  197. },
  198. updateObject: function (msg) {
  199. let obj = objects.find(o => o.serverId === msg.id);
  200. if (!obj)
  201. return;
  202. let msgObj = msg.obj;
  203. let components = msgObj.components || [];
  204. delete msgObj.components;
  205. for (let p in msgObj)
  206. obj[p] = msgObj[p];
  207. let cLen = components.length;
  208. for (let i = 0; i < cLen; i++) {
  209. let c = components[i];
  210. let component = obj[c.type];
  211. for (let p in c)
  212. component[p] = c[p];
  213. }
  214. },
  215. queueAction: function (msg) {
  216. let obj = objects.find(o => o.serverId === msg.id);
  217. if (!obj)
  218. return;
  219. else if (msg.action.action === 'move') {
  220. let moveEntries = obj.actionQueue.filter(q => (q.action === 'move')).length;
  221. if (moveEntries >= 50)
  222. return;
  223. }
  224. obj.queue(msg.action);
  225. },
  226. performAction: function (msg) {
  227. let obj = null;
  228. let targetId = msg.action.data.targetId;
  229. if (!targetId)
  230. obj = objects.find(o => o.serverId === msg.id);
  231. else {
  232. obj = objects.find(o => o.id === targetId);
  233. if (obj) {
  234. let action = msg.action;
  235. if (!action.data)
  236. action.data = {};
  237. action.data.sourceId = msg.id;
  238. }
  239. }
  240. if (!obj)
  241. return;
  242. obj.performAction(msg.action);
  243. },
  244. removeObject: async function (msg) {
  245. if (this.regenBusy) {
  246. this.unqueueMessage(msg);
  247. return;
  248. }
  249. //We fire this event because even though an object might be destroyed already,
  250. // mods and modules might have staged events/actions we need to clear
  251. eventEmitter.emit('removeObject', { obj: msg.obj });
  252. let obj = msg.obj;
  253. obj = objects.find(o => o.serverId === obj.id);
  254. if (!obj) {
  255. //We should probably never reach this
  256. return;
  257. }
  258. if (obj.auth)
  259. await obj.auth.doSave();
  260. if (obj.player) {
  261. obj.fireEvent('beforeRezone');
  262. eventEmitter.emit('onAfterPlayerLeaveZone', obj);
  263. }
  264. obj.destroyed = true;
  265. if (msg.callbackId) {
  266. process.send({
  267. module: 'atlas',
  268. method: 'resolveCallback',
  269. msg: {
  270. id: msg.callbackId
  271. }
  272. });
  273. }
  274. },
  275. notifyOnceIdle: async function () {
  276. await transactions.returnWhenDone();
  277. process.send({
  278. method: 'onZoneIdle'
  279. });
  280. },
  281. forceSavePlayer: async function ({ playerName, callbackId }) {
  282. const player = objects.objects.find(o => o.player && o.name === playerName);
  283. if (!player?.auth) {
  284. await io.setAsync({
  285. key: new Date(),
  286. table: 'error',
  287. value: 'no auth found for forcesave ' + playerName
  288. });
  289. return;
  290. }
  291. await player.auth.doSave();
  292. process.send({
  293. module: 'atlas',
  294. method: 'resolveCallback',
  295. msg: {
  296. id: callbackId
  297. }
  298. });
  299. }
  300. };