feat: rocks and plants
This commit is contained in:
parent
86aed82ccd
commit
e1008f0ed5
11
packages/core/src/packets/trait-update-plant.js
Normal file
11
packages/core/src/packets/trait-update-plant.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import {Packet} from '@latus/socket';
|
||||
|
||||
export default () => class TraitUpdatePlantPacket extends Packet {
|
||||
|
||||
static get data() {
|
||||
return {
|
||||
growthStage: 'uint8',
|
||||
};
|
||||
}
|
||||
|
||||
};
|
123
packages/core/src/traits/plant.js
Normal file
123
packages/core/src/traits/plant.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
17
packages/core/src/traits/projectile.js
Normal file
17
packages/core/src/traits/projectile.js
Normal 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'],
|
||||
],
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
318
packages/core/src/traits/tool.js
Normal file
318
packages/core/src/traits/tool.js
Normal 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);
|
||||
},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
Loading…
Reference in New Issue
Block a user