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.
 
 
 

545 lines
11 KiB

  1. //System Imports
  2. const fs = require('fs');
  3. //Imports
  4. const phaseTemplate = require('./phases/phaseTemplate');
  5. const { mapList } = require('../world/mapManager');
  6. //Internals
  7. const phasePaths = [{
  8. type: 'end',
  9. path: './phases/phaseEnd'
  10. }];
  11. //Helpers
  12. const applyVariablesToDescription = (desc, variables) => {
  13. if (!variables)
  14. return desc;
  15. Object.entries(variables).forEach(e => {
  16. const [key, value] = e;
  17. desc = desc.split(`$${key}$`).join(value);
  18. });
  19. return desc;
  20. };
  21. //Exports
  22. module.exports = {
  23. configs: [],
  24. nextId: 0,
  25. init: function (instance) {
  26. this.instance = instance;
  27. this.instance.eventEmitter.emit('beforeGetEventPhasePaths', {
  28. phasePaths
  29. });
  30. const zoneName = this.instance.map.name;
  31. const zonePath = mapList.find(z => z.name === zoneName).path;
  32. const zoneEventPath = zonePath + '/' + zoneName + '/events';
  33. const paths = ['config/globalEvents', zoneEventPath];
  34. const files = [];
  35. paths.forEach(p => {
  36. if (!fs.existsSync(p))
  37. return;
  38. files.push(...fs.readdirSync(p).map(f => ('../' + p + '/' + f)));
  39. });
  40. this.instance.eventEmitter.emit('onBeforeGetEventList', zoneName, files);
  41. files.forEach(f => {
  42. if (!f.includes('.js'))
  43. return;
  44. const e = require(f);
  45. if (!e.disabled)
  46. this.configs.push(extend({}, e));
  47. }, this);
  48. },
  49. getEvent: function (name) {
  50. return this.configs.find(c => (c.name === name)).event.config;
  51. },
  52. setEventDescription: function (name, desc) {
  53. let config = this.getEvent(name);
  54. let event = config.event;
  55. if (!event)
  56. return;
  57. if (!config.oldDescription)
  58. config.oldDescription = config.description;
  59. if ((config.events) && (config.events.beforeSetDescription))
  60. config.events.beforeSetDescription(this);
  61. if (desc) {
  62. desc = applyVariablesToDescription(desc, event.variables);
  63. config.description = desc;
  64. }
  65. event.participators.forEach(p => p.events.syncList());
  66. },
  67. setEventRewards: function (name, rewards) {
  68. let config = this.getEvent(name);
  69. let event = config.event;
  70. if (!event)
  71. return;
  72. event.rewards = rewards;
  73. event.age = event.config.duration - 2;
  74. },
  75. setParticipantRewards: function (eventName, participantName, newRewards) {
  76. const { event: { rewards } } = this.getEvent(eventName);
  77. rewards[participantName] = newRewards;
  78. },
  79. addParticipantRewards: function (eventName, participantName, addRewards) {
  80. const { event: { rewards } } = this.getEvent(eventName);
  81. let pRewards = rewards[participantName];
  82. if (!pRewards) {
  83. pRewards = [];
  84. rewards[participantName] = pRewards;
  85. }
  86. if (!addRewards.push)
  87. addRewards = [ addRewards ];
  88. addRewards.forEach(r => {
  89. const { name, quantity = 1 } = r;
  90. const exists = pRewards.find(f => f.name === name);
  91. if (exists)
  92. exists.quantity = (exists.quantity || 1) + quantity;
  93. else
  94. pRewards.push(r);
  95. });
  96. },
  97. setWinText: function (name, text) {
  98. let config = this.getEvent(name);
  99. let event = config.event;
  100. if (!event)
  101. return;
  102. event.winText = text;
  103. },
  104. setEventVariable: function (eventName, variableName, value) {
  105. let config = this.getEvent(eventName);
  106. let event = config.event;
  107. if (!event)
  108. return;
  109. event.variables[variableName] = value;
  110. },
  111. incrementEventVariable: function (eventName, variableName, delta) {
  112. let config = this.getEvent(eventName);
  113. let event = config.event;
  114. if (!event)
  115. return;
  116. const currentValue = event.variables[variableName] || 0;
  117. event.variables[variableName] = currentValue + delta;
  118. },
  119. update: function () {
  120. let configs = this.configs;
  121. if (!configs)
  122. return;
  123. let scheduler = this.instance.scheduler;
  124. let cLen = configs.length;
  125. for (let i = 0; i < cLen; i++) {
  126. let c = configs[i];
  127. if (c.event) {
  128. this.updateEvent(c.event);
  129. const shouldStop = (
  130. c.event.done ||
  131. (
  132. c.cron &&
  133. c.durationEvent &&
  134. !scheduler.isActive(c)
  135. )
  136. );
  137. if (shouldStop)
  138. this.stopEvent(c);
  139. continue;
  140. } else if ((c.ttl) && (c.ttl > 0)) {
  141. c.ttl--;
  142. continue;
  143. } else if (c.cron) {
  144. if (c.durationEvent && !scheduler.isActive(c))
  145. continue;
  146. else if (!c.durationEvent && !scheduler.shouldRun(c))
  147. continue;
  148. } else if (c.manualTrigger)
  149. continue;
  150. c.event = this.startEvent(c);
  151. this.updateEvent(c.event);
  152. }
  153. },
  154. startEvent: function (config) {
  155. if (config.oldDescription)
  156. config.description = config.oldDescription;
  157. const event = {
  158. id: this.nextId++,
  159. config: extend({}, config),
  160. eventManager: this,
  161. variables: {},
  162. rewards: {},
  163. phases: [],
  164. participators: [],
  165. objects: [],
  166. nextPhase: 0,
  167. age: 0
  168. };
  169. event.config.event = event;
  170. const onStart = _.getDeepProperty(event, ['config', 'events', 'onStart']);
  171. if (onStart)
  172. onStart(this, event);
  173. return event;
  174. },
  175. startEventByCode: function (eventCode) {
  176. const config = this.configs.find(c => c.code === eventCode);
  177. if (!config || config.event)
  178. return;
  179. config.event = this.startEvent(config);
  180. this.updateEvent(config.event);
  181. this.instance.syncer.queue('onGetMessages', {
  182. messages: {
  183. class: 'color-pinkA',
  184. message: `The ${config.name} event has begun!`
  185. }
  186. }, -1);
  187. },
  188. stopEventByCode: function (eventCode) {
  189. const config = this.configs.find(c => c.code === eventCode);
  190. if (!config || !config.event)
  191. return;
  192. this.stopEvent(config);
  193. this.instance.syncer.queue('onGetMessages', {
  194. messages: {
  195. class: 'color-pinkA',
  196. message: `The ${config.name} event has come to an end!`
  197. }
  198. }, -1);
  199. },
  200. giveRewards: function (config) {
  201. const { event: { rewards = {} } } = config;
  202. const subject = `${config.name} Rewards`;
  203. const senderName = config.rewardSenderName;
  204. Object.entries(rewards).forEach(e => {
  205. const [ name, rList ] = e;
  206. if (!rList || !rList.length)
  207. return;
  208. //Hack: Mail is a mod. As such, events should be a mod that depends on mail
  209. if (global.mailManager) {
  210. global.mailManager.sendSystemMail({
  211. to: name,
  212. from: senderName,
  213. subject,
  214. msg: '',
  215. items: rList,
  216. notify: true
  217. });
  218. }
  219. });
  220. if ((config.events) && (config.events.afterGiveRewards))
  221. config.events.afterGiveRewards(this, config);
  222. },
  223. stopAll: function () {
  224. this.configs.forEach(c => {
  225. if (c.event)
  226. c.event.done = true;
  227. });
  228. },
  229. stopEvent: function (config) {
  230. let event = config.event;
  231. config.event.participators.forEach(function (p) {
  232. p.events.unregisterEvent(event);
  233. }, this);
  234. config.event.objects.forEach(function (o) {
  235. o.destroyed = true;
  236. this.instance.syncer.queue('onGetObject', {
  237. x: o.x,
  238. y: o.y,
  239. components: [{
  240. type: 'attackAnimation',
  241. row: 0,
  242. col: 4
  243. }]
  244. }, -1);
  245. }, this);
  246. if (event.winText) {
  247. this.instance.syncer.queue('serverModule', {
  248. module: 'cons',
  249. method: 'emit',
  250. msg: [
  251. 'event',
  252. {
  253. event: 'onGetMessages',
  254. data: {
  255. messages: {
  256. class: 'color-pinkA',
  257. message: event.winText
  258. }
  259. }
  260. }
  261. ]
  262. }, ['server']);
  263. }
  264. event.phases.forEach(function (p) {
  265. if ((p.destroy) && (!p.destroyed)) {
  266. p.destroyed = true;
  267. p.destroy();
  268. }
  269. });
  270. const onStop = _.getDeepProperty(event, ['config', 'events', 'onStop']);
  271. if (onStop)
  272. onStop(this, event);
  273. delete config.event;
  274. },
  275. handleNotification: function (event, { msg, desc, event: triggerEvent }) {
  276. if (msg) {
  277. this.instance.syncer.queue('serverModule', {
  278. module: 'cons',
  279. method: 'emit',
  280. msg: [
  281. 'event',
  282. {
  283. event: 'onGetMessages',
  284. data: {
  285. messages: {
  286. class: 'color-pinkA',
  287. message: msg
  288. }
  289. }
  290. }
  291. ]
  292. }, ['server']);
  293. }
  294. if (desc) {
  295. event.config.descTimer = desc;
  296. this.setEventDescription(event.config.name);
  297. }
  298. if (triggerEvent && event.config.events[triggerEvent])
  299. event.config.events[triggerEvent](this, event);
  300. },
  301. updateEvent: function (event) {
  302. const onTick = _.getDeepProperty(event, ['config', 'events', 'onTick']);
  303. if (onTick)
  304. onTick(this, event);
  305. let objects = event.objects;
  306. let oLen = objects.length;
  307. for (let i = 0; i < oLen; i++) {
  308. if (objects[i].destroyed) {
  309. objects.splice(i, 1);
  310. i--;
  311. oLen--;
  312. }
  313. }
  314. let currentPhases = event.phases;
  315. let cLen = currentPhases.length;
  316. let stillBusy = false;
  317. for (let i = 0; i < cLen; i++) {
  318. let phase = currentPhases[i];
  319. if (!phase.destroyed) {
  320. if (phase.end || (phase.endMark !== -1 && phase.endMark <= event.age)) {
  321. if ((phase.destroy) && (!phase.destroyed))
  322. phase.destroy();
  323. phase.destroyed = true;
  324. continue;
  325. } else {
  326. if (phase.has('ttl')) {
  327. if (phase.ttl === 0) {
  328. phase.end = true;
  329. continue;
  330. }
  331. phase.ttl--;
  332. stillBusy = true;
  333. } else if (!phase.auto)
  334. stillBusy = true;
  335. phase.update(event);
  336. }
  337. }
  338. }
  339. const notifications = event.config.notifications || [];
  340. notifications.forEach(n => {
  341. if (n.mark === event.age)
  342. this.handleNotification(event, n);
  343. });
  344. event.age++;
  345. if (event.age === event.config.duration)
  346. event.done = true;
  347. else if ((event.config.prizeTime) && (event.age === event.config.prizeTime))
  348. this.giveRewards(event.config);
  349. if (stillBusy)
  350. return;
  351. let config = event.config;
  352. let phases = config.phases;
  353. let pLen = phases.length;
  354. for (let i = event.nextPhase; i < pLen; i++) {
  355. let p = phases[i];
  356. let phase = event.phases[i];
  357. if (!phase) {
  358. let typeTemplate;
  359. const phasePathEntry = phasePaths.find(f => f.type === p.type);
  360. if (!phasePathEntry) {
  361. const phaseFile = 'phase' + p.type[0].toUpperCase() + p.type.substr(1);
  362. typeTemplate = require('./phases/' + phaseFile);
  363. } else
  364. typeTemplate = require(`../${phasePathEntry.path}`);
  365. phase = extend({
  366. instance: this.instance,
  367. event: event
  368. }, phaseTemplate, typeTemplate, p);
  369. event.phases.push(phase);
  370. event.currentPhase = phase;
  371. }
  372. event.nextPhase = i + 1;
  373. phase.init(event);
  374. if (!p.auto) {
  375. stillBusy = true;
  376. break;
  377. }
  378. }
  379. if ((event.nextPhase >= pLen) && (!stillBusy))
  380. event.done = true;
  381. let oList = this.instance.objects.objects;
  382. oLen = oList.length;
  383. for (let i = 0; i < oLen; i++) {
  384. let o = oList[i];
  385. if (!o.player)
  386. continue;
  387. o.events.events.afterMove.call(o.events);
  388. }
  389. },
  390. getCloseEvents: function (obj) {
  391. let x = obj.x;
  392. let y = obj.y;
  393. let configs = this.configs;
  394. if (!configs)
  395. return;
  396. let cLen = configs.length;
  397. let result = [];
  398. for (let i = 0; i < cLen; i++) {
  399. let event = configs[i].event;
  400. if (!event)
  401. continue;
  402. let exists = event.participators.find(p => (p.name === obj.name));
  403. if (exists) {
  404. event.participators.spliceWhere(p => (p === exists));
  405. event.participators.push(obj);
  406. result.push(event);
  407. continue;
  408. }
  409. let distance = event.config.distance;
  410. if (distance === -1) {
  411. event.participators.push(obj);
  412. result.push(event);
  413. if (event.config.events && event.config.events.onParticipantJoin)
  414. event.config.events.onParticipantJoin(this, obj);
  415. continue;
  416. }
  417. let objects = event.objects;
  418. let oLen = objects.length;
  419. for (let j = 0; j < oLen; j++) {
  420. let o = objects[j];
  421. if (
  422. (distance === -1) ||
  423. (!distance) ||
  424. (
  425. (Math.abs(x - o.x) < distance) &&
  426. (Math.abs(y - o.y) < distance)
  427. )
  428. ) {
  429. event.participators.push(obj);
  430. result.push(event);
  431. if (event.config.events && event.config.events.onParticipantJoin)
  432. event.config.events.onParticipantJoin(this, obj);
  433. break;
  434. }
  435. }
  436. }
  437. return result;
  438. }
  439. };