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.
 
 
 

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