humus-old/common/combat/vulnerable.trait.js

309 lines
7.7 KiB
JavaScript

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(),
};
}
initialize() {
this.damageId = 0;
this.damageList = {};
this._hasAddedEmitter = false;
this._hasAddedEmitterRenderer = false;
this._isHydrating = false;
this._isInvulnerable = false;
this.locks = new Map();
this._tookDamageActionsJSON = this.params.get('tookDamageActions').toJS();
this.tookDamageActions = [];
}
hydrate() {
this._isHydrating = true;
this.addEmitter();
this.addEmitterRenderer();
}
patchStateStep(key, step) {
if ('state' !== key) {
return;
}
const stateKey = step.path.substr(1);
const value = this.transformPatchValue(stateKey, step.value);
const stateKeyParts = stateKey.split('/');
switch (stateKeyParts[0]) {
case 'damageList':
for (let i = 0; i < value.length; ++i) {
const damage = value[i];
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:
super.patchStateStep(key, step);
break;
}
}
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;
}
set isInvulnerable(isInvulnerable) {
this._isInvulnerable = isInvulnerable;
}
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.addEmitterRenderer();
},
traitsChanged: () => {
this.addEmitter();
this.addEmitterRenderer();
},
};
}
methods() {
return {
takeDamageFrom: (entity) => {
const damageSpecs = entity.damageSpecs;
for (let i = 0; i < damageSpecs.length; ++i) {
const damageSpec = damageSpecs[i];
if (this.locks.has(damageSpec)) {
return;
}
this.locks.set(damageSpec, damageSpec.lock);
const variance = Math.random() * damageSpec.variance * 2 - damageSpec.variance;
const difference = damageSpec.power * variance;
// Account for variance past 0, so track if it's damage or not.
let amount = Math.round(damageSpec.power + difference);
let isDamage;
if (damageSpec.power < 0) {
isDamage = false;
if (amount > 0) {
amount = 0;
}
}
else {
isDamage = true;
if (amount < 0) {
amount = 0;
}
}
amount = Math.abs(amount);
if (!this.damageList[entity.instanceUuid]) {
this.damageList[entity.instanceUuid] = [];
}
const damage = {
id: this.damageId++,
isDamage,
amount,
damageSpec,
from: entity.instanceUuid,
};
this.damageList[entity.instanceUuid].push(damage);
this.entity.emit('tookDamage', damage, 'server');
}
},
};
}
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());
}
if (Object.keys(this.damageList).length > 0) {
this.state = this.state.set('damageList', I.Map(this.damageList));
this.isDirty = true;
this.damageList = {};
}
const keys = Array.from(this.locks.keys());
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const remaining = this.locks.get(key) - elapsed;
if (remaining <= 0) {
this.locks.delete(key);
}
else {
this.locks.set(key, remaining);
}
}
}
}