From 1ba411a690b9770937d1fb8a7a6e9419cb5d32fe Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 3 Jan 2021 00:57:51 -0600 Subject: [PATCH] flow --- .../behavior/src/compilers/expressions.js | 2 +- packages/entity/src/entity.js | 52 +------- packages/entity/src/traits/alive.js | 122 ++++++++++-------- packages/entity/test/alive.js | 118 +++++++++++++++++ packages/entity/test/directional.js | 31 +++++ packages/entity/test/entity.js | 66 ---------- packages/entity/test/existent.js | 31 +++++ packages/entity/test/listed.js | 31 +++++ packages/entity/test/mobile.js | 31 +++++ packages/entity/test/perishable.js | 31 +++++ packages/entity/test/positioned.js | 31 +++++ packages/entity/test/spawner.js | 31 +++++ packages/entity/yarn.lock | 8 +- 13 files changed, 408 insertions(+), 177 deletions(-) create mode 100644 packages/entity/test/alive.js create mode 100644 packages/entity/test/directional.js create mode 100644 packages/entity/test/existent.js create mode 100644 packages/entity/test/listed.js create mode 100644 packages/entity/test/mobile.js create mode 100644 packages/entity/test/perishable.js create mode 100644 packages/entity/test/positioned.js create mode 100644 packages/entity/test/spawner.js diff --git a/packages/behavior/src/compilers/expressions.js b/packages/behavior/src/compilers/expressions.js index 940c259..72fec68 100644 --- a/packages/behavior/src/compilers/expressions.js +++ b/packages/behavior/src/compilers/expressions.js @@ -1,4 +1,4 @@ -import compile from './expression'; +import compile from './compile'; export default (latus) => ({expressions}) => () => ( expressions.map((expression) => compile(expression, latus)) diff --git a/packages/entity/src/entity.js b/packages/entity/src/entity.js index f1fcd9a..d43caa7 100644 --- a/packages/entity/src/entity.js +++ b/packages/entity/src/entity.js @@ -55,10 +55,6 @@ export default (latus) => class Entity extends decorate(JsonResource) { this.position = [0, 0]; this.room = null; this.visibleAabb = [0, 0, 0, 0]; - // Fast path for instance. - // if ('undefined' !== typeof json) { - // this.fromJSON(merge(json, jsonext)); - // } this.instanceUuid = instanceUuid || this.numericId; this.addTraits(traits); } @@ -87,12 +83,10 @@ export default (latus) => class Entity extends decorate(JsonResource) { } addTrait([Trait, json]) { - // const Trait = trait.constructor; if (this.is(Trait.type)) { debug(`Tried to add trait "${Trait.type}" when it already exists!`); return; } - // const {fromType: {[type]: Trait}} = traits(latus); if (!Trait) { debug(`Tried to add trait "${Trait.type}" which isn't registered!`); return; @@ -112,7 +106,6 @@ export default (latus) => class Entity extends decorate(JsonResource) { // Instantiate. // eslint-disable-next-line no-param-reassign json.entity = this; - // const instance = new Trait(json); const trait = new Trait(json); // Proxy properties. defineTraitAccessors(Trait.prototype, this, trait); @@ -135,7 +128,7 @@ export default (latus) => class Entity extends decorate(JsonResource) { this._hooks[key] = this._hooks[key] || []; this._hooks[key].push({ fn, - type: Trait.type(), + type: Trait.type, }); } // Track trait. @@ -157,11 +150,6 @@ export default (latus) => class Entity extends decorate(JsonResource) { for (let i = 0; i < traits.length; i++) { this.addTrait(traits[i]); } - // const entries = Object.entries(traits); - // for (let i = 0; i < entries.length; i++) { - // const [type, trait] = entries[i]; - // this.addTrait(type, trait); - // } } allTraitInstances() { @@ -182,35 +170,10 @@ export default (latus) => class Entity extends decorate(JsonResource) { this._fastDirtyCheck = false; } - // fromJSON(json) { - // super.fromJSON(json); - // if (json.instanceUuid) { - // this.instanceUuid = json.instanceUuid; - // } - // this.addTraits(json.traits); - // return this; - // } - - // hydrate() { - // if (!this._hydrationPromise) { - // const promises = []; - // for (let i = 0; i < this._traitsFlat.length; i++) { - // promises.push(this._traitsFlat[i].hydrate()); - // } - // this._hydrationPromise = Promise.all(promises); - // this._hydrationPromise.then(() => { - // this.tick(0); - // }); - // } - // return this._hydrationPromise; - // } - static async extendJson(json) { const extended = await super.extendJson(json); - // const {fromResourceType: {EntityList, Tileset}} = resource(latus); if (extended.traits) { const entries = Object.entries(extended.traits); - // extended.traits = []; const promises = entries.map(async ([type, json]) => { const {fromType: {[type]: Trait}} = traits(latus); if (!Trait) { @@ -220,20 +183,7 @@ export default (latus) => class Entity extends decorate(JsonResource) { return [Trait, await Trait.extendJson(json)]; }); extended.traits = (await Promise.all(promises)).filter((entry) => !!entry); - // for (let i = 0; i < entries.length; i++) { - // const [type, json] = entries[i]; - // const {fromType: {[type]: Trait}} = traits(latus); - // if (!Trait) { - // debug(`Tried to add trait "${type}" which isn't registered!`); - // } - // else { - // extended.traits.push(await Trait.load(json)); - // } - // } } - // if (extended.tilesetUri) { - // extended.tileset = await Tileset.load(extended.tilesetUri); - // } return extended; } diff --git a/packages/entity/src/traits/alive.js b/packages/entity/src/traits/alive.js index 453729f..272e94f 100644 --- a/packages/entity/src/traits/alive.js +++ b/packages/entity/src/traits/alive.js @@ -9,6 +9,10 @@ import { import {StateProperty, Trait} from '@avocado/traits'; import {compose} from '@latus/core'; +const { + SIDE, +} = process.env; + const decorate = compose( StateProperty('isDying', { track: true, @@ -23,6 +27,31 @@ const decorate = compose( export default (latus) => class Alive extends decorate(Trait) { + constructor(...args) { + super(...args); + this._context = new Context( + { + entity: [this.entity, 'entity'], + }, + latus, + ); + this._deathActions = new Actions(compile(this.params.deathActions, latus)); + this._deathCondition = compile(this.params.deathCondition, latus); + } + + acceptPacket(packet) { + switch (packet.constructor.name) { + case 'DiedPacket': + this.entity.forceDeath(); + break; + case 'TraitUpdateAlivePacket': + this.entity.life = packet.data.life; + this.entity.maxLife = packet.data.maxLife; + break; + default: + } + } + static behaviorTypes() { return { deathSound: { @@ -36,6 +65,10 @@ export default (latus) => class Alive extends decorate(Trait) { }; } + get deathSound() { + return this.params.deathSound; + } + static defaultParams() { const playDeathSound = buildInvoke(['entity', 'playSound'], [ buildExpression(['entity', 'deathSound']), @@ -107,77 +140,38 @@ export default (latus) => class Alive extends decorate(Trait) { }; } - constructor(...args) { - super(...args); - this._context = new Context( - { - entity: [this.entity, 'entity'], - }, - latus, - ); - this._deathActions = new Actions(compile(this.params.deathActions, latus)); - this._deathCondition = compile(this.params.deathCondition, latus); - } - destroy() { this._context.destroy(); } - acceptPacket(packet) { - switch (packet.constructor.name) { - case 'DiedPacket': - this.entity.forceDeath(); - break; - case 'TraitUpdateAlivePacket': - this.entity.life = packet.data.life; - this.entity.maxLife = packet.data.maxLife; - break; - default: - } + get life() { + return super.life; } - get deathSound() { - return this.params.deathSound; - } - - packets() { - const packets = []; - const {isDying, life, maxLife} = this.stateDifferences(); - if (life || maxLife) { - packets.push([ - 'TraitUpdateAlivePacket', - { - life: this.state.life, - maxLife: this.state.maxLife, - }, - ]); - } - if (isDying) { - packets.push(['DiedPacket']); - } - return packets; + set life(life) { + // Clamp health between 0 and max. + super.life = Math.min(Math.max(0, life), this.entity.maxLife); } listeners() { return { tookHarm: (harm) => { - if (harm.isHarm) { - this.entity.life -= harm.amount; - } - else { - this.entity.life += harm.amount; - } - // Clamp health between 0 and max. - this.entity.life = Math.min( - Math.max(0, this.entity.life), - this.entity.maxLife, - ); + this.entity.life += (harm.isHarm ? -1 : 1) * harm.amount; }, }; } + get maxLife() { + return super.maxLife; + } + + set maxLife(maxLife) { + super.maxLife = maxLife; + this.entity.life = super.life; + } + methods() { return { @@ -194,8 +188,26 @@ export default (latus) => class Alive extends decorate(Trait) { }; } + packets() { + const packets = []; + const {isDying, life, maxLife} = this.stateDifferences(); + if (life || maxLife) { + packets.push([ + 'TraitUpdateAlive', + { + life: this.state.life, + maxLife: this.state.maxLife, + }, + ]); + } + if (isDying) { + packets.push(['Died']); + } + return packets; + } + tick() { - if (AVOCADO_SERVER) { + if ('client' !== SIDE) { if (!this.entity.isDying && this._deathCondition(this._context)) { this.entity.forceDeath(); } diff --git a/packages/entity/test/alive.js b/packages/entity/test/alive.js new file mode 100644 index 0000000..96c4311 --- /dev/null +++ b/packages/entity/test/alive.js @@ -0,0 +1,118 @@ +import { + buildCondition, + buildExpression, + buildInvoke, +} from '@avocado/behavior'; +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + '@avocado/behavior', + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + '@latus/socket', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Alive', () => { + let entity; + beforeEach(async () => { + entity = await Entity.load({ + traits: { + Alive: {}, + }, + }); + }); + it('exists', async () => { + expect(entity.is('Alive')).to.equal(true); + }); + it('is alive', async () => { + expect(entity.isDying).to.equal(false); + }); + it('can die', async () => { + entity.life = 0; + entity.tick(0); + expect(entity.isDying).to.equal(true); + }); + it('clamps life', async () => { + entity.life = 120; + expect(entity.life).to.equal(100); + entity.maxLife = 50; + expect(entity.life).to.equal(50); + }); + it('can have a custom death condition', async () => { + const entity = await Entity.load({ + traits: { + Alive: { + params: { + deathCondition: buildCondition('<=', [ + buildExpression(['entity', 'life']), + 10, + ]), + }, + }, + }, + }); + entity.tick(0); + expect(entity.isDying).to.equal(false); + entity.life = 10; + entity.tick(0); + expect(entity.isDying).to.equal(true); + }); + it('runs actions on death', async () => { + let didActions; + const entity = await Entity.load({ + traits: { + Alive: { + params: { + deathActions: { + type: 'expressions', + expressions: [ + buildInvoke(['entity', 'ded']), + ], + }, + }, + }, + }, + }); + entity.ded = () => { + didActions = true; + }; + const handle = setInterval(() => { + entity.tick(); + }, 16.66); + await entity.forceDeath(); + clearInterval(handle); + expect(didActions).to.equal(true); + }); + describe('Packets', () => { + it('generates life packets', async () => { + entity.life = 80; + entity.maxLife = 90; + const packets = entity.traitInstance('Alive').packets(); + expect(packets).to.have.lengthOf(1); + expect(packets[0][0]).to.equal('TraitUpdateAlive'); + expect(packets[0][1]).to.deep.equal({life: 80, maxLife: 90}); + }); + it('generates death packets', async () => { + entity.life = 0; + entity.tick(); + const packets = entity.traitInstance('Alive').packets(); + expect(packets).to.have.lengthOf(2); + expect(packets[0][0]).to.equal('TraitUpdateAlive'); + expect(packets[1][0]).to.equal('Died'); + }); + }); + }); + }); +}); diff --git a/packages/entity/test/directional.js b/packages/entity/test/directional.js new file mode 100644 index 0000000..2664218 --- /dev/null +++ b/packages/entity/test/directional.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Directional', () => { + it('is Directional', async () => { + const entity = await Entity.load({ + traits: { + Directional: {}, + }, + }); + expect(entity.is('Directional')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/entity.js b/packages/entity/test/entity.js index c84ed88..da8d4e1 100644 --- a/packages/entity/test/entity.js +++ b/packages/entity/test/entity.js @@ -57,70 +57,4 @@ describe(name, () => { }); expect(Date.now() - start).to.be.at.least(DELAY); }); - describe('Traits', () => { - it('is Alive', async () => { - const entity = await Entity.load({ - traits: { - Alive: {}, - }, - }); - expect(entity.is('Alive')).to.equal(true); - }); - it('is Directional', async () => { - const entity = await Entity.load({ - traits: { - Directional: {}, - }, - }); - expect(entity.is('Directional')).to.equal(true); - }); - it('is Existent', async () => { - const entity = await Entity.load({ - traits: { - Existent: {}, - }, - }); - expect(entity.is('Existent')).to.equal(true); - }); - it('is Listed', async () => { - const entity = await Entity.load({ - traits: { - Listed: {}, - }, - }); - expect(entity.is('Listed')).to.equal(true); - }); - it('is Mobile', async () => { - const entity = await Entity.load({ - traits: { - Mobile: {}, - }, - }); - expect(entity.is('Mobile')).to.equal(true); - }); - it('is Perishable', async () => { - const entity = await Entity.load({ - traits: { - Perishable: {}, - }, - }); - expect(entity.is('Perishable')).to.equal(true); - }); - it('is Positioned', async () => { - const entity = await Entity.load({ - traits: { - Positioned: {}, - }, - }); - expect(entity.is('Positioned')).to.equal(true); - }); - it('is Spawner', async () => { - const entity = await Entity.load({ - traits: { - Spawner: {}, - }, - }); - expect(entity.is('Spawner')).to.equal(true); - }); - }); }); diff --git a/packages/entity/test/existent.js b/packages/entity/test/existent.js new file mode 100644 index 0000000..a182cdd --- /dev/null +++ b/packages/entity/test/existent.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Existent', () => { + it('is Existent', async () => { + const entity = await Entity.load({ + traits: { + Existent: {}, + }, + }); + expect(entity.is('Existent')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/listed.js b/packages/entity/test/listed.js new file mode 100644 index 0000000..33c98d5 --- /dev/null +++ b/packages/entity/test/listed.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Listed', () => { + it('is Listed', async () => { + const entity = await Entity.load({ + traits: { + Listed: {}, + }, + }); + expect(entity.is('Listed')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/mobile.js b/packages/entity/test/mobile.js new file mode 100644 index 0000000..7924de2 --- /dev/null +++ b/packages/entity/test/mobile.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Mobile', () => { + it('is Mobile', async () => { + const entity = await Entity.load({ + traits: { + Mobile: {}, + }, + }); + expect(entity.is('Mobile')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/perishable.js b/packages/entity/test/perishable.js new file mode 100644 index 0000000..5ba3a1d --- /dev/null +++ b/packages/entity/test/perishable.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Perishable', () => { + it('is Perishable', async () => { + const entity = await Entity.load({ + traits: { + Perishable: {}, + }, + }); + expect(entity.is('Perishable')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/positioned.js b/packages/entity/test/positioned.js new file mode 100644 index 0000000..c719331 --- /dev/null +++ b/packages/entity/test/positioned.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Positioned', () => { + it('is Positioned', async () => { + const entity = await Entity.load({ + traits: { + Positioned: {}, + }, + }); + expect(entity.is('Positioned')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/test/spawner.js b/packages/entity/test/spawner.js new file mode 100644 index 0000000..2bb88ab --- /dev/null +++ b/packages/entity/test/spawner.js @@ -0,0 +1,31 @@ +import {resource} from '@avocado/resource'; +import {Latus} from '@latus/core'; +import {expect} from 'chai'; + +const {name} = require('../package.json'); + +describe(name, () => { + let latus; + let Entity; + beforeEach(async () => { + latus = Latus.mock([ + ['@avocado/entity', `${__dirname}/../src`], + '@avocado/resource', + '@avocado/traits', + ]); + await Promise.all(latus.invokeFlat('@latus/core/starting')); + ({fromResourceType: {Entity}} = resource(latus)); + }); + describe('Traits', () => { + describe('Spawner', () => { + it('is Spawner', async () => { + const entity = await Entity.load({ + traits: { + Spawner: {}, + }, + }); + expect(entity.is('Spawner')).to.equal(true); + }); + }); + }); +}); diff --git a/packages/entity/yarn.lock b/packages/entity/yarn.lock index 8942e6c..67d2e2f 100644 --- a/packages/entity/yarn.lock +++ b/packages/entity/yarn.lock @@ -4,8 +4,8 @@ "@avocado/behavior@2.0.0": version "2.0.0" - resolved "https://npm.i12e.cha0s.io/@avocado%2fbehavior/-/behavior-2.0.0.tgz#24d29c0cff73aea30ad323b5c5cb474ee9fe8c8a" - integrity sha512-BQVlDW80DeRj6ZoPDjq7EuAezCYGixsLhNemrDaDnmPfk+Y3nPIMR5sbq7acIw2QM6NkW6aud5sLxboxgwIMbA== + resolved "https://npm.i12e.cha0s.io/@avocado%2fbehavior/-/behavior-2.0.0.tgz#6012a83233beb48ab85d965a1834097961cd7fda" + integrity sha512-f2uTKGXFY0aLxS7VCh7+6q8S7HhkRLnl8tEdNpmqUIlXR7fglm/CCIk5ueZJ2sYCnnBfloptdU31ir8PTCGOBw== dependencies: "@avocado/core" "2.0.0" "@avocado/traits" "^2.0.0" @@ -1029,8 +1029,8 @@ "@latus/socket@2.0.0": version "2.0.0" - resolved "https://npm.i12e.cha0s.io/@latus%2fsocket/-/socket-2.0.0.tgz#f8843d8733c3f84b7ac9f72afb62a1c783077a7d" - integrity sha512-+hpxoIo0lILIZ2U/xkvD3R6Mv5/SJKaMjKTBbyefMPxwK1TDnhO3peZb5SEizITNw8xmsM/Y06h7s2TrlYN64Q== + resolved "https://npm.i12e.cha0s.io/@latus%2fsocket/-/socket-2.0.0.tgz#b0590eabc39928803ede05dada3b31603ede8e20" + integrity sha512-i6Ed+2tWenNq3S157Vo0BXgMXentWfwCafyD+/3xqKt9tNI2+e2Fp8lQfAYLQoj1jrnvdL+cmJh3bAOLALWDtw== dependencies: "@latus/core" "2.0.0" "@latus/http" "2.0.0"