let components = require('../components/components'); module.exports = { components: [], actionQueue: [], eventListeners: [], addComponent: function (type, blueprint, isTransfer) { let cpn = this[type]; if (!cpn) { let template = components.components[type]; if (!template) { template = extend({ type: type }, blueprint || {}); } cpn = extend({}, template); cpn.obj = this; this.components.push(cpn); this[cpn.type] = cpn; } if (cpn.init && this.has('instance')) cpn.init(blueprint || {}, isTransfer); else { for (let p in blueprint) cpn[p] = blueprint[p]; } return cpn; }, addBuiltComponent: function (cpn) { this[cpn.type] = cpn; cpn.obj = this; this.components.push(cpn); return cpn; }, removeComponent: function (type) { let cpn = this[type]; if (!cpn) return; cpn.destroyed = true; }, extendComponent: function (ext, type, blueprint) { let template = require('../components/extensions/' + type); let cpn = this[ext]; extend(cpn, template); if (template.init) cpn.init(blueprint); return cpn; }, update: function () { let usedTurn = false; let cpns = this.components; let len = cpns.length; for (let i = 0; i < len; i++) { let c = cpns[i]; if (c.destroyed) { this.syncer.setSelfArray(false, 'removeComponents', c.type); cpns.spliceWhere(f => (f === c)); delete this[c.type]; len--; i--; } else if (c.update) { if (c.update()) usedTurn = true; } } if (!usedTurn) this.performQueue(); }, getSimple: function (self, isSave, isTransfer) { let s = this.simplify(null, self, isSave, isTransfer); if (self && !isSave && this.syncer) { this.syncer.oSelf.components .forEach(c => { if (!this[c.type]) s.components.push(c); }); } return s; }, simplify: function (o, self, isSave, isTransfer) { let result = {}; if (!o) { result.components = []; o = this; } const syncTypes = ['portrait', 'area', 'filters']; const ignoreKeysWhenNotSelf = ['account']; for (let p in o) { let value = o[p]; if (value === null) continue; let type = typeof (value); if (type === 'function') continue; else if (type !== 'object') { if (self || !ignoreKeysWhenNotSelf.includes(p)) result[p] = value; } else if (type === 'undefined') continue; else { if (value.type) { if (!value.simplify) { if (self) { result.components.push({ type: value.type }); } } else { let component = null; if (isSave && value.save) component = value.save(); else if (isTransfer && value.simplifyTransfer) component = value.simplifyTransfer(); else component = value.simplify(self); if (value.destroyed) { if (!component) { component = { type: value.type }; } component.destroyed = true; } if (component) result.components.push(component); } } else if (syncTypes.includes(p)) result[p] = value; continue; } } return result; }, sendEvent: function (event, data) { process.send({ method: 'event', id: this.serverId, data: { event: event, data: data } }); }, queue: function (msg) { const { action, auto, data: { priority } } = msg; if (action === 'spell') { let spellbook = this.spellbook; const isCasting = spellbook.isCasting(); if (isCasting && (!priority || !spellbook.canCast(msg))) { if (auto) spellbook.queueAuto(msg); return; } if (isCasting) spellbook.stopCasting(); this.actionQueue.spliceWhere(a => a.priority); this.actionQueue.splice(0, 0, msg); } else { if (priority) { this.spellbook.stopCasting(); this.actionQueue.splice(0, 0, msg); return; } this.actionQueue.push(msg); } }, dequeue: function () { if (this.actionQueue.length === 0) return null; return this.actionQueue.splice(0, 1)[0]; }, clearQueue: function () { if (this.has('serverId')) { this.instance.syncer.queue('onClearQueue', { id: this.id }, [this.serverId]); } this.actionQueue = []; this.fireEvent('clearQueue'); }, performAction: function (action) { if (action.instanceModule) return; let cpn = this[action.cpn]; if (!cpn) return; cpn[action.method](action.data); }, performQueue: function () { let q = this.dequeue(); if (!q) return; if (q.action === 'move') { let maxDistance = 1; if ((this.actionQueue[0]) && (this.actionQueue[0].action === 'move')) { let moveEvent = { sprintChance: this.stats.values.sprintChance || 0 }; this.fireEvent('onBeforeTryMove', moveEvent); let physics = this.instance.physics; let sprintChance = moveEvent.sprintChance; do { if ((~~(Math.random() * 100) < sprintChance) && (!physics.isTileBlocking(q.data.x, q.data.y))) { q = this.dequeue(); maxDistance++; } sprintChance -= 100; } while (sprintChance > 0 && this.actionQueue.length > 0); } q.maxDistance = maxDistance; let success = this.performMove(q); if (!success) this.clearQueue(); } else if (q.action === 'spell') { let success = this.spellbook.cast(q.data); if (!success) this.performQueue(); } }, performMove: function (action) { const { x: xOld, y: yOld, syncer, aggro, instance: { physics } } = this; const { maxDistance = 1, force, data } = action; const { x: xNew, y: yNew } = data; if (!force) { if (physics.isTileBlocking(data.x, data.y)) return true; data.success = true; this.fireEvent('beforeMove', data); if (data.success === false) { action.priority = true; this.queue(action); return true; } let deltaX = Math.abs(xOld - xNew); let deltaY = Math.abs(yOld - yNew); if ( ( (deltaX > maxDistance) || (deltaY > maxDistance) ) || ( (deltaX === 0) && (deltaY === 0) ) ) return false; } this.x = xNew; this.y = yNew; if (physics.addObject(this, xNew, yNew, xOld, yOld)) physics.removeObject(this, xOld, yOld, xNew, yNew); else { this.x = xOld; this.y = yOld; return false; } //We can't use xNew and yNew because addObject could have changed the position (like entering a building interior with stairs) syncer.o.x = this.x; syncer.o.y = this.y; if (aggro) aggro.move(); this.fireEvent('afterMove'); return true; }, collisionEnter: function (obj) { let cpns = this.components; let cLen = cpns.length; for (let i = 0; i < cLen; i++) { let c = cpns[i]; if (c.collisionEnter) { if (c.collisionEnter(obj)) return true; } } }, collisionExit: function (obj) { let cpns = this.components; let cLen = cpns.length; for (let i = 0; i < cLen; i++) { let c = cpns[i]; if (c.collisionExit) c.collisionExit(obj); } }, onEvent: function (eventName, callback) { const entry = { eventName, callback }; this.eventListeners.push(entry); return this.offEvent.bind(this, entry); }, offEvent: function (entry) { this.eventListeners.spliceWhere(e => e === entry); }, fireEvent: function (event) { let args = [].slice.call(arguments, 1); let cpns = this.components; let cLen = cpns.length; for (let i = 0; i < cLen; i++) { let cpn = cpns[i]; if (cpn.fireEvent) cpn.fireEvent(event, args); let events = cpn.events; if (!events) continue; let callback = events[event]; if (!callback) continue; callback.apply(cpn, args); } this.eventListeners.forEach(l => { const { eventName, callback } = l; if (eventName !== event) return; callback.apply(null, args); }); }, destroy: function () { let cpns = this.components; let len = cpns.length; for (let i = 0; i < len; i++) { let c = cpns[i]; if (c.destroy) c.destroy(); } }, toString: function () { let res = {}; for (let p in this) { if (['components', 'syncer'].includes(p)) continue; let val = this[p]; let stringVal = (val && val.toString) ? val.toString() : val; const type = typeof(val); if ( type !== 'function' && ( type !== 'object' || val.type ) ) res[p] = stringVal; } return JSON.stringify(res, null, 4).split('"').join('') + '\r\n'; } };