import {compose, merge} from '@avocado/core'; import {Vector} from '@avocado/math'; import {StateProperty, Trait} from '../trait'; import {Entity} from '..'; const decorate = compose( StateProperty('isSpawning', { track: true, }), StateProperty('maxSpawns'), ); export default class Spawner extends decorate(Trait) { static behaviorTypes() { return { killAllChildren: { type: 'void', label: 'Kill all spawned children', }, spawn: { cycle: true, type: 'void|entity', label: 'Spawn $1 with $2 extensions.', args: [ ['key', { type: 'string', options: (entity) => this.optionsForSpawn(entity), }], ['json', { type: 'object', }], ], }, spawnAt: { cycle: true, type: 'void|entity', label: 'Spawn $1 as $2 with $3 extensions.', args: [ ['key', { type: 'string', options: (entity) => this.optionsForSpawn(entity), }], ['position', { type: 'vector', }], ['json', { type: 'object', }], ], }, spawnRaw: { cycle: true, type: 'void|entity', label: 'Spawn $1.', args: [ ['json', { type: 'object', }], ] }, spawnRawAt: { cycle: true, type: 'void|entity', label: 'Spawn $1 at $2.', args: [ ['position', { type: 'vector', }], ['json', { type: 'object', }], ] }, }; } static defaultParams() { return { spawns: {}, }; } static describeParams() { return { spawns: { type: 'object', label: 'Entities that may be spawned', }, }; } static defaultState() { return { isSpawning: true, maxSpawns: Infinity, }; } static describeState() { return { isSpawning: { type: 'bool', label: 'Is spawning', }, maxSpawns: { type: 'number', label: 'Maximum concurrent spawns', }, }; } static optionsForSpawn(entity) { if (!entity) { return undefined; } return Object.keys(entity.traitInstance('spawner').params.spawns) .reduce((r, key) => ({...r, [key]: key}), {}); } static type() { return 'spawner'; } destroy() { while (this.children.length > 0) { const child = this.children.pop(); if (child) { this.removeChild(child); } } } constructor(entity, params, state) { super(entity, params, state); this.children = []; this.childrenListeners = new Map(); this.spawnJSONs = this.params.spawns; } augmentJSONWithPosition(json, position) { if (!json.traits) { json.traits = {}; } if (!json.traits.positioned) { json.traits.positioned = {}; } if (!json.traits.positioned.state) { json.traits.positioned.state = {}; } json.traits.positioned.state.x = position[0]; json.traits.positioned.state.y = position[1]; return json; } destinationEntityList() { if ( this.entity.is('listed') && this.entity.list ) { return this.entity.list; } if ( this.entity.wielder && this.entity.wielder.is('listed') && this.entity.wielder.list ) { return this.entity.wielder.list; } } maySpawn() { if (this.maxSpawns <= this.children.length) { return false; } if (!this.destinationEntityList()) { return false; } return true; } removeChild(child) { const index = this.children.indexOf(child); if (-1 !== index) { this.children.splice(index, 1); const listener = this.childrenListeners.get(child); child.off('destroy', listener); this.childrenListeners.delete(child); } } listeners() { return { isDyingChanged: (_, isDying) => { this.isSpawning = !isDying; }, }; } methods() { return { killAllChildren: () => { // Juggle children since this may cause splices and mess up the array. const children = this.children.slice(0); for (let i = 0; i < children.length; i++) { children[i].destroyGently(); } }, spawn: (key, json = {}) => { if (!this.maySpawn()) { return; } const spawnJSON = this.spawnJSONs[key]; if (!spawnJSON) { return; } return this.entity.spawnRaw(merge({}, spawnJSON, json)); }, spawnAt: (key, position, json = {}) => { if (!this.maySpawn()) { return; } json = this.augmentJSONWithPosition(json, position); return this.entity.spawn(key, json); }, spawnRaw: (json) => { if (!this.maySpawn()) { return; } // Add null to children to prevent race. const childIndex = this.children.length; this.children.push(null); const list = this.destinationEntityList(); return Entity.loadOrInstance(json).then((child) => { this.children[childIndex] = child; // Listen for destroy event. const listener = this.removeChild.bind(this, child); this.childrenListeners.set(child, listener); child.once('destroy', listener); // Add child to list. list.addEntity(child); return child; }); }, spawnRawAt: (position, json = {}) => { if (!this.maySpawn()) { return; } json = this.augmentJSONWithPosition(json, position); return this.entity.spawnRaw(json); }, }; } }