humus-old/common/combat/vulnerable.trait.js
2019-05-31 16:58:19 -05:00

270 lines
6.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 {Vector} from '@avocado/math';
import {DamagePacket} from './damage.packet';
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,
],
},
vulnerabilities: undefined,
}
}
static type() {
return 'vulnerable';
}
constructor(entity, params, state) {
super(entity, params, state);
this.damageId = 0;
this.damages = [];
this._hasAddedEmitter = false;
this._hasAddedEmitterRenderer = false;
this._isHydrating = false;
this._isInvulnerable = false;
this.locks = new Map();
const tookDamageActionsJSON = this.params.tookDamageActions;
this._tookDamageActions = behaviorItemFromJSON(tookDamageActionsJSON);
this.tookDamageActionsList = [];
}
destroy() {
this.locks.clear();
this.tookDamageActionsList = [];
}
hydrate() {
this._isHydrating = true;
this.addEmitter();
this.addEmitterRenderer();
}
acceptDamage(damage) {
const context = createContext();
context.add('entity', this.entity);
context.add('damage', damage);
const Actions = this._tookDamageActions.constructor;
const actions = new Actions();
actions.clone(this._tookDamageActions);
const tuple = {context, actions};
this.tookDamageActionsList.push(tuple);
actions.on('actionsFinished', () => {
context.clear();
const index = this.tookDamageActionsList.indexOf(tuple);
this.tookDamageActionsList.splice(tuple);
});
}
acceptPacket(packet) {
if (packet instanceof DamagePacket) {
const damages = packet.data.damages;
for (let i = 0; i < damages.length; ++i) {
const damage = damages[i];
if (this.entity.is('listed') && this.entity.list) {
damage.from = this.entity.list.findEntity(damage.from);
}
else {
damage.from = undefined;
}
this.acceptDamage(damage);
}
}
}
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;
}
packetsForUpdate() {
const packets = [];
if (this.damages.length > 0) {
packets.push(new DamagePacket({
damages: this.damages,
}, this.entity));
this.damages = [];
}
return packets;
}
listeners() {
return {
dying: () => {
this._isInvulnerable = true;
},
stageChanged: () => {
this.addEmitterRenderer();
},
tookDamage: (damage) => {
if (AVOCADO_SERVER) {
this.damages.push(damage);
}
},
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];
let power = damageSpec.power;
// Check if vulnerable to this affinity.
if (this.params.vulnerabilities) {
if (damageSpec.affinity in this.params.vulnerabilities) {
power *= this.params.vulnerabilities[damageSpec.affinity];
}
}
if (this.locks.has(damageSpec)) {
continue;
}
this.locks.set(damageSpec, damageSpec.lock);
// Give any knockback.
if (damageSpec.knockback) {
if (
this.entity.is('mobile')
&& this.entity.is('positioned')
&& entity.is('positioned')
) {
const difference = Vector.sub(
this.entity.position,
entity.position,
);
const unit = Vector.normalize(difference)
const knockback = Vector.scale(unit, damageSpec.knockback);
this.entity.applyMovement(knockback);
}
}
const variance = Math.random() * damageSpec.variance * 2 - damageSpec.variance;
const difference = power * variance;
// Account for variance past 0, so track if it's damage or not.
let amount = Math.round(power + difference);
let isDamage;
if (power < 0) {
isDamage = false;
if (amount > 0) {
amount = 0;
}
}
else {
isDamage = true;
if (amount < 0) {
amount = 0;
}
}
amount = Math.abs(amount);
const damage = {
id: this.damageId++,
isDamage,
amount,
damageSpec,
from: entity.numericUid,
};
this.entity.emit('tookDamage', damage);
}
},
};
}
tick(elapsed) {
if (AVOCADO_CLIENT) {
for (let i = 0; i < this.tookDamageActionsList.length; ++i) {
const {context, actions} = this.tookDamageActionsList[i];
actions.tick(context, elapsed);
}
}
if (AVOCADO_SERVER) {
const keyIterator = this.locks.keys();
for (
let thisKey = keyIterator.next();
thisKey.done !== true;
thisKey = keyIterator.next()
) {
const {value: key} = thisKey;
const remaining = this.locks.get(key) - elapsed;
if (remaining <= 0) {
this.locks.delete(key);
}
else {
this.locks.set(key, remaining);
}
}
}
}
}