humus-old/common/traits/tool.trait.js
2019-11-03 22:13:49 -06:00

282 lines
6.8 KiB
JavaScript

import {behaviorItemFromJSON, Context} from '@avocado/behavior';
import {compose, TickingPromise} from '@avocado/core';
import {StateProperty, Trait} from '@avocado/entity';
import {Color, Primitives} from '@avocado/graphics';
import {Rectangle, Vector} from '@avocado/math';
const decorate = compose(
);
// Tools.
export class Tool extends decorate(Trait) {
static defaultParams() {
return {
actions: {
type: 'actions',
traversals: [],
},
condition: {
type: 'condition',
operator: 'or',
operands: [],
},
setup: {
type: 'actions',
traversals: [],
},
target: {
type: 'projection',
distance: 1,
length: 1,
width: 1,
},
};
}
static type() {
return 'tool';
}
constructor(entity, params, state) {
super(entity, params, state);
if (AVOCADO_CLIENT) {
this.guidePrimitives = new Primitives();
}
if (1 !== this.params.target.length % 2) {
throw new Error("Tool::params.target.length must be odd!");
}
if (1 !== this.params.target.width % 2) {
throw new Error("Tool::params.target.width must be odd!");
}
this.targets = [];
this.toolActions = behaviorItemFromJSON(this.params.actions);
this.toolCondition = behaviorItemFromJSON(this.params.condition);
this.toolSetup = behaviorItemFromJSON(this.params.setup);
}
destroy() {
if (AVOCADO_CLIENT) {
this.guidePrimitives.destroy();
}
}
calculateTargetStart() {
const wielder = this.entity.wielder;
if (!wielder) {
return;
}
if (!wielder.is('directional')) {
return;
}
if (!wielder.is('layered')) {
return;
}
const tile = Vector.floor(wielder.tile);
const distance = this.params.target.distance;
return Vector.add(
tile,
Vector.directionalProjection(wielder.direction, [0, distance]),
);
}
calculateTargets() {
this.targets = [];
const wielder = this.entity.wielder;
if (!wielder) {
return;
}
if (!wielder.is('layered')) {
return;
}
const layer = wielder.layer;
if (!layer) {
return;
}
const tile = Vector.floor(wielder.tile);
const tileset = layer.tileset;
if (!tileset) {
return;
}
if (!wielder.is('directional')) {
return;
}
const direction = wielder.direction;
const start = this.calculateTargetStart();
const width = (this.params.target.width - 1) / 2;
for (let i = -width; i < (width + 1); ++i) {
for (let j = 0; j < this.params.target.length; ++j) {
this.targets.push(Vector.add(
start,
Vector.directionalProjection(direction, [i, j]),
));
}
}
}
createTargetContext(target) {
return new Context({
item: this.entity,
target,
wielder: this.entity.wielder,
});
}
onWielderActiveSlotIndexChanged() {
this.calculateTargets();
if (AVOCADO_CLIENT) {
const wielder = this.entity.wielder;
if (!wielder) {
return;
}
if (!wielder.is('visible')) {
return;
}
if (this.entity === wielder.itemInActiveSlot()) {
wielder.container.addChild(this.guidePrimitives);
}
else {
wielder.container.removeChild(this.guidePrimitives);
}
}
}
onWielderDirectionChanged() {
this.calculateTargets();
this.renderPrimitives();
}
onWielderPositionChanged() {
this.calculateTargets();
this.repositionPrimitives();
}
renderPrimitives() {
if (AVOCADO_CLIENT) {
this.guidePrimitives.clear();
const wielder = this.entity.wielder;
if (!wielder) {
return;
}
if (!wielder.is('layered')) {
return;
}
const layer = wielder.layer;
if (!layer) {
return;
}
const tile = Vector.floor(wielder.tile);
const tileset = layer.tileset;
if (!tileset) {
return;
}
const tileSize = tileset.tileSize;
for (let i = 0; i < this.targets.length; ++i) {
const target = this.targets[i];
const relativeTarget = Vector.sub(target, tile);
const scaledRelativeTarget = Vector.mul(relativeTarget, tileSize);
this.guidePrimitives.drawRectangle(
Rectangle.compose(scaledRelativeTarget, tileSize),
Primitives.lineStyle(new Color(255, 255, 255), 1),
);
}
}
}
repositionPrimitives() {
if (AVOCADO_CLIENT) {
const wielder = this.entity.wielder;
if (!wielder) {
return;
}
if (!wielder.is('layered')) {
return;
}
this.guidePrimitives.position = Vector.scale(wielder.tileOffset, -1);
}
}
listeners() {
return {
wielderChanged: (oldWielder, newWielder) => {
if (oldWielder && oldWielder.is('visible')) {
oldWielder.off(
'activeSlotIndexChanged',
this.onWielderActiveSlotIndexChanged,
);
oldWielder.off(
'directionChanged',
this.onWielderDirectionChanged,
);
oldWielder.off(
'positionChanged',
this.onWielderPositionChanged,
);
}
if (newWielder && newWielder.is('visible')) {
newWielder.on(
'activeSlotIndexChanged',
this.onWielderActiveSlotIndexChanged,
this,
);
newWielder.on(
'directionChanged',
this.onWielderDirectionChanged,
this,
);
newWielder.on(
'positionChanged',
this.onWielderPositionChanged,
this,
);
}
this.calculateTargets();
this.renderPrimitives();
this.repositionPrimitives();
},
};
}
methods() {
return {
useTool: () => {
// Recalculate targets. TODO: Should already be done. This is a hammer.
this.calculateTargets();
// Each target gets a promise.
const promises = this.targets.map((target) => {
// Set up.
const context = this.createTargetContext(target);
let setupFinished = false;
return new TickingPromise(
(resolve) => {
this.toolSetup.once('actionsFinished', () => {
setupFinished = true;
this.toolActions.once('actionsFinished', resolve);
});
},
(elapsed, resolve) => {
// Run setup.
if (!setupFinished) {
return this.toolSetup.tick(context, elapsed);
}
// Check condition.
if (!this.toolCondition.get(context)) {
return resolve();
}
// Run actions.
this.toolActions.tick(context, elapsed);
},
);
})
return TickingPromise.all(promises);
},
};
}
}