import { behaviorItemFromJSON, buildCondition, buildInvoke, buildTraversal, Context, } from '@avocado/behavior'; import {compose} from '@avocado/core'; import {StateProperty, Trait} from '../trait'; import {TraitUpdateAlivePacket} from '../packets/trait-update-alive.packet'; const decorate = compose( StateProperty('life', { track: true, }), StateProperty('maxLife', { track: true, }), ); export class Alive extends decorate(Trait) { static defaultParams() { const playDeathSound = buildInvoke(['entity', 'playSound'], [ buildTraversal(['entity', 'deathSound']), ]); const squeeze = buildInvoke(['entity', 'transition'], [ { opacity: 0, visibleScaleX: .3, visibleScaleY: 3, }, 0.2, ]); const isLifeGone = buildCondition('<=', [ buildTraversal(['entity', 'life']), 0, ]); return { deathActions: { type: 'actions', traversals: [ playDeathSound, squeeze, ], }, deathCondition: isLifeGone, deathSound: 'deathSound', }; } static defaultState() { return { life: 100, maxLife: 100, }; } static type() { return 'alive'; } constructor(entity, params, state) { super(entity, params, state); this._context = new Context({ entity: this.entity, }); const actionsJSON = this.params.deathActions; this._deathActions = behaviorItemFromJSON(actionsJSON); this._deathSound = this.params.deathSound; const conditionJSON = this.params.deathCondition; this._deathCondition = behaviorItemFromJSON(conditionJSON); this._dyingTickingPromise = false; } destroy() { this._context.destroy(); } acceptPacket(packet) { if (packet instanceof TraitUpdateAlivePacket) { this.entity.life = packet.data.life; this.entity.maxLife = packet.data.maxLife; } } get deathSound() { return this._deathSound; } packets(informed) { const {life, maxLife} = this.stateDifferences(); if (life || maxLife) { return new TraitUpdateAlivePacket(this.state); } } listeners() { return { tookDamage: (damage, source) => { if (damage.damageSpec.power > 0) { this.entity.life -= damage.amount; } else { this.entity.life += damage.amount; } // Clamp health between 0 and max. this.entity.life = Math.min( Math.max(0, this.entity.life), this.entity.maxLife ); }, } } methods() { return { dieIfPossible: () => { if (this._deathCondition.check(this._context)) { this.entity.forceDeath(); } }, forceDeath: () => { if (this._dyingTickingPromise) { return; } this._dyingTickingPromise = this._deathActions.tickingPromise( this._context ) this._dyingTickingPromise.then(() => { this.entity.destroy(); }); }, }; } tick(elapsed) { if (this._dyingTickingPromise) { this._dyingTickingPromise.tick(elapsed); } else { this.entity.dieIfPossible(); } } }