//System Imports const fs = require('fs'); //Imports const phaseTemplate = require('./phases/phaseTemplate'); const { mapList } = require('../world/mapManager'); //Helpers const applyVariablesToDescription = (desc, variables) => { if (!variables) return desc; Object.entries(variables).forEach(e => { const [key, value] = e; desc = desc.split(`$${key}$`).join(value); }); return desc; }; //Exports module.exports = { configs: [], nextId: 0, init: function (instance) { this.instance = instance; const zoneName = this.instance.map.name; const zonePath = mapList.find(z => z.name === zoneName).path; const zoneEventPath = zonePath + '/' + zoneName + '/events'; const paths = ['config/globalEvents', zoneEventPath]; const files = []; paths.forEach(p => { if (!fs.existsSync(p)) return; files.push(...fs.readdirSync(p).map(f => ('../' + p + '/' + f))); }); this.instance.eventEmitter.emit('onBeforeGetEventList', zoneName, files); files.forEach(f => { if (!f.includes('.js')) return; const e = require(f); if (!e.disabled) this.configs.push(extend({}, e)); }, this); }, getEvent: function (name) { return this.configs.find(c => (c.name === name)).event.config; }, setEventDescription: function (name, desc) { let config = this.getEvent(name); let event = config.event; if (!event) return; if (!config.oldDescription) config.oldDescription = config.description; if ((config.events) && (config.events.beforeSetDescription)) config.events.beforeSetDescription(this); if (desc) { desc = applyVariablesToDescription(desc, event.variables); config.description = desc; } event.participators.forEach(p => p.events.syncList()); }, setEventRewards: function (name, rewards) { let config = this.getEvent(name); let event = config.event; if (!event) return; event.rewards = rewards; event.age = event.config.duration - 2; }, setParticipantRewards: function (eventName, participantName, newRewards) { const { event: { rewards } } = this.getEvent(eventName); rewards[participantName] = newRewards; }, addParticipantRewards: function (eventName, participantName, addRewards) { const { event: { rewards } } = this.getEvent(eventName); let pRewards = rewards[participantName]; if (!pRewards) { pRewards = []; rewards[participantName] = pRewards; } if (!addRewards.push) addRewards = [ addRewards ]; addRewards.forEach(r => { const { name, quantity = 1 } = r; const exists = pRewards.find(f => f.name === name); if (exists) exists.quantity = (exists.quantity || 1) + quantity; else pRewards.push(r); }); }, setWinText: function (name, text) { let config = this.getEvent(name); let event = config.event; if (!event) return; event.winText = text; }, setEventVariable: function (eventName, variableName, value) { let config = this.getEvent(eventName); let event = config.event; if (!event) return; event.variables[variableName] = value; }, incrementEventVariable: function (eventName, variableName, delta) { let config = this.getEvent(eventName); let event = config.event; if (!event) return; const currentValue = event.variables[variableName] || 0; event.variables[variableName] = currentValue + delta; }, update: function () { let configs = this.configs; if (!configs) return; let scheduler = this.instance.scheduler; let cLen = configs.length; for (let i = 0; i < cLen; i++) { let c = configs[i]; if (c.event) { this.updateEvent(c.event); const shouldStop = ( c.event.done || ( c.cron && c.durationEvent && !scheduler.isActive(c) ) ); if (shouldStop) this.stopEvent(c); continue; } else if ((c.ttl) && (c.ttl > 0)) { c.ttl--; continue; } else if (c.cron) { if (c.durationEvent && !scheduler.isActive(c)) continue; else if (!c.durationEvent && !scheduler.shouldRun(c)) continue; } else if (c.manualTrigger) continue; c.event = this.startEvent(c); this.updateEvent(c.event); } }, startEvent: function (config) { if (config.oldDescription) config.description = config.oldDescription; const event = { id: this.nextId++, config: extend({}, config), eventManager: this, variables: {}, rewards: {}, phases: [], participators: [], objects: [], nextPhase: 0, age: 0 }; event.config.event = event; const onStart = _.getDeepProperty(event, ['config', 'events', 'onStart']); if (onStart) onStart(this, event); return event; }, startEventByCode: function (eventCode) { const config = this.configs.find(c => c.code === eventCode); if (!config || config.event) return; config.event = this.startEvent(config); this.updateEvent(config.event); this.instance.syncer.queue('onGetMessages', { messages: { class: 'color-pinkA', message: `The ${config.name} event has begun!` } }, -1); }, stopEventByCode: function (eventCode) { const config = this.configs.find(c => c.code === eventCode); if (!config || !config.event) return; this.stopEvent(config); this.instance.syncer.queue('onGetMessages', { messages: { class: 'color-pinkA', message: `The ${config.name} event has come to an end!` } }, -1); }, giveRewards: function (config) { const { event: { rewards = {} } } = config; const subject = `${config.name} Rewards`; const senderName = config.rewardSenderName; Object.entries(rewards).forEach(e => { const [ name, rList ] = e; if (!rList || !rList.length) return; //Hack: Mail is a mod. As such, events should be a mod that depends on mail if (global.mailManager) { global.mailManager.sendSystemMail({ to: name, from: senderName, subject, msg: '', items: rList, notify: true }); } }); if ((config.events) && (config.events.afterGiveRewards)) config.events.afterGiveRewards(this, config); }, stopAll: function () { this.configs.forEach(c => { if (c.event) c.event.done = true; }); }, stopEvent: function (config) { let event = config.event; config.event.participators.forEach(function (p) { p.events.unregisterEvent(event); }, this); config.event.objects.forEach(function (o) { o.destroyed = true; this.instance.syncer.queue('onGetObject', { x: o.x, y: o.y, components: [{ type: 'attackAnimation', row: 0, col: 4 }] }, -1); }, this); if (event.winText) { this.instance.syncer.queue('serverModule', { module: 'cons', method: 'emit', msg: [ 'event', { event: 'onGetMessages', data: { messages: { class: 'color-pinkA', message: event.winText } } } ] }, ['server']); } event.phases.forEach(function (p) { if ((p.destroy) && (!p.destroyed)) { p.destroyed = true; p.destroy(); } }); const onStop = _.getDeepProperty(event, ['config', 'events', 'onStop']); if (onStop) onStop(this, event); delete config.event; }, handleNotification: function (event, { msg, desc, event: triggerEvent }) { if (msg) { this.instance.syncer.queue('serverModule', { module: 'cons', method: 'emit', msg: [ 'event', { event: 'onGetMessages', data: { messages: { class: 'color-pinkA', message: msg } } } ] }, ['server']); } if (desc) { event.config.descTimer = desc; this.setEventDescription(event.config.name); } if (triggerEvent && event.config.events[triggerEvent]) event.config.events[triggerEvent](this, event); }, updateEvent: function (event) { const onTick = _.getDeepProperty(event, ['config', 'events', 'onTick']); if (onTick) onTick(this, event); let objects = event.objects; let oLen = objects.length; for (let i = 0; i < oLen; i++) { if (objects[i].destroyed) { objects.splice(i, 1); i--; oLen--; } } let currentPhases = event.phases; let cLen = currentPhases.length; let stillBusy = false; for (let i = 0; i < cLen; i++) { let phase = currentPhases[i]; if (!phase.destroyed) { if (phase.end || (phase.endMark !== -1 && phase.endMark <= event.age)) { if ((phase.destroy) && (!phase.destroyed)) phase.destroy(); phase.destroyed = true; continue; } else { if (phase.has('ttl')) { if (phase.ttl === 0) { phase.end = true; continue; } phase.ttl--; stillBusy = true; } else if (!phase.auto) stillBusy = true; phase.update(event); } } } const notifications = event.config.notifications || []; notifications.forEach(n => { if (n.mark === event.age) this.handleNotification(event, n); }); event.age++; if (event.age === event.config.duration) event.done = true; else if ((event.config.prizeTime) && (event.age === event.config.prizeTime)) this.giveRewards(event.config); if (stillBusy) return; let config = event.config; let phases = config.phases; let pLen = phases.length; for (let i = event.nextPhase; i < pLen; i++) { let p = phases[i]; let phase = event.phases[i]; if (!phase) { let phaseFile = 'phase' + p.type[0].toUpperCase() + p.type.substr(1); let typeTemplate = require('./phases/' + phaseFile); phase = extend({ instance: this.instance, event: event }, phaseTemplate, typeTemplate, p); event.phases.push(phase); event.currentPhase = phase; } event.nextPhase = i + 1; phase.init(event); if (!p.auto) { stillBusy = true; break; } } if ((event.nextPhase >= pLen) && (!stillBusy)) event.done = true; let oList = this.instance.objects.objects; oLen = oList.length; for (let i = 0; i < oLen; i++) { let o = oList[i]; if (!o.player) continue; o.events.events.afterMove.call(o.events); } }, getCloseEvents: function (obj) { let x = obj.x; let y = obj.y; let configs = this.configs; if (!configs) return; let cLen = configs.length; let result = []; for (let i = 0; i < cLen; i++) { let event = configs[i].event; if (!event) continue; let exists = event.participators.find(p => (p.name === obj.name)); if (exists) { event.participators.spliceWhere(p => (p === exists)); event.participators.push(obj); result.push(event); continue; } let distance = event.config.distance; if (distance === -1) { event.participators.push(obj); result.push(event); if (event.config.events && event.config.events.onParticipantJoin) event.config.events.onParticipantJoin(this, obj); continue; } let objects = event.objects; let oLen = objects.length; for (let j = 0; j < oLen; j++) { let o = objects[j]; if ( (distance === -1) || (!distance) || ( (Math.abs(x - o.x) < distance) && (Math.abs(y - o.y) < distance) ) ) { event.participators.push(obj); result.push(event); if (event.config.events && event.config.events.onParticipantJoin) event.config.events.onParticipantJoin(this, obj); break; } } } return result; } };