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.
 
 
 

348 lines
7.4 KiB

  1. let childProcess = require('child_process');
  2. let objects = require('../objects/objects');
  3. let mapList = require('../config/maps/mapList');
  4. let connections = require('../security/connections');
  5. let events = require('../misc/events');
  6. const listenersOnZoneIdle = [];
  7. module.exports = {
  8. nextId: 0,
  9. lastCallbackId: 0,
  10. threads: [],
  11. callbacks: [],
  12. init: function () {
  13. this.getMapFiles();
  14. },
  15. addObject: async function (obj, keepPos, transfer) {
  16. const serverObj = objects.objects.find(o => o.id === obj.id);
  17. if (!serverObj)
  18. return;
  19. events.emit('onBeforePlayerEnterWorld', obj);
  20. let thread;
  21. let map = mapList.mapList.find(m => m.name === obj.zoneName);
  22. if (!map)
  23. map = mapList.mapList.find(m => m.name === clientConfig.config.defaultZone);
  24. thread = this.threads.find(t => t.id === obj.zoneId && t.name === obj.zoneName);
  25. if (!thread) {
  26. if (map.instanced) {
  27. delete obj.x;
  28. delete obj.y;
  29. thread = this.spawnMap(map);
  30. await new Promise(res => setTimeout(res, 2000));
  31. } else
  32. thread = this.getThreadFromName(map.name);
  33. }
  34. obj.zoneName = thread.name;
  35. obj.zoneId = thread.id;
  36. serverObj.zoneId = thread.id;
  37. serverObj.zoneName = thread.name;
  38. serverObj.player.broadcastSelf();
  39. const simpleObj = obj.getSimple ? obj.getSimple(true, true) : obj;
  40. this.send(obj.zoneId, {
  41. method: 'addObject',
  42. args: {
  43. keepPos: keepPos,
  44. obj: simpleObj,
  45. transfer: transfer
  46. }
  47. });
  48. },
  49. removeObjectFromInstancedZone: async function (thread, obj, callback) {
  50. await new Promise(res => {
  51. const cb = this.registerCallback(res);
  52. thread.worker.send({
  53. method: 'forceSavePlayer',
  54. args: {
  55. playerName: obj.name,
  56. callbackId: cb
  57. }
  58. });
  59. });
  60. thread.worker.kill();
  61. this.threads.spliceWhere(t => t === thread);
  62. if (callback)
  63. callback();
  64. },
  65. removeObject: function (obj, skipLocal, callback) {
  66. if (!skipLocal)
  67. objects.removeObject(obj);
  68. let thread = this.findObjectThread(obj);
  69. if (!thread)
  70. return;
  71. if (thread.instanced) {
  72. this.removeObjectFromInstancedZone(thread, obj, callback);
  73. return;
  74. }
  75. let callbackId = null;
  76. if (callback)
  77. callbackId = this.registerCallback(callback);
  78. this.send(obj.zoneId, {
  79. method: 'removeObject',
  80. args: {
  81. obj: obj.getSimple(true),
  82. callbackId: callbackId
  83. }
  84. });
  85. },
  86. updateObject: function (obj, msgObj) {
  87. this.send(obj.zoneId, {
  88. method: 'updateObject',
  89. args: {
  90. id: obj.id,
  91. obj: msgObj
  92. }
  93. });
  94. },
  95. queueAction: function (obj, action) {
  96. this.send(obj.zoneId, {
  97. method: 'queueAction',
  98. args: {
  99. id: obj.id,
  100. action: action
  101. }
  102. });
  103. },
  104. performAction: function (obj, action) {
  105. this.send(obj.zoneId, {
  106. method: 'performAction',
  107. args: {
  108. id: obj.id,
  109. action: action
  110. }
  111. });
  112. },
  113. registerCallback: function (callback) {
  114. this.callbacks.push({
  115. id: ++this.lastCallbackId,
  116. callback: callback
  117. });
  118. return this.lastCallbackId;
  119. },
  120. resolveCallback: function (msg) {
  121. let callback = this.callbacks.spliceFirstWhere(c => c.id === msg.msg.id);
  122. if (!callback)
  123. return;
  124. callback.callback(msg.msg.result);
  125. },
  126. send: function (threadId, msg) {
  127. const thread = this.threads.find(t => t.id === threadId);
  128. if (thread)
  129. thread.worker.send(msg);
  130. },
  131. findObjectThread: function ({ zoneId }) {
  132. return this.threads.find(t => t.id === zoneId);
  133. },
  134. getThreadFromName: function (name) {
  135. return this.threads.find(t => t.name === name);
  136. },
  137. getMapFiles: function () {
  138. mapList.mapList
  139. .filter(m => !m.disabled && !m.instanced)
  140. .forEach(m => this.spawnMap(m));
  141. },
  142. spawnMap: function ({ name, path, instanced }) {
  143. const worker = childProcess.fork('./world/worker', [name]);
  144. const id = instanced ? _.getGuid() : name;
  145. const thread = {
  146. id,
  147. name,
  148. instanced,
  149. path,
  150. worker
  151. };
  152. const onMessage = this.onMessage.bind(this, thread);
  153. worker.on('message', function (m) {
  154. onMessage(m);
  155. });
  156. this.threads.push(thread);
  157. return thread;
  158. },
  159. onMessage: function (thread, message) {
  160. if (message.module) {
  161. try {
  162. global[message.module][message.method](message);
  163. } catch (e) {
  164. /* eslint-disable-next-line no-console */
  165. console.log('No global method found', message.module, message.method);
  166. process.exit();
  167. }
  168. } else if (message.event === 'onCrashed') {
  169. thread.worker.kill();
  170. process.exit();
  171. } else
  172. this.thread[message.method].call(this, thread, message);
  173. },
  174. messageAllThreads: function (message) {
  175. this.threads.forEach(t => t.worker.send(message));
  176. },
  177. fireEventOnAllThreads: function ({ msg: { event, data } }) {
  178. this.threads.forEach(t => t.worker.send({ event, data }));
  179. },
  180. thread: {
  181. onReady: function (thread) {
  182. thread.worker.send({
  183. method: 'init',
  184. args: {
  185. zoneName: thread.name,
  186. zoneId: thread.id,
  187. path: thread.path
  188. }
  189. });
  190. },
  191. event: function (thread, message) {
  192. objects.sendEvent(message, thread);
  193. },
  194. events: function (thread, message) {
  195. objects.sendEvents(message, thread);
  196. },
  197. object: function (thread, message) {
  198. objects.updateObject(message);
  199. },
  200. track: function (thread, message) {
  201. let player = objects.objects.find(o => o.id === message.serverId);
  202. if (!player)
  203. return;
  204. player.auth.gaTracker.track(message.obj);
  205. },
  206. callDifferentThread: function (thread, message) {
  207. let obj = connections.players.find(p => (p.name === message.playerName));
  208. if (!obj)
  209. return;
  210. let newThread = this.getThreadFromName(obj.zoneName);
  211. if (!newThread)
  212. return;
  213. newThread.worker.send({
  214. module: message.data.module,
  215. method: message.data.method,
  216. args: message.data.args
  217. });
  218. },
  219. rezone: async function (thread, message) {
  220. const { args: { obj, newZone, keepPos = true } } = message;
  221. if (thread.instanced) {
  222. thread.worker.kill();
  223. this.threads.spliceWhere(t => t === thread);
  224. }
  225. //When messages are sent from map threads, they have an id (id of the object in the map thread)
  226. // as well as a serverId (id of the object in the main thread)
  227. const serverId = obj.serverId;
  228. obj.id = serverId;
  229. obj.destroyed = false;
  230. const serverObj = objects.objects.find(o => o.id === obj.id);
  231. const mapExists = mapList.mapList.some(m => m.name === newZone);
  232. if (mapExists) {
  233. serverObj.zoneName = newZone;
  234. obj.zoneName = newZone;
  235. } else {
  236. obj.zoneName = clientConfig.config.defaultZone;
  237. serverObj.zoneName = clientConfig.config.defaultZone;
  238. }
  239. delete serverObj.zoneId;
  240. delete obj.zoneId;
  241. const isRezone = true;
  242. await this.addObject(obj, keepPos, isRezone);
  243. },
  244. onZoneIdle: function (thread) {
  245. listenersOnZoneIdle.forEach(l => l(thread));
  246. }
  247. },
  248. returnWhenZonesIdle: async function () {
  249. return new Promise(res => {
  250. const waiting = [...this.threads];
  251. const onZoneIdle = thread => {
  252. waiting.spliceWhere(w => w === thread);
  253. if (waiting.length)
  254. return;
  255. listenersOnZoneIdle.spliceWhere(l => l === onZoneIdle);
  256. res();
  257. };
  258. listenersOnZoneIdle.push(onZoneIdle);
  259. this.threads.forEach(t => {
  260. t.worker.send({
  261. method: 'notifyOnceIdle'
  262. });
  263. });
  264. });
  265. },
  266. forceSavePlayer: async function (playerName, zoneId) {
  267. const thread = this.threads.find(t => t.id === zoneId);
  268. if (!thread)
  269. return;
  270. return new Promise(res => {
  271. const callbackId = this.registerCallback(res);
  272. thread.worker.send({
  273. method: 'forceSavePlayer',
  274. args: {
  275. playerName,
  276. callbackId
  277. }
  278. });
  279. });
  280. }
  281. };