diff --git a/client/app.js b/client/app.js index 4f9309a..9b36abf 100644 --- a/client/app.js +++ b/client/app.js @@ -251,9 +251,20 @@ export class App extends decorate(class {}) { const slotIndex = (parseInt(key) + 9) % 10; this.setActiveSlotIndex(slotIndex); break; + case 'ArrowLeft': + if (this.selfEntity) { + this.actionRegistry.state = this.actionRegistry.state.set( + 'UseItem', + this.selfEntity.activeSlotIndex + ); + } + break; } } + onKeyUp(key) { + } + onPacket(packet) { if (!this.hasReceivedState) { this.renderIntoDom(document.querySelector('.app')).then(() => { @@ -468,8 +479,10 @@ export class App extends decorate(class {}) { startProcessingInput() { const config = this.readConfig(); - // Pointer input. + // Key input. this.stage.on('keyDown', this.onKeyDown, this); + this.stage.on('keyUp', this.onKeyUp, this); + // Pointer input. this.stage.on('pointerDown', this.onPointerDown, this); this.stage.on('pointerMove', this.onPointerMove, this); this.stage.on('pointerUp', this.onPointerUp, this); @@ -486,6 +499,9 @@ export class App extends decorate(class {}) { if (this.actionState !== this.actionRegistry.state) { this.actionState = this.actionRegistry.state; this.socket.send(InputPacket.fromState(this.actionState)); + this.actionRegistry.state = this.actionRegistry.state.delete( + 'UseItem' + ); } }, 1000 * config.inputFrequency); // Mouse/touch movement. diff --git a/client/ui/hooks/use-event.js b/client/ui/hooks/use-event.js new file mode 100644 index 0000000..f725462 --- /dev/null +++ b/client/ui/hooks/use-event.js @@ -0,0 +1,18 @@ +// 3rd party. +import React, {useEffect} from 'react'; + +export function useEvent(object, eventName, fn) { + useEffect(() => { + if (!object) { + return; + } + const onEvent = (...args) => { + fn(...args); + }; + onEvent(); + object.on(eventName, onEvent); + return () => { + object.off(eventName, onEvent); + }; + }, [object]); +} diff --git a/client/ui/menu/hotbar.js b/client/ui/menu/hotbar.js index 2eb5963..e7dbd4f 100644 --- a/client/ui/menu/hotbar.js +++ b/client/ui/menu/hotbar.js @@ -1,10 +1,12 @@ // 3rd party. import classnames from 'classnames'; +import * as I from 'immutable'; import React, {useEffect, useState} from 'react'; // 2nd party. import {compose} from '@avocado/core'; import contempo from 'contempo'; // 1st party. +import {useEvent} from '../hooks/use-event'; import {usePropertyChange} from '../hooks/use-property-change'; import ItemSlot from './item-slot'; @@ -43,49 +45,34 @@ const decorate = compose( const HotbarComponent = ({app}) => { const selfEntity = usePropertyChange(app, 'selfEntity'); const activeSlotIndex = usePropertyChange(selfEntity, 'activeSlotIndex'); - const [slotImageUris, setSlotImageUris] = useState({}); - useEffect(() => { - if (!selfEntity) { - return; - } - const onInventoryChanged = () => { - const imageUris = {}; - const itemPromises = []; - for (let i = 0; i < 10; i++) { + const [items, setItems] = useState(I.List()); + useEvent(selfEntity, 'inventoryChanged', () => { + setItems(items.withMutations((items) => { + for (let i = 0; i < 10; ++i) { const item = selfEntity.itemInSlot(i); if (!item) { continue; } - itemPromises.push(item.then((item) => { - return item.hydrate().then(() => { - const image = item.imageForSlot(0); - if (image) { - imageUris[i] = image.uri; - } - }); + if (!items.has(i)) { + items.set(i, I.Map()); + } + items.set(i, items.get(i).withMutations((listItem) => { + listItem.set('backgroundImage', item.imageForSlot()); + listItem.set('qty', item.qty); })); } - Promise.all(itemPromises).then(() => { - setSlotImageUris(imageUris); - }) - }; - selfEntity.on('inventoryChanged', onInventoryChanged); - onInventoryChanged(); - return () => { - selfEntity.off('inventoryChanged', onInventoryChanged); - }; - }, [selfEntity]); - + })); + }); const hotkeyForSlot = (index) => { return (index + 1) % 10; } const slots = []; for (let i = 0; i < 10; ++i) { const slot = { if (selfEntity) { diff --git a/client/ui/menu/item-slot.js b/client/ui/menu/item-slot.js index 2b36249..92fba8d 100644 --- a/client/ui/menu/item-slot.js +++ b/client/ui/menu/item-slot.js @@ -1,6 +1,6 @@ // 3rd party. import classnames from 'classnames'; -import React from 'react'; +import React, {useEffect, useState} from 'react'; // 2nd party. import {compose} from '@avocado/core'; import contempo from 'contempo'; @@ -30,12 +30,62 @@ const decorate = compose( background-size: contain; width: 100%; height: 100%; + + image-rendering: optimizeSpeed; /* Older versions of FF */ + image-rendering: -moz-crisp-edges; /* FF 6.0+ */ + image-rendering: -webkit-optimize-contrast; /* Safari */ + image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */ + image-rendering: pixelated; /* Awesome future-browsers */ + -ms-interpolation-mode: nearest-neighbor; /* IE */ } +.item-slot-inner .qty { + color: white; + text-shadow: 0.25px 0.25px 0 black; + font-family: joystix; + position: absolute; + bottom: 0px; + right: 0.5px; + font-size: 5px; + line-height: 5px; +} +.qty.e3 { + font-size: 4px; +} +.qty.e4 { + font-size: 3px; + bottom: -1px; +} + `), ); const ItemSlotComponent = (props) => { - const {backgroundImage, children, className, ...rest} = props; + const {children, className, item, ...rest} = props; + let backgroundImageUri; + let qty; + let qtyClass; + if (item) { + const backgroundImage = item.get('backgroundImage'); + if (backgroundImage) { + backgroundImageUri = backgroundImage.uri; + } + qty = item.get('qty'); + if (qty > 9999) { + qtyClass = 'e4'; + } + else if (qty > 999) { + qtyClass = 'e3'; + } + else if (qty > 99) { + qtyClass = 'e2'; + } + else if (qty > 9) { + qtyClass = 'e1'; + } + else { + qtyClass = 'e0'; + } + } return
{ >
{children}
+ style={{ + backgroundImage: backgroundImageUri && `url(${backgroundImageUri})`, + }} + > + {children} + {qty &&
{qty}
} +
; } diff --git a/common/traits/controllable.trait.js b/common/traits/controllable.trait.js index f8023dc..da82d93 100644 --- a/common/traits/controllable.trait.js +++ b/common/traits/controllable.trait.js @@ -16,6 +16,12 @@ export class Controllable extends Trait { set inputState(inputState) { this._inputState = I.Map(inputState); + if (AVOCADO_SERVER) { + if (inputState.has('UseItem')) { + const slotIndex = inputState.get('UseItem'); + this.entity.useItemInSlot(slotIndex); + } + } } tick(elapsed) { diff --git a/common/traits/item.trait.js b/common/traits/item.trait.js new file mode 100644 index 0000000..3743772 --- /dev/null +++ b/common/traits/item.trait.js @@ -0,0 +1,76 @@ +import {behaviorItemFromJSON} from '@avocado/behavior'; +import {compose, TickingPromise} from '@avocado/core'; +import {StateProperty, Trait} from '@avocado/entity'; +import {Image} from '@avocado/graphics'; + +const decorate = compose( + StateProperty('qty', { + track: true, + }), +); + +export class Item extends decorate(Trait) { + + static defaultParams() { + return { + itemActions: { + type: 'actions', + traversals: [], + }, + slotImages: { + default: '/question-mark.png', + }, + }; + } + + static defaultState() { + return { + qty: 1, + }; + } + + static type() { + return 'item'; + } + + constructor(entity, params, state) { + super(entity, params, state); + this._itemActions = behaviorItemFromJSON(this.params.itemActions); + this._slotImages = {}; + } + + hydrate() { + const promises = []; + for (const index in this.params.slotImages) { + promises.push(Image.load(this.params.slotImages[index]).then((image) => { + this._slotImages[index] = image; + })); + } + return Promise.all(promises); + } + + get itemActions() { + return this._itemActions; + } + + methods() { + return { + + decrementQuantity: (amount = 1) => { + this.entity.qty -= amount; + }, + + imageForSlot: () => { + const qty = this.entity.qty; + if (this._slotImages[qty]) { + return this._slotImages[qty]; + } + else { + return this._slotImages['default']; + } + }, + + }; + } + +} diff --git a/common/traits/receptacle.trait.js b/common/traits/receptacle.trait.js new file mode 100644 index 0000000..aaa087b --- /dev/null +++ b/common/traits/receptacle.trait.js @@ -0,0 +1,208 @@ +import {compose, TickingPromise} from '@avocado/core'; +import {Entity, StateProperty, Trait} from '@avocado/entity'; + +import {TraitItemQtyPacket} from './trait-item-qty.packet'; +import {TraitItemSwapPacket} from './trait-item-swap.packet'; + +const decorate = compose( + StateProperty('slotCount', { + track: true, + }), +); + +const NULL_SLOT = 65535; +const AUTO_SLOT = 65535; + +// TODO more localized events; inventoryChanged is too noisy +export class Receptacle extends decorate(Trait) { + + static defaultParams() { + return { + slots: {}, + }; + } + + static defaultState() { + return { + slotCount: 10, + }; + } + + static type() { + return 'receptacle'; + } + + constructor(entity, params, state) { + super(entity, params, state); + this.packetUpdates = []; + this.qtyListeners = new Map(); + this.slotItems = {}; + // Load items. + for (const index in this.params.slots) { + const slotSpec = this.params.slots[index]; + this.slotItems[index] = Entity.load(slotSpec.uri).then((item) => { + // Set quantity. + item.qty = slotSpec.qty; + // On the client, hydrate the item before adding it to the inventory. + if (AVOCADO_CLIENT) { + item.hydrate().then(() => { + this.entity.addItemToSlot(item, index); + }); + } + // Server just adds it. + else { + this.entity.addItemToSlot(item, index); + } + }); + } + } + + destroy() { + for (let i = 0; i < this.entity.slotCount; ++i) { + const item = this.entity.removeItemFromSlot(i); + if (item) { + this.removeListenersForItem(item); + item.destroy(); + } + } + } + + acceptPacket(packet) { + // Quantity update. + if (packet instanceof TraitItemQtyPacket) { + const item = this.entity.itemInSlot(packet.data.slotIndex); + if (item) { + item.qty = packet.data.qty; + } + } + // Slot swap. + if (packet instanceof TraitItemSwapPacket) { + // NULL destination slot === remove. + const {firstSlotIndex, secondSlotIndex} = packet.data; + if (NULL_SLOT === secondSlotIndex) { + const item = this.entity.removeItemFromSlot(firstSlotIndex); + if (item) { + item.destroy(); + } + } + // Normal slot swap. + else { + this.entity.swapSlots(firstSlotIndex, secondSlotIndex); + } + } + } + + addListenersForItem(item) { + const listener = (oldQty, newQty) => { + if (AVOCADO_SERVER) { + const slotIndex = this.itemSlotIndex(item); + // Valid quantity; update client. + if (newQty > 0) { + this.packetUpdates.push(new TraitItemQtyPacket({ + slotIndex, + qty: item.qty, + }, this.entity)); + } + // Item was used up. + else { + this.entity.removeItemFromSlot(slotIndex); + item.destroy(); + } + } + this.entity.emit('inventoryChanged'); + }; + this.qtyListeners.set(item, listener); + item.on('qtyChanged', listener); + } + + isSlotIndexInRange(slotIndex) { + return slotIndex >= 0 && slotIndex < this.entity.slotCount; + } + + itemSlotIndex(item) { + for (let i = 0; i < this.entity.slotCount; ++i) { + if (item === this.slotItems[i]) { + return i; + } + } + return -1; + } + + // TODO + mergeItem(item) { + } + + packetsForUpdate() { + const packetsForUpdate = this.packetUpdates; + this.packetUpdates = []; + return packetsForUpdate; + } + + removeListenersForItem(item) { + item.off('qtyChanged', this.qtyListeners.get(item)); + this.qtyListeners.delete(item); + } + + methods() { + return { + + addItemToSlot: (item, slotIndex = AUTO_SLOT) => { + if (AUTO_SLOT === slotIndex) { + return this.mergeItem(item); + } + else { + this.addListenersForItem(item); + this.slotItems[slotIndex] = item; + } + this.entity.emit('inventoryChanged'); + }, + + removeItemFromSlot: (slotIndex) => { + const item = this.entity.itemInSlot(slotIndex); + if (!item) { + return; + } + this.removeListenersForItem(item); + delete this.slotItems[slotIndex]; + if (AVOCADO_SERVER) { + this.packetUpdates.push(new TraitItemSwapPacket({ + firstSlotIndex: slotIndex, + secondSlotIndex: NULL_SLOT, + }, this.entity)); + } + this.entity.emit('inventoryChanged'); + return item; + }, + + itemInSlot: (slotIndex) => { + if (this.isSlotIndexInRange(slotIndex)) { + // While it's loading, it'll be a promise. + if (this.slotItems[slotIndex] instanceof Entity) { + return this.slotItems[slotIndex]; + } + } + }, + + swapSlots: (leftIndex, rightIndex) => { + if ( + !this.isSlotIndexInRange(leftIndex) + || !this.isSlotIndexInRange(rightIndex) + ) { + return; + } + // Swap items. + const leftItem = this.entity.itemInSlot(leftIndex); + const rightItem = this.entity.itemInSlot(rightIndex); + this.removeListenersForItem(leftItem); + this.removeListenersForItem(rightItem); + this.slotItems[leftIndex] = rightItem; + this.slotItems[rightIndex] = leftItem; + this.addListenersForItem(leftItem); + this.addListenersForItem(rightItem); + }, + + }; + } + +} + diff --git a/common/traits/trait-item-qty.packet.js b/common/traits/trait-item-qty.packet.js new file mode 100644 index 0000000..6ae0608 --- /dev/null +++ b/common/traits/trait-item-qty.packet.js @@ -0,0 +1,12 @@ +import {EntityPacket} from '@avocado/entity'; + +export class TraitItemQtyPacket extends EntityPacket { + + static get schema() { + const schema = super.schema; + schema.data.slotIndex = 'uint16'; + schema.data.qty = 'uint16'; + return schema; + } + +} diff --git a/common/traits/trait-item-swap.packet.js b/common/traits/trait-item-swap.packet.js new file mode 100644 index 0000000..6ab8ded --- /dev/null +++ b/common/traits/trait-item-swap.packet.js @@ -0,0 +1,12 @@ +import {EntityPacket} from '@avocado/entity'; + +export class TraitItemSwapPacket extends EntityPacket { + + static get schema() { + const schema = super.schema; + schema.data.firstSlotIndex = 'uint16'; + schema.data.secondSlotIndex = 'uint16'; + return schema; + } + +} diff --git a/common/traits/wielder.trait.js b/common/traits/wielder.trait.js new file mode 100644 index 0000000..7fdbc9b --- /dev/null +++ b/common/traits/wielder.trait.js @@ -0,0 +1,84 @@ +import {createContext} from '@avocado/behavior'; +import {compose} from '@avocado/core'; +import {StateProperty, Trait} from '@avocado/entity'; + +const decorate = compose( + StateProperty('activeSlotIndex', { + track: true, + }), +); + +export class Wielder extends decorate(Trait) { + + static dependencies() { + return [ + 'receptacle', + ]; + } + + static defaultState() { + return { + activeSlotIndex: 0, + }; + } + + static type() { + return 'wielder'; + } + + constructor(entity, params, state) { + super(entity, params, state); + this.itemActions = undefined; + this.itemActionsContext = createContext(); + this.itemInUse = undefined; + } + + methods() { + return { + + useItemInActiveSlot: () => { + if (-1 === this.entity.activeSlotIndex) { + return; + } + this.entity.useItemInSlot(this.entity.activeSlotIndex) + }, + + useItemInSlot: (slotIndex) => { + // Already using an item? TODO: cancel on active slot change? + if (this.itemInUse) { + return; + } + const item = this.entity.itemInSlot(slotIndex); + if (!item) { + return; + } + this.itemInUse = item; + // Set up context. + this.itemActionsContext.add('user', this.entity); + this.itemActionsContext.add('item', item); + this.itemActionsContext.add('slotIndex', slotIndex); + // Keep a reference to the item. + this.itemActions = item.itemActions; + // Defer until the item actions are finished. + return new Promise((resolve) => { + this.itemActions.once('actionsFinished', () => { + // Check if the item was used up. + this.itemActions = undefined; + this.itemActionsContext.clear(); + this.itemInUse = undefined; + resolve(); + }); + }); + }, + + }; + } + + tick(elapsed) { + if (this.itemActions) { + this.itemActions.tick(this.itemActionsContext, elapsed); + } + } + +} + diff --git a/resource/kitty-fire.room.json b/resource/kitty-fire.room.json index 1c3022b..153a47a 100644 --- a/resource/kitty-fire.room.json +++ b/resource/kitty-fire.room.json @@ -1 +1 @@ -{"size":[384,384],"layers":[{"entities":[{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":608,"y":896}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":904,"y":1024}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":708,"y":604}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":896,"y":332}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":720,"y":248}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1056,"y":768}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":668,"y":800}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1088,"y":352}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":636,"y":692}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":792,"y":1136}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":980,"y":448}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1224,"y":292}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":828,"y":1192}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1308,"y":1044}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1148,"y":264}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":228,"y":1052}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":460,"y":256}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1108,"y":296}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":540,"y":580}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":740,"y":400}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1148,"y":544}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1000,"y":212}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":428,"y":1288}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":240,"y":920}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":984,"y":1224}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":220,"y":440}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1248,"y":868}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":880,"y":480}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":608,"y":1212}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":900,"y":1028}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":352,"y":1332}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":784,"y":992}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":396,"y":968}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":216,"y":1316}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":392,"y":628}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":596,"y":1080}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":552,"y":928}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":616,"y":1000}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1136,"y":220}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":696,"y":1236}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":940,"y":1032}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":964,"y":704}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1268,"y":296}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1116,"y":300}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":488,"y":940}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1044,"y":448}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1132,"y":944}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":848,"y":872}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":384,"y":544}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1020,"y":524}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":524,"y":536}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":500,"y":552}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":324,"y":652}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":964,"y":1136}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":248,"y":588}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":980,"y":1192}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":700,"y":984}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":764,"y":392}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":952,"y":1312}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":892,"y":1272}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":260,"y":720}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":360,"y":1332}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1168,"y":940}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":940,"y":780}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1196,"y":928}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":316,"y":1100}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":484,"y":996}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":316,"y":468}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":492,"y":952}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":860,"y":864}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":512,"y":368}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":916,"y":564}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":620,"y":1196}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":512,"y":1292}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":792,"y":1152}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":696,"y":908}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":232,"y":564}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":392,"y":316}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":932,"y":1312}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":504,"y":980}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":728,"y":712}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1100,"y":1144}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1284,"y":628}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":552,"y":588}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":340,"y":996}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":324,"y":1164}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1256,"y":916}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":956,"y":1176}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":636,"y":1124}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":456,"y":1276}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1104,"y":1320}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":468,"y":232}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":536,"y":1324}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1332,"y":1012}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":656,"y":828}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1284,"y":268}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1156,"y":1240}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":944,"y":1272}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":348,"y":440}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":492,"y":972}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":472,"y":528}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":992,"y":1300}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1312,"y":448}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":508,"y":960}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1136,"y":636}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1172,"y":964}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":808,"y":1320}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1292,"y":1104}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":696,"y":1124}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":212,"y":672}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":244,"y":1048}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":632,"y":936}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1052,"y":932}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":756,"y":1332}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":628,"y":1316}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":1208,"y":980}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":888,"y":540}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":232,"y":928}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":676,"y":1008}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":924,"y":804}}}}],"tiles":{"size":[24,24],"data":[1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4]},"tilesetUri":"/tileset.json"}]} \ No newline at end of file +{"size":[384,384],"layers":[{"entities":[{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":508,"y":724}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1240,"y":248}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":264,"y":724}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1116,"y":304}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1080,"y":984}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":528,"y":200}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":800,"y":476}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":368,"y":748}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":308,"y":456}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":604,"y":1096}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":584,"y":504}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":296,"y":1116}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":700,"y":888}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":596,"y":868}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1044,"y":1252}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1164,"y":396}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":548,"y":748}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":980,"y":724}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1128,"y":208}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":644,"y":720}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":848,"y":1004}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":948,"y":240}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":360,"y":1052}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1088,"y":556}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1200,"y":292}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":484,"y":484}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1224,"y":200}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":240,"y":616}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1156,"y":700}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":780,"y":736}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":608,"y":868}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":968,"y":620}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1064,"y":620}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":472,"y":1172}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":992,"y":908}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1148,"y":1132}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1044,"y":1016}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1256,"y":924}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":508,"y":884}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1296,"y":1216}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1076,"y":224}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":256,"y":840}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":308,"y":540}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1108,"y":960}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":744,"y":800}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":1284,"y":1072}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":740,"y":276}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":728,"y":472}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":324,"y":524}}}},{"uri":"/flower-barrel.entity.json","traits":{"positioned":{"state":{"x":784,"y":536}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":1016,"y":920}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":280,"y":620}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":984,"y":280}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":824,"y":984}}}},{"uri":"/mama-kitty-spawner.entity.json","traits":{"positioned":{"state":{"x":984,"y":344}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1300,"y":408}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":252,"y":436}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1304,"y":1212}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":520,"y":772}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":268,"y":1208}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":352,"y":300}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1152,"y":1036}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":808,"y":916}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":572,"y":736}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":916,"y":576}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":788,"y":1288}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":804,"y":796}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1140,"y":576}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1072,"y":1068}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":432,"y":852}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":984,"y":536}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":276,"y":528}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":424,"y":732}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":304,"y":912}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1044,"y":460}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1004,"y":1312}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1004,"y":380}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":844,"y":588}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":624,"y":980}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":572,"y":684}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1084,"y":1040}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1140,"y":980}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":728,"y":380}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":944,"y":956}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":924,"y":424}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":816,"y":316}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1320,"y":272}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":220,"y":804}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":576,"y":768}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":240,"y":1176}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":308,"y":480}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":280,"y":504}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":616,"y":384}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":524,"y":1208}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":928,"y":352}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1080,"y":720}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1328,"y":808}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1312,"y":540}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":348,"y":368}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":668,"y":1256}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1196,"y":272}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":824,"y":572}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":688,"y":1148}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":796,"y":480}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":948,"y":212}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1292,"y":1192}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":276,"y":904}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":404,"y":492}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":968,"y":276}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1276,"y":620}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1004,"y":216}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":412,"y":1184}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":1048,"y":1196}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":256,"y":1324}}}},{"uri":"/fire.entity.json","traits":{"positioned":{"state":{"x":744,"y":900}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":1328,"y":1216}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":236,"y":680}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":636,"y":776}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":696,"y":1044}}}},{"uri":"/blue-fire.entity.json","traits":{"positioned":{"state":{"x":1172,"y":624}}}}],"tiles":{"size":[24,24],"data":[1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4]},"tilesetUri":"/tileset.json"}]} \ No newline at end of file diff --git a/resource/potion.entity.json b/resource/potion.entity.json new file mode 100644 index 0000000..ed2a55c --- /dev/null +++ b/resource/potion.entity.json @@ -0,0 +1 @@ +{"traits":{"damaging":{"params":{"damageSpecs":[{"affinity":0,"lock":0,"power":-50,"variance":0.1}]}},"existent":{},"item":{"params":{"itemActions":{"type":"actions","traversals":[{"type":"traversal","steps":[{"type":"key","key":"user"},{"type":"key","key":"takeDamageFrom"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"item"}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"item"},{"type":"key","key":"decrementQuantity"},{"type":"invoke","args":[]}]},{"type":"traversal","steps":[{"type":"key","key":"global"},{"type":"key","key":"wait"},{"type":"invoke","args":[{"type":"literal","value":0.5}]}]}]},"slotImages":{"default":"/potion.png"}}}}} \ No newline at end of file diff --git a/resource/potion.png b/resource/potion.png new file mode 100644 index 0000000..c7586d2 Binary files /dev/null and b/resource/potion.png differ diff --git a/resource/question-mark.png b/resource/question-mark.png new file mode 100644 index 0000000..6d4b379 Binary files /dev/null and b/resource/question-mark.png differ diff --git a/server/create-entity-for-connection.js b/server/create-entity-for-connection.js index 73f5ce7..8018b6d 100644 --- a/server/create-entity-for-connection.js +++ b/server/create-entity-for-connection.js @@ -59,6 +59,16 @@ export function createEntityForConnection(socket) { y: y * 4, }, }, + receptacle: { + params: { + slots: { + 0: { + qty: 10, + uri: '/potion.entity.json', + }, + }, + }, + }, roomed: {}, shaped: { params: { @@ -70,6 +80,11 @@ export function createEntityForConnection(socket) { }, visible: {}, vulnerable: {}, + wielder: { + state: { + activeSlotIndex: 0, + }, + }, }, }); // Embed socket. diff --git a/server/create-fixtures.js b/server/create-fixtures.js index 9ddc2eb..2589862 100644 --- a/server/create-fixtures.js +++ b/server/create-fixtures.js @@ -25,6 +25,9 @@ import {mamaKittySpawnerJSON} from './fixtures/mama-kitty-spawner.entity'; writeFixture('mama-kitty-spawner.entity.json', mamaKittySpawnerJSON()); import {mamaKittyJSON} from './fixtures/mama-kitty.entity'; writeFixture('mama-kitty.entity.json', mamaKittyJSON()); +// Write items. +import {potionJSON} from './fixtures/potion.entity'; +writeFixture('potion.entity.json', potionJSON()); // Write rooms. import {kittyFireJSON} from './fixtures/kitty-fire.room'; writeFixture('kitty-fire.room.json', kittyFireJSON()); diff --git a/server/fixtures/potion.entity.js b/server/fixtures/potion.entity.js new file mode 100644 index 0000000..34f2fe5 --- /dev/null +++ b/server/fixtures/potion.entity.js @@ -0,0 +1,49 @@ +import {buildInvoke, buildTraversal} from '@avocado/behavior'; + +import {AFFINITY_NONE} from '../../common/combat/constants'; + +// Healing potion. +export function potionJSON() { + const causeHealing = buildInvoke(['user', 'takeDamageFrom'], [ + buildTraversal(['item']), + ]); + const decrement = buildInvoke( + ['item', 'decrementQuantity'], + ); + const cooldown = buildInvoke( + ['global', 'wait'], + [0.5], + ); + return { + traits: { + damaging: { + params: { + damageSpecs: [ + { + affinity: AFFINITY_NONE, + lock: 0, + power: -50, + variance: 0.1, + }, + ], + }, + }, + existent: {}, + item: { + params: { + itemActions: { + type: 'actions', + traversals: [ + causeHealing, + decrement, + cooldown, + ], + }, + slotImages: { + default: '/potion.png', + }, + }, + }, + }, + }; +} diff --git a/server/traits/informed.trait.js b/server/traits/informed.trait.js index 749b654..16725fe 100644 --- a/server/traits/informed.trait.js +++ b/server/traits/informed.trait.js @@ -247,6 +247,9 @@ export class Informed extends decorate(Trait) { if (0 === packets.length) { return; } + + // TODO: filter packets that are only delivered to self entity. + // Filter invisible entities. packets = this.filterInvisibleEntityPackets(packets); // Reduce entities by range.