feat: rocks and plants

This commit is contained in:
cha0s 2021-01-19 20:39:07 -06:00
parent 86aed82ccd
commit e1008f0ed5
4 changed files with 469 additions and 0 deletions

View File

@ -0,0 +1,11 @@
import {Packet} from '@latus/socket';
export default () => class TraitUpdatePlantPacket extends Packet {
static get data() {
return {
growthStage: 'uint8',
};
}
};

View File

@ -0,0 +1,123 @@
import {compile, Context} from '@avocado/behavior';
import {StateProperty, Trait} from '@avocado/traits';
import {compose} from '@latus/core';
const decorate = compose(
StateProperty('growthStage', {
track: true,
}),
);
export default (latus) => class Plant extends decorate(Trait) {
constructor() {
super();
this.growthElapsed = 0;
}
static defaultParams() {
return {
growthCondition: {
type: 'condition',
operator: 'or',
operands: [],
},
stageSpecs: {},
};
}
static defaultState() {
return {
growthStage: 0,
};
}
static describeParams() {
return {
growthCondition: {
type: 'condition',
label: 'Growth condition',
},
};
}
static describeState() {
return {
growthStage: {
type: 'number',
label: 'Growth stage',
},
};
}
acceptPacket(packet) {
if ('TraitUpdatePlant' === packet.constructor.type) {
this.entity.growthStage = packet.data.growthStage;
}
}
listeners() {
return {
growthStageChanged: (oldStage, newStage) => {
const stageSpec = this.params.stageSpecs[newStage];
this.entity.currentImage = stageSpec.image;
},
};
}
async load(json) {
await super.load(json);
this.growthCondition = compile(this.params.growthCondition, latus);
this.growthConditionContext = new Context(
{
entity: [this.entity, 'entity'],
},
latus,
);
}
methods() {
return {
growToNextStage: () => {
const {growthStage} = this.entity;
const stageSpec = this.params.stageSpecs[growthStage];
if (!('growAt' in stageSpec)) {
return;
}
this.growthElapsed = stageSpec.growAt;
this.entity.growthStage = growthStage + 1;
},
};
}
packets() {
const {growthStage} = this.stateDifferences();
return growthStage
? [[
'TraitUpdatePlant',
{
growthStage: this.state.growthStage,
},
]]
: [];
}
tick(elapsed) {
if (this.growthCondition(this.growthConditionContext)) {
const {growthStage} = this.entity;
const stageSpec = this.params.stageSpecs[growthStage];
// TODO variance
this.growthElapsed += elapsed;
if ('growAt' in stageSpec) {
if (this.growthElapsed >= stageSpec.growAt) {
this.entity.growthStage = growthStage + 1;
}
}
}
}
};

View File

@ -0,0 +1,17 @@
import {Trait} from '@avocado/traits';
export default () => class Projectile extends Trait {
// eslint-disable-next-line class-methods-use-this
hooks() {
return {
contextTypeHints: () => [
['projectile', 'entity'],
['wielder', 'entity'],
],
};
}
};

View File

@ -0,0 +1,318 @@
import {Actions, compile, Context} from '@avocado/behavior';
import {TickingPromise} from '@avocado/core';
import {Trait} from '@avocado/traits';
import {Color, Primitives} from '@avocado/graphics';
import {Rectangle, Vector} from '@avocado/math';
// Tools.
export default (latus) => class Tool extends Trait {
constructor() {
super();
if ('client' === process.env.SIDE) {
this.guidePrimitives = new Primitives();
}
this.targets = [];
}
static behaviorTypes() {
return {
useTool: {
type: 'void',
label: 'Use tool.',
},
};
}
static defaultParams() {
return {
actions: {
type: 'expressions',
expressions: [],
},
condition: {
type: 'condition',
operator: 'or',
operands: [],
},
setup: {
type: 'expressions',
expressions: [],
},
target: {
type: 'projection',
distance: 1,
length: 1,
width: 1,
},
};
}
static describeParams() {
return {
actions: {
type: 'expressions',
label: 'Actions',
},
condition: {
type: 'condition',
label: 'Condition',
},
setup: {
type: 'expressions',
label: 'Setup actions',
},
target: {
type: 'object',
label: 'Target',
},
};
}
destroy() {
if ('client' === process.env.SIDE) {
this.guidePrimitives.destroy();
}
}
calculateTargetStart() {
const {wielder} = this.entity;
if (!wielder || !wielder.is('Directional') || !wielder.is('Layered')) {
return [0, 0];
}
const tile = Vector.floor(wielder.tile);
const {distance} = this.params.target;
return Vector.add(
tile,
Vector.directionalProjection(wielder.direction, [0, distance]),
);
}
calculateTargets() {
this.targets = [];
const {wielder} = this.entity;
if (!wielder || !wielder.is('Directional') || !wielder.is('Layered')) {
return;
}
const {direction, layer} = wielder;
if (!layer) {
return;
}
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, 'entity'],
target: [target, 'vector'],
wielder: [this.entity.wielder, 'entity'],
},
latus,
);
}
async load(json) {
await super.load(json);
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.toolActions = new Actions(compile(this.params.actions, latus));
this.toolCondition = compile(this.params.condition, latus);
this.toolSetup = new Actions(compile(this.params.setup, latus));
}
onWielderActiveSlotIndexChanged() {
this.calculateTargets();
if ('client' === process.env.SIDE) {
const {wielder} = this.entity;
if (!wielder) {
return;
}
if (!wielder.container) {
return;
}
if (this.entity === wielder.itemInActiveSlot()) {
wielder.container.addChild(this.guidePrimitives);
}
else {
wielder.container.removeChild(this.guidePrimitives);
}
}
}
onWielderDirectionChanged() {
this.calculateTargets();
this.renderPrimitives();
}
onWielderAddedToLayer() {
this.calculateTargets();
this.renderPrimitives();
}
onWielderPositionChanged() {
this.calculateTargets();
this.repositionPrimitives();
}
renderPrimitives() {
if ('client' === process.env.SIDE) {
this.guidePrimitives.clear();
const {wielder} = this.entity;
if (!wielder || !wielder.is('Layered')) {
return;
}
const {layer} = wielder;
if (!layer) {
return;
}
const {tileset} = layer;
if (!tileset) {
return;
}
const tile = Vector.floor(wielder.tile);
const {tileSize} = tileset;
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 ('client' === process.env.SIDE) {
const {wielder} = this.entity;
if (!wielder) {
return;
}
if (!wielder.is('Layered')) {
return;
}
this.guidePrimitives.position = Vector.scale(wielder.tileOffset, -1);
}
}
// eslint-disable-next-line class-methods-use-this
hooks() {
return {
contextTypeHints: () => [
['item', 'entity'],
['target', 'vector'],
['wielder', 'entity'],
],
};
}
listeners() {
return {
wielderChanged: (oldWielder, newWielder) => {
if (oldWielder && oldWielder.is('Visible')) {
oldWielder.off(
'activeSlotIndexChanged',
this.onWielderActiveSlotIndexChanged,
);
oldWielder.off(
'directionChanged',
this.onWielderDirectionChanged,
);
oldWielder.off(
'addedToLayer',
this.onWielderAddedToLayer,
);
oldWielder.off(
'positionChanged',
this.onWielderPositionChanged,
);
}
if (newWielder && newWielder.is('Visible')) {
newWielder.on(
'activeSlotIndexChanged',
this.onWielderActiveSlotIndexChanged,
this,
);
newWielder.on(
'directionChanged',
this.onWielderDirectionChanged,
this,
);
newWielder.on(
'addedToLayer',
this.onWielderAddedToLayer,
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('finished', () => {
setupFinished = true;
this.toolActions.once('finished', resolve);
});
},
(elapsed, resolve) => {
// Run setup.
if (!setupFinished) {
this.toolSetup.tick(context, elapsed);
return;
}
// Check condition.
if (!this.toolCondition(context)) {
resolve();
return;
}
// Run actions.
this.toolActions.tick(context, elapsed);
},
);
});
return TickingPromise.all(promises);
},
};
}
};