242 lines
5.8 KiB
JavaScript
242 lines
5.8 KiB
JavaScript
import * as I from 'immutable';
|
|
|
|
import {Trait} from '@avocado/entity';
|
|
import {
|
|
behaviorItemFromJSON,
|
|
buildInvoke,
|
|
buildTraversal,
|
|
createContext,
|
|
} from '@avocado/behavior';
|
|
import {hasGraphics, TextNodeRenderer} from '@avocado/graphics';
|
|
|
|
import {DamageEmitter} from './emitter';
|
|
|
|
export class Vulnerable extends Trait {
|
|
|
|
static defaultParams() {
|
|
const emitDamage = buildInvoke(['entity', 'emitParticle'], [
|
|
'damage',
|
|
buildTraversal(['entity', 'position']),
|
|
buildTraversal(['damage']),
|
|
]);
|
|
const playDamagingSound = buildInvoke(['damage', 'from', 'playSound'], [
|
|
buildTraversal(['damage', 'from', 'damagingSound']),
|
|
]);
|
|
return {
|
|
tookDamageActions: {
|
|
type: 'actions',
|
|
traversals: [
|
|
emitDamage,
|
|
playDamagingSound,
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
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':
|
|
if (!value) {
|
|
break;
|
|
}
|
|
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.acceptDamage(damage);
|
|
}
|
|
break;
|
|
default:
|
|
super.patchStateStep(key, step);
|
|
break;
|
|
}
|
|
}
|
|
|
|
acceptDamage(damage) {
|
|
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);
|
|
});
|
|
}
|
|
|
|
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 {
|
|
|
|
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);
|
|
}
|
|
},
|
|
|
|
};
|
|
}
|
|
|
|
tick(elapsed) {
|
|
if (AVOCADO_CLIENT) {
|
|
for (let i = 0; i < this.tookDamageActions.length; ++i) {
|
|
const {context, actions} = this.tookDamageActions[i];
|
|
actions.tick(context, elapsed);
|
|
}
|
|
}
|
|
if (AVOCADO_SERVER) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|