flow: emitter and sound, and some refactor

This commit is contained in:
cha0s 2019-04-20 16:09:34 -05:00
parent b8f69732aa
commit d46bd2a7fa
11 changed files with 264 additions and 39 deletions

View File

@ -18,6 +18,37 @@ export class Alive extends decorate(Trait) {
deathActions: {
type: 'actions',
traversals: [
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'entity',
},
{
type: 'key',
key: 'playSound',
},
{
type: 'invoke',
args: [
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'entity',
},
{
type: 'key',
key: 'deathSound',
},
],
},
],
},
],
},
{
type: 'traversal',
steps: [
@ -74,6 +105,7 @@ export class Alive extends decorate(Trait) {
},
],
},
deathSound: 'deathSound',
};
}
@ -89,15 +121,20 @@ export class Alive extends decorate(Trait) {
this._context.add('entity', this.entity);
const actionsJSON = this.params.get('deathActions').toJS();
this._deathActions = behaviorItemFromJSON(actionsJSON);
this._deathSound = this.params.get('deathSound');
const conditionJSON = this.params.get('deathCondition').toJS();
this._deathCondition = behaviorItemFromJSON(conditionJSON);
this._isDying = false;
}
get deathSound() {
return this._deathSound;
}
listeners() {
return {
tookDamage: (damage) => {
tookDamage: (damage, source) => {
this.entity.life -= damage.amount;
// Clamp health between 0 and max.
this.entity.life = Math.min(
@ -112,11 +149,19 @@ export class Alive extends decorate(Trait) {
methods() {
return {
die: () => {
dieIfPossible: () => {
if (this._deathCondition.check(this._context)) {
this.entity.forceDeath();
}
},
forceDeath: () => {
this._isDying = true;
this.entity.emit('dying');
this._deathActions.on('actionsFinished', () => {
this.entity.destroy();
if (this.entity.is('existent')) {
this.entity.destroy();
}
});
},
@ -128,10 +173,7 @@ export class Alive extends decorate(Trait) {
this._deathActions.tick(this._context, elapsed);
}
else {
if (this._deathCondition.check(this._context)) {
// It's a good day to die.
this.entity.die();
}
this.entity.dieIfPossible();
}
}

View File

@ -6,6 +6,7 @@ export class Damaging extends Trait {
static defaultParams() {
return {
damagingSound: '',
damageSpecs: [],
};
}
@ -22,12 +23,17 @@ export class Damaging extends Trait {
...damageSpec,
};
});
this._damagingSound = this.params.get('damagingSound');
}
get damageSpecs() {
return this._damageSpecs;
}
get damagingSound() {
return this._damagingSound;
}
tick(elapsed) {
const isCollidingWith = this.entity.isCollidingWith;
for (let i = 0; i < isCollidingWith.length; ++i) {

View File

@ -1,12 +1,106 @@
import * as I from 'immutable';
import {Trait} from '@avocado/entity';
import {behaviorItemFromJSON, createContext} from '@avocado/behavior';
import {hasGraphics, TextNodeRenderer} from '@avocado/graphics';
import {DamageEmitter} from './emitter';
export class Vulnerable extends Trait {
static defaultParams() {
return {
tookDamageActions: {
type: 'actions',
traversals: [
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'entity',
},
{
type: 'key',
key: 'emitParticle',
},
{
type: 'invoke',
args: [
{
type: 'literal',
value: 'damage',
},
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'entity',
},
{
type: 'key',
key: 'position',
},
],
},
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'damage',
},
],
},
],
},
],
},
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'damage',
},
{
type: 'key',
key: 'from',
},
{
type: 'key',
key: 'playSound',
},
{
type: 'invoke',
args: [
{
type: 'traversal',
steps: [
{
type: 'key',
key: 'damage',
},
{
type: 'key',
key: 'from',
},
{
type: 'key',
key: 'damagingSound',
},
],
},
],
},
],
},
],
},
}
}
static defaultState() {
return {
damageList: I.Map(),
@ -16,12 +110,19 @@ export class Vulnerable extends Trait {
initialize() {
this.damageId = 0;
this.damageList = {};
this._hasAddedEmitter = false;
this._hasAddedEmitterRenderer = false;
this._isHydrating = false;
this._isInvulnerable = false;
this.locks = new Map();
if (hasGraphics) {
this.emitter = new DamageEmitter();
this.setRenderer();
}
this._tookDamageActionsJSON = this.params.get('tookDamageActions').toJS();
this.tookDamageActions = [];
}
hydrate() {
this._isHydrating = true;
this.addEmitter();
this.addEmitterRenderer();
}
patchStateStep(key, step) {
@ -35,7 +136,13 @@ export class Vulnerable extends Trait {
case 'damageList':
for (let i = 0; i < value.length; ++i) {
const damage = value[i];
this.emitter.emit(this.entity.position, damage);
if (this.entity.is('listed')) {
damage.from = this.entity.list.findEntity(damage.from);
}
else {
damage.from = undefined;
}
this.entity.emit('tookDamage', damage, 'client');
}
break;
default:
@ -44,6 +151,38 @@ export class Vulnerable extends Trait {
}
}
addEmitter() {
if (!this._isHydrating) {
return;
}
if (this._hasAddedEmitter) {
return;
}
if (!this.entity.is('emitter')) {
return;
}
this.entity.addEmitter('damage', new DamageEmitter());
this._hasAddedEmitter = true;
}
addEmitterRenderer() {
if (!this._isHydrating) {
return;
}
if (this._hasAddedEmitterRenderer) {
return;
}
if (!this.entity.is('emitter')) {
return;
}
if (!this.entity.is('staged') || !this.entity.stage) {
return;
}
const renderer = new TextNodeRenderer('.damage', this.entity.stage);
this.entity.addEmitterRenderer('damage', renderer);
this._hasAddedEmitterRenderer = true;
}
get isInvulnerable() {
return this._isInvulnerable;
}
@ -52,38 +191,41 @@ export class Vulnerable extends Trait {
this._isInvulnerable = isInvulnerable;
}
setRenderer() {
if (this.entity.is('staged') && this.entity.stage) {
const renderer = new TextNodeRenderer('.damage', this.entity.stage);
this.emitter.addRenderer(renderer);
}
}
hooks() {
return {
afterDestructionTickers: () => {
return (elapsed) => {
if (!hasGraphics) {
return true;
}
this.emitter.tick(elapsed);
return !this.emitter.hasParticles();
};
},
}
}
listeners() {
return {
tookDamage: (damage, source) => {
if ('server' === source) {
return;
}
const context = createContext();
context.add('entity', this.entity);
context.add('damage', damage);
const actions = behaviorItemFromJSON(
this._tookDamageActionsJSON
);
const tuple = {
context,
actions,
};
this.tookDamageActions.push(tuple);
actions.on('actionsFinished', () => {
const index = this.tookDamageActions.indexOf(tuple);
this.tookDamageActions.splice(tuple);
});
},
dying: () => {
this._isInvulnerable = true;
},
stageChanged: () => {
this.setRenderer();
this.addEmitterRenderer();
},
traitsChanged: () => {
this.addEmitter();
this.addEmitterRenderer();
},
};
@ -126,9 +268,10 @@ export class Vulnerable extends Trait {
isDamage,
amount,
damageSpec,
from: entity.instanceUuid,
};
this.damageList[entity.instanceUuid].push(damage);
this.entity.emit('tookDamage', damage);
this.entity.emit('tookDamage', damage, 'server');
}
},
@ -136,8 +279,9 @@ export class Vulnerable extends Trait {
}
tick(elapsed) {
if (hasGraphics) {
this.emitter.tick(elapsed);
for (let i = 0; i < this.tookDamageActions.length; ++i) {
const {context, actions} = this.tookDamageActions[i];
actions.tick(context, elapsed);
}
if (this.state.get('damageList').size > 0) {
this.state = this.state.set('damageList', I.Map());

View File

@ -34,6 +34,7 @@
"@avocado/physics": "1.x",
"@avocado/resource": "1.x",
"@avocado/server": "1.x",
"@avocado/sound": "1.x",
"@avocado/state": "1.x",
"@avocado/timing": "1.x",
"@avocado/topdown": "1.x",

BIN
resource/aria-math2.mp3 Executable file

Binary file not shown.

BIN
resource/ded.wav Executable file

Binary file not shown.

BIN
resource/fire.wav Executable file

Binary file not shown.

BIN
resource/shatter.mp3 Executable file

Binary file not shown.

BIN
resource/step-grass.ogg Executable file

Binary file not shown.

View File

@ -4,6 +4,16 @@ import {Room} from '@avocado/topdown';
function fireJSON(position) {
return {
traits: {
audible: {
params: {
sounds: {
fire: {
src: '/fire.wav',
volume: 0.05,
},
}
}
},
collider: {
params: {
isSensor: true,
@ -11,6 +21,7 @@ function fireJSON(position) {
},
damaging: {
params: {
damagingSound: 'fire',
damageSpecs: [
{
affinity: 'fire',
@ -142,6 +153,16 @@ function kittyJSON(position) {
}
},
},
audible: {
params: {
sounds: {
deathSound: {
src: '/ded.wav',
volume: 0.1,
},
}
}
},
behaved: {
params: {
routines: {
@ -243,6 +264,7 @@ function kittyJSON(position) {
direction: 2,
},
},
emitter: {},
existent: {},
visible: {},
mobile: {

View File

@ -106,6 +106,12 @@
dependencies:
socket.io "2.2.0"
"@avocado/sound@1.x":
version "1.0.0"
resolved "https://npm.i12e.cha0s.io/@avocado%2fsound/-/sound-1.0.0.tgz#348939934234e1966dcc92bee1bae4829924c95d"
dependencies:
howler "2.1.2"
"@avocado/state@1.x":
version "1.0.2"
resolved "https://npm.i12e.cha0s.io/@avocado%2fstate/-/state-1.0.2.tgz#aef7928bcd512874a31567c7133759708ae6b32f"
@ -2385,6 +2391,10 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
howler@2.1.2:
version "2.1.2"
resolved "https://npm.i12e.cha0s.io/howler/-/howler-2.1.2.tgz#8433a09d8fe84132a3e726e05cb2bd352ef8bd49"
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://npm.i12e.cha0s.io/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"