diff --git a/packages/behavior/context/flow.hooks.js b/packages/behavior/context/flow.hooks.js index 2f5c4e6..acd38fb 100644 --- a/packages/behavior/context/flow.hooks.js +++ b/packages/behavior/context/flow.hooks.js @@ -1,3 +1,5 @@ +import {Context} from './context'; + class Flow { static conditional(condition, actions, context) { @@ -6,6 +8,44 @@ class Flow { } }; + static contextDescription() { + return { + type: 'object', + children: { + conditional: { + type: 'ticking-promise', + label: 'If $1 then run $2.', + args: [ + ['condition', { + type: 'condition', + }], + ['actions', { + type: 'actions', + }], + ], + }, + parallel: { + type: 'ticking-promise', + label: 'Run $1 in parallel.', + args: [ + ['actions', { + type: 'actions', + }], + ], + }, + serial: { + type: 'ticking-promise', + label: 'Run $1 serially.', + args: [ + ['actions', { + type: 'actions', + }], + ], + }, + } + }; + } + static parallel(actions, context) { return actions.parallel(context); } diff --git a/packages/behavior/context/timing.hooks.js b/packages/behavior/context/timing.hooks.js index 1ef60c4..572a2ff 100644 --- a/packages/behavior/context/timing.hooks.js +++ b/packages/behavior/context/timing.hooks.js @@ -2,6 +2,23 @@ import {TickingPromise} from '@avocado/core'; class Timing { + static contextDescription() { + return { + type: 'object', + children: { + wait: { + type: 'ticking-promise', + label: 'Wait for $1 seconds.', + args: [ + ['duration', { + type: 'number', + }], + ], + }, + }, + }; + } + static wait (duration) { return new TickingPromise( () => {}, diff --git a/packages/behavior/traits/behaved.trait.js b/packages/behavior/traits/behaved.trait.js index da6ed57..be04d86 100644 --- a/packages/behavior/traits/behaved.trait.js +++ b/packages/behavior/traits/behaved.trait.js @@ -26,6 +26,15 @@ export default class Behaved extends decorate(Trait) { } } + static describeParams() { + return { + routines: { + type: 'routines', + label: 'Routines', + }, + } + } + static describeState() { return { isBehaving: { diff --git a/packages/entity/entity.synchronized.js b/packages/entity/entity.synchronized.js index e31a718..7129101 100644 --- a/packages/entity/entity.synchronized.js +++ b/packages/entity/entity.synchronized.js @@ -227,6 +227,20 @@ export default class Entity extends decorate(Resource) { this._fastDirtyCheck = false; } + contextDescription() { + return { + type: 'entity', + children: this._traitsFlat.reduce( + (r, trait) => ({ + ...r, + ...trait.constructor.contextDescription(), + ...trait.constructor.describeParams(), + ...trait.constructor.describeState(), + }), {}, + ), + }; + } + fromJSON(json) { super.fromJSON(json); if (json.instanceUuid) { diff --git a/packages/entity/trait/index.js b/packages/entity/trait/index.js index cabe5d9..a6690e6 100644 --- a/packages/entity/trait/index.js +++ b/packages/entity/trait/index.js @@ -38,7 +38,7 @@ export class Trait extends decorate(class {}) { this._fastDirtyCheck = false; } - static contextType() { + static contextDescription() { return {}; } diff --git a/packages/entity/traits/alive.trait.js b/packages/entity/traits/alive.trait.js index afea412..5afa609 100644 --- a/packages/entity/traits/alive.trait.js +++ b/packages/entity/traits/alive.trait.js @@ -25,6 +25,15 @@ const decorate = compose( export default class Alive extends decorate(Trait) { + static contextDescription() { + return { + forceDeath: { + type: 'void', + label: 'Force death', + }, + }; + } + static defaultParams() { const playDeathSound = buildInvoke(['entity', 'playSound'], [ buildTraversal(['entity', 'deathSound']), diff --git a/packages/entity/traits/existent.trait.js b/packages/entity/traits/existent.trait.js index 2870f88..45310ba 100644 --- a/packages/entity/traits/existent.trait.js +++ b/packages/entity/traits/existent.trait.js @@ -10,6 +10,26 @@ const decorate = compose( export default class Existent extends decorate(Trait) { + static contextDescription() { + return { + transition: { + type: 'ticking-promise', + label: 'Transition $1 for $2 seconds using $3.', + args: [ + ['props', { + type: 'object', + }], + ['duration', { + type: 'number', + }], + ['easing', { + type: 'string', + }], + ], + }, + }; + } + static defaultState() { return { isTicking: true, diff --git a/packages/entity/traits/listed.trait.js b/packages/entity/traits/listed.trait.js index e85d488..d4075e5 100644 --- a/packages/entity/traits/listed.trait.js +++ b/packages/entity/traits/listed.trait.js @@ -4,6 +4,24 @@ import {Trait} from '../trait'; export default class Listed extends Trait { + static contextDescription() { + return { + detachFromList: { + type: 'void', + label: 'Detach from list.', + }, + attachToList: { + type: 'void', + label: 'Attach to $1.', + args: [ + ['list', { + type: 'entity-list', + }], + ], + }, + }; + } + static type() { return 'listed'; } diff --git a/packages/entity/traits/mobile.trait.js b/packages/entity/traits/mobile.trait.js index 3f66a9a..1654c9a 100644 --- a/packages/entity/traits/mobile.trait.js +++ b/packages/entity/traits/mobile.trait.js @@ -10,6 +10,50 @@ const decorate = compose( export default class Mobile extends decorate(Trait) { + static contextDescription() { + return { + moveFor: { + type: 'void', + label: 'Move toward $1 for $2 seconds.', + args: [ + ['movement', { + type: 'vector', + }], + ['duration', { + type: 'number', + }], + ], + }, + applyMovement: { + type: 'void', + label: 'Apply movement of $1.', + args: [ + ['movement', { + type: 'vector', + }], + ], + }, + forceMovement: { + type: 'void', + label: 'Force movement of $1.', + args: [ + ['movement', { + type: 'vector', + }], + ], + }, + requestMovement: { + type: 'void', + label: 'Request movement of $1.', + args: [ + ['movement', { + type: 'vector', + }], + ], + }, + }; + } + static defaultState() { return { isMobile: true, diff --git a/packages/entity/traits/positioned.trait.js b/packages/entity/traits/positioned.trait.js index c200162..e248a33 100644 --- a/packages/entity/traits/positioned.trait.js +++ b/packages/entity/traits/positioned.trait.js @@ -17,6 +17,20 @@ const decorate = compose( // < 16768 will pack into 1 short per axe and give +/- 0.25 precision. export default class Positioned extends decorate(Trait) { + static contextDescription() { + return { + setPosition: { + type: 'void', + label: 'Set position to $1.', + args: [ + ['position', { + type: 'vector', + }], + ], + }, + }; + } + static defaultState() { return { x: 0, diff --git a/packages/entity/traits/spawner.trait.js b/packages/entity/traits/spawner.trait.js index 707c1cd..66946ed 100644 --- a/packages/entity/traits/spawner.trait.js +++ b/packages/entity/traits/spawner.trait.js @@ -13,6 +13,59 @@ const decorate = compose( export default class Spawner extends decorate(Trait) { + static contextDescription() { + return { + spawn: { + type: 'entity', + label: 'Spawn $1 with $2 extensions.', + args: [ + ['key', { + type: 'string', + }], + ['json', { + type: 'object', + }], + ], + }, + spawnAt: { + type: 'entity', + label: 'Spawn $1 as $2 with $3 extensions.', + args: [ + ['key', { + type: 'string', + }], + ['position', { + type: 'vector', + }], + ['json', { + type: 'object', + }], + ], + }, + spawnRaw: { + type: 'entity', + label: 'Spawn $1.', + args: [ + ['json', { + type: 'object', + }], + ] + }, + spawnRawAt: { + type: 'entity', + label: 'Spawn $1 at $2.', + args: [ + ['position', { + type: 'vector', + }], + ['json', { + type: 'object', + }], + ] + }, + }; + } + static defaultParams() { return { spawns: {}, diff --git a/packages/graphics/traits/visible.trait.js b/packages/graphics/traits/visible.trait.js index ac64b23..03ec7f9 100644 --- a/packages/graphics/traits/visible.trait.js +++ b/packages/graphics/traits/visible.trait.js @@ -30,6 +30,16 @@ const decorate = compose( export default class Visible extends decorate(Trait) { + static contextDescription() { + return { + updateVisibleBoundingBox: { + advanced: true, + type: 'void', + label: 'Update the visible bounding box.', + }, + }; + } + static defaultParams() { return { filter: undefined, diff --git a/packages/math/globals.hooks.js b/packages/math/globals.hooks.js index 4e893fc..74d3a50 100644 --- a/packages/math/globals.hooks.js +++ b/packages/math/globals.hooks.js @@ -1,5 +1,37 @@ import * as MathExt from '.'; +MathExt.contextDescription = () => { + return { + type: 'module', + children: { + floor: { + type: 'number', + label: 'Floor $1.', + args: [ + ['operand', { + type: 'number', + }], + ], + }, + randomNumber: { + type: 'number', + label: 'Random number between $1 and $2.', + args: [ + ['min', { + type: 'number', + }], + ['max', { + type: 'number', + }], + ], + }, + Vector: { + type: 'Vector', + }, + }, + }; +}; + export function behaviorContextGlobals() { return { Math: MathExt, diff --git a/packages/math/vector/index.js b/packages/math/vector/index.js index 276834b..39a7840 100644 --- a/packages/math/vector/index.js +++ b/packages/math/vector/index.js @@ -408,3 +408,21 @@ export class Range extends MathRange { } } + +export function contextDescription() { + return { + type: 'Vector', + children: { + fromDirection: { + type: 'vector', + label: 'Vector from direction $1.', + args: [ + ['direction', { + type: 'number', + }], + ], + }, + }, + }; +} + diff --git a/packages/physics/traits/collider.trait.js b/packages/physics/traits/collider.trait.js index d627f76..25e0d2f 100644 --- a/packages/physics/traits/collider.trait.js +++ b/packages/physics/traits/collider.trait.js @@ -10,6 +10,51 @@ const decorate = compose( export default class Collider extends decorate(Trait) { + static contextDescription() { + return { + collidesWith: { + advanced: true, + type: 'bool', + label: 'I would collide with $1.', + args: [ + ['other', { + type: 'entity', + }], + ], + }, + doesNotCollideWith: { + advanced: true, + type: 'bool', + label: 'I would not collide with $1.', + args: [ + ['other', { + type: 'entity', + }], + ], + }, + setDoesCollideWith: { + advanced: true, + type: 'void', + label: 'Set $1 as colliding with myself.', + args: [ + ['other', { + type: 'entity', + }], + ], + }, + setDoesNotCollideWith: { + advanced: true, + type: 'void', + label: 'Set $1 as not colliding with myself.', + args: [ + ['other', { + type: 'entity', + }], + ], + }, + }; + } + static defaultParams() { return { activeCollision: false, @@ -68,7 +113,7 @@ export default class Collider extends decorate(Trait) { } destroy() { - this.entity.releaseAllCollisions(); + this.releaseAllCollisions(); } checkActiveCollision() { @@ -144,6 +189,14 @@ export default class Collider extends decorate(Trait) { this._collisionTickingPromises.push(tickingPromise); } + releaseAllCollisions() { + for (let i = 0; i < this._isCollidingWith.length; i++) { + const entity = this._isCollidingWith[i]; + entity.emit('collisionEnd', this.entity); + } + this._isCollidingWith = []; + } + listeners() { return { @@ -174,7 +227,7 @@ export default class Collider extends decorate(Trait) { }, removedFromRoom: () => { - this.entity.releaseAllCollisions(); + this.releaseAllCollisions(); }, }; @@ -198,14 +251,6 @@ export default class Collider extends decorate(Trait) { return !this.entity.collidesWith(entity); }, - releaseAllCollisions: () => { - for (let i = 0; i < this._isCollidingWith.length; i++) { - const entity = this._isCollidingWith[i]; - entity.emit('collisionEnd', this.entity); - } - this._isCollidingWith = []; - }, - setDoesCollideWith: (entity) => { const index = this._doesNotCollideWith.indexOf(entity); if (-1 !== index) { diff --git a/packages/sound/traits/audible.trait.js b/packages/sound/traits/audible.trait.js index ef88e49..a7d4b30 100644 --- a/packages/sound/traits/audible.trait.js +++ b/packages/sound/traits/audible.trait.js @@ -4,6 +4,20 @@ import {Sound} from '..'; export default class Audible extends Trait { + static contextDescription() { + return { + playSound: { + type: 'void', + label: 'Play the $1 sound.', + args: [ + ['key', { + type: 'string', + }], + ], + }, + }; + } + static defaultParams() { return { sounds: {},