humus-old/common/combat/vulnerable.trait.js
2019-12-12 19:27:19 -06:00

363 lines
8.7 KiB
JavaScript

import {Trait} from '@avocado/entity';
import {
behaviorItemFromJSON,
buildInvoke,
buildTraversal,
Context,
} from '@avocado/behavior';
import {flatten, invokeHookFlat, iterateForEach} from '@avocado/core';
import {hasGraphics, TextNodeRenderer} from '@avocado/graphics';
import {randomNumber, Vector} from '@avocado/math';
import {HarmPacket} from './harm.packet';
export class Vulnerable extends Trait {
static defaultParams() {
const emitHarm = buildInvoke(['entity', 'emitHarmParticles'], [
buildTraversal(['harm']),
]);
const playHarmfulSound = buildInvoke(['harm', 'from', 'playSound'], [
buildTraversal(['harm', 'from', 'harmfulSound']),
]);
return {
tookHarmActions: {
type: 'actions',
traversals: [
emitHarm,
playHarmfulSound,
],
},
modifiers: undefined,
}
}
static type() {
return 'vulnerable';
}
constructor(entity, params, state) {
super(entity, params, state);
this.harmTickingPromises = [];
this.harms = [];
this._isInvulnerable = false;
this.locks = new Map();
this._tookHarmActions = behaviorItemFromJSON(
this.params.tookHarmActions
);
}
destroy() {
this.locks.clear();
this.harmTickingPromises = [];
}
acceptHarm(harm) {
const context = new Context({
harm,
entity: this.entity,
});
const tickingPromise = this._tookHarmActions.tickingPromise(context);
tickingPromise.then(() => {
context.destroy();
});
this.harmTickingPromises.push(tickingPromise);
if (harm.from) {
harm.from.emitHarmfulParticles(this.entity);
}
}
acceptPacket(packet) {
if (packet instanceof HarmPacket) {
for (let i = 0; i < packet.data.length; ++i) {
const harm = packet.data[i];
if (this.entity.is('listed') && this.entity.list) {
harm.from = this.entity.list.findEntity(harm.from);
}
else {
harm.from = undefined;
}
this.acceptHarm(harm);
}
}
}
cleanPackets() {
this.harms = [];
}
harmTextSize(amount) {
const biggest = 16;
const smallest = biggest / 2;
const step = biggest / 6;
if (amount > 999) {
return biggest;
}
else if (amount > 99) {
return smallest + (step * 2);
}
else if (amount > 9) {
return smallest + step;
}
else {
return smallest;
}
}
get isInvulnerable() {
return this._isInvulnerable;
}
set isInvulnerable(isInvulnerable) {
this._isInvulnerable = isInvulnerable;
}
packets(informed) {
if (this.harms.length > 0) {
return new HarmPacket(this.harms);
}
}
hooks() {
return {
particles: () => {
return {
harm: {
traits: {
darkened: {
params: {
isDarkened: false,
},
},
emitted: {
params: {
alpha: {
start: 1,
end: 0,
},
force: [0, 1],
velocity: {
angle: {
min: 337.5,
max: 382.5,
},
magnitude: {
min: -1.25,
max: -0.75,
},
},
rotation: {
start: 0,
add: {
min: -0.5,
max: 0.5,
},
},
scale: {
start: 1,
end: 1.25,
},
},
},
existent: {},
layered: {},
listed: {},
positioned: {},
roomed: {},
visible: {
state: {
zIndex: 65535,
}
},
},
},
blood: {
traits: {
emitted: {
params: {
alpha: {
start: 1,
end: 0.4,
},
force: [0, 3],
velocity: {
angle: {
min: 338.5,
max: 382.5,
},
magnitude: {
min: 0.7,
max: 0.9,
},
},
scale: {
start: 1,
end: 1.25,
},
transient: false,
ttl: 0.5,
},
},
existent: {},
layered: {},
listed: {},
perishable: {
params: {
ttl: 10,
},
},
positioned: {},
primitive: {
params: {
primitives: [
{
type: 'circle',
radius: 0.5,
line: {
rgba: [255, 0, 0],
},
fill: {
rgba: [255, 0, 0],
},
},
],
},
},
roomed: {},
visible: {},
},
},
};
},
}
}
listeners() {
return {
isDyingChanged: (_, isDying) => {
this._isInvulnerable = isDying;
},
tookHarm: (harm) => {
if (AVOCADO_SERVER) {
this.harms.push(harm);
this.setDirty();
}
},
};
}
methods() {
return {
emitHarmParticles: (harm) => {
const {amount, isHarm} = harm;
const fill = isHarm ? '#FF0000' : '#00FF77';
this.entity.emitParticle('harm', {
traits: {
textual: {
state: {
text: amount,
textStyle: {
fill,
fontFamily: 'joystix',
fontSize: this.harmTextSize(amount),
strokeThickness: 2,
},
},
},
},
});
this.entity.emitParticle('blood', {
rate: 0.025,
count: 8 * Math.log10(amount),
});
},
takeHarmFrom: (entity) => {
const harmSpecs = entity.harmSpecs;
for (let i = 0; i < harmSpecs.length; ++i) {
const harmSpec = harmSpecs[i];
if (this.locks.has(harmSpec)) {
continue;
}
this.locks.set(harmSpec, harmSpec.lock);
let power = harmSpec.power;
// Check if vulnerable to this affinity.
if (this.params.modifiers) {
if (harmSpec.affinity in this.params.modifiers) {
power *= this.params.modifiers[harmSpec.affinity];
}
}
// Give any knockback.
if (harmSpec.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, harmSpec.knockback);
this.entity.applyMovement(knockback);
}
}
const variance = Math.random() * harmSpec.variance * 2 - harmSpec.variance;
const difference = power * variance;
// Account for variance past 0, so track if it's harm or not.
let amount = Math.round(power + difference);
let isHarm;
if (power < 0) {
isHarm = false;
if (amount > 0) {
amount = 0;
}
}
else {
isHarm = true;
if (amount < 0) {
amount = 0;
}
}
amount = Math.abs(amount);
const harm = {
isHarm,
amount,
harmSpec,
from: entity.numericUid,
};
this.entity.emit('tookHarm', harm);
}
},
};
}
tick(elapsed) {
for (let i = 0; i < this.harmTickingPromises.length; ++i) {
this.harmTickingPromises[i].tick(elapsed);
}
if (AVOCADO_SERVER) {
iterateForEach(this.locks.keys(), (key) => {
const remaining = this.locks.get(key) - elapsed;
if (remaining <= 0) {
this.locks.delete(key);
}
else {
this.locks.set(key, remaining);
}
});
}
}
}