diff --git a/client/app.js b/client/app.js index 9b36abf..2d2da83 100644 --- a/client/app.js +++ b/client/app.js @@ -357,6 +357,17 @@ export class App extends decorate(class {}) { this.stage.camera = camera; // Avoid the initial 'lerp. camera.realPosition = camera.position; + // Manage held item. + entity.on('activeSlotIndexChanged', (oldIndex, newIndex) => { + const oldItem = entity.itemInSlot(oldIndex); + if (oldItem) { + oldItem.wielder = null; + } + const newItem = entity.itemInSlot(newIndex); + if (newItem) { + newItem.wielder = entity; + } + }); } } } diff --git a/common/traits/item.trait.js b/common/traits/item.trait.js index 3743772..9eaa747 100644 --- a/common/traits/item.trait.js +++ b/common/traits/item.trait.js @@ -1,9 +1,15 @@ import {behaviorItemFromJSON} from '@avocado/behavior'; -import {compose, TickingPromise} from '@avocado/core'; +import {compose, Property, TickingPromise} from '@avocado/core'; import {StateProperty, Trait} from '@avocado/entity'; import {Image} from '@avocado/graphics'; const decorate = compose( + Property('wielder', { + track: true, + emit: function (...args) { + this.entity.emit(...args); + }, + }), StateProperty('qty', { track: true, }), diff --git a/common/traits/tool.trait.js b/common/traits/tool.trait.js new file mode 100644 index 0000000..155f119 --- /dev/null +++ b/common/traits/tool.trait.js @@ -0,0 +1,181 @@ +import {compose} 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 { + 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 = []; + } + + 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]), + )); + } + } + } + + onWielderDirectionChanged() { + this.calculateTargets(); + this.renderPrimitives(); + } + + onWielderPositionChanged() { + this.repositionPrimitives(); + } + + renderPrimitives() { + 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() { + 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( + 'positionChanged', + this.onWielderPositionChanged, + ); + oldWielder.off( + 'directionChanged', + this.onWielderDirectionChanged, + ); + oldWielder.container.removeChild(this.guidePrimitives); + } + if (newWielder && newWielder.is('visible')) { + newWielder.on( + 'positionChanged', + this.onWielderPositionChanged, + this, + ); + newWielder.on( + 'directionChanged', + this.onWielderDirectionChanged, + this, + ); + newWielder.container.addChild(this.guidePrimitives); + } + this.calculateTargets(); + this.renderPrimitives(); + this.repositionPrimitives(); + }, + + }; + } + +} + diff --git a/resource/hoe.png b/resource/hoe.png new file mode 100644 index 0000000..a0de610 Binary files /dev/null and b/resource/hoe.png differ diff --git a/server/create-entity-for-connection.js b/server/create-entity-for-connection.js index d3a77de..e7411ea 100644 --- a/server/create-entity-for-connection.js +++ b/server/create-entity-for-connection.js @@ -70,6 +70,10 @@ export function createEntityForConnection(socket) { qty: 1, uri: '/rock.entity.json', }, + 2: { + qty: 1, + uri: '/hoe.entity.json', + }, }, }, }, diff --git a/server/create-fixtures.js b/server/create-fixtures.js index 0655124..c5b28f2 100644 --- a/server/create-fixtures.js +++ b/server/create-fixtures.js @@ -30,6 +30,8 @@ import {potionJSON} from './fixtures/potion.entity'; writeFixture('potion.entity.json', potionJSON()); import {rockJSON} from './fixtures/rock.entity'; writeFixture('rock.entity.json', rockJSON()); +import {hoeJSON} from './fixtures/hoe.entity'; +writeFixture('hoe.entity.json', hoeJSON()); // Write rooms. import {kittyFireJSON} from './fixtures/kitty-fire.room'; writeFixture('kitty-fire.room.json', kittyFireJSON()); diff --git a/server/fixtures/hoe.entity.js b/server/fixtures/hoe.entity.js new file mode 100644 index 0000000..af5d418 --- /dev/null +++ b/server/fixtures/hoe.entity.js @@ -0,0 +1,25 @@ +// Hoe. +export function hoeJSON() { + return { + traits: { + existent: {}, + item: { + params: { + slotImages: { + default: '/hoe.png', + }, + }, + }, + tool: { + params: { + target: { + type: 'projection', + distance: 1, + length: 3, + width: 1, + }, + }, + }, + }, + }; +}