363 lines
8.7 KiB
JavaScript
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);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
|