From 4777a2a3a5c2d2c8f99f8e487818c38518755798 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 28 Jul 2024 18:42:28 -0500 Subject: [PATCH] fun: an actual chest --- app/ecs/component.js | 4 +- app/ecs/components/inventory.js | 32 ++++++++----- app/ecs/components/player.js | 50 +++++++++++++++++++- app/ecs/components/sprite.js | 4 +- app/ecs/entity-factory.js | 13 ++++- app/ecs/systems/inventory-closer.js | 24 ++++++++++ app/react/components/dom/bag.jsx | 1 - app/react/components/dom/bag.module.css | 3 -- app/react/components/dom/external.jsx | 27 +++++++++++ app/react/components/dom/external.module.css | 7 +++ app/react/components/dom/grid.jsx | 2 +- app/react/components/dom/hotbar.module.css | 20 -------- app/react/components/ui.jsx | 50 ++++++++++++++++---- app/server/create/ecs.js | 1 + app/server/create/homestead.js | 17 +++---- app/server/engine.js | 25 +++++++--- 16 files changed, 213 insertions(+), 67 deletions(-) create mode 100644 app/ecs/systems/inventory-closer.js create mode 100644 app/react/components/dom/external.jsx create mode 100644 app/react/components/dom/external.module.css diff --git a/app/ecs/component.js b/app/ecs/component.js index bac1f87..c050e1f 100644 --- a/app/ecs/component.js +++ b/app/ecs/component.js @@ -152,8 +152,8 @@ export default class Component { } } destroy() {} - toNet() { - return Component.constructor.filterDefaults(this); + toNet(recipient, data) { + return data || Component.constructor.filterDefaults(this); } toJSON() { return Component.constructor.filterDefaults(this); diff --git a/app/ecs/components/inventory.js b/app/ecs/components/inventory.js index a537f85..f083daa 100644 --- a/app/ecs/components/inventory.js +++ b/app/ecs/components/inventory.js @@ -137,12 +137,14 @@ export default class Inventory extends Component { } } if (swapped) { - for (const [l, r] of swapped) { + for (const [l, otherEntityId, r] of swapped) { + const otherInstance = this.get(otherEntityId); + const {$$items: $$otherItems, slots: otherSlots} = otherInstance; const tmp = [$$items[l], slots[l]]; - [$$items[l], slots[l]] = [$$items[r], slots[r]]; - [$$items[r], slots[r]] = tmp; - if ($$items[r]) { - $$items[r].slot = r; + [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]]; + [$$otherItems[r], otherSlots[r]] = tmp; + if ($$otherItems[r]) { + $$otherItems[r].slot = r; } if ($$items[l]) { $$items[l].slot = l; @@ -189,18 +191,26 @@ export default class Inventory extends Component { } } } - swapSlots(l, r) { + swapSlots(l, OtherInventory, r) { const {$$items, slots} = this; + const {$$items: $$otherItems, slots: otherSlots} = OtherInventory; const tmp = [$$items[l], slots[l]]; - [$$items[l], slots[l]] = [$$items[r], slots[r]]; - [$$items[r], slots[r]] = tmp; + [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]]; + [$$otherItems[r], otherSlots[r]] = tmp; if (undefined === slots[l]) { delete slots[l]; } - if (undefined === slots[r]) { - delete slots[r]; + if (undefined === otherSlots[r]) { + delete otherSlots[r]; } - Component.markChange(this.entity, 'swapped', [[l, r]]); + Component.markChange(this.entity, 'swapped', [[l, OtherInventory.entity, r]]); + Component.markChange(OtherInventory.entity, 'swapped', [[r, this.entity, l]]); + } + toNet(recipient, data) { + if (recipient.id !== this.entity && this !== recipient.Player.openInventory) { + return {}; + } + return super.toNet(data); } } } diff --git a/app/ecs/components/player.js b/app/ecs/components/player.js index 0bb6007..6885935 100644 --- a/app/ecs/components/player.js +++ b/app/ecs/components/player.js @@ -1,3 +1,51 @@ import Component from '@/ecs/component.js'; -export default class Player extends Component {} +const State = { + CLOSED: 0, + OPENING: 1, + OPEN: 2, + CLOSING: 3, +}; + +export default class Player extends Component { + instanceFromSchema() { + const {ecs} = this; + return class PlayerInstance extends super.instanceFromSchema() { + $$openInventory; + $$openInventoryState = State.CLOSED; + closeInventory() { + this.$$openInventoryState = State.CLOSING; + } + updateAttachments(update) { + if (!this.$$openInventory) { + return; + } + if (![State.OPENING, State.CLOSING].includes(this.$$openInventoryState)) { + return; + } + if (!ecs.get(this.$$openInventory.entity)) { + return; + } + if (!update[this.$$openInventory.entity]) { + update[this.$$openInventory.entity] = {}; + } + if (this.$$openInventoryState === State.OPENING) { + update[this.$$openInventory.entity].Inventory = this.$$openInventory.toNet(ecs.get(this.entity)) + this.$$openInventoryState = State.OPEN; + } + else { + update[this.$$openInventory.entity].Inventory = {closed: true}; + this.$$openInventory = undefined; + this.$$openInventoryState = State.CLOSED; + } + } + get openInventory() { + return this.$$openInventory; + } + set openInventory(Inventory) { + this.$$openInventoryState = State.OPENING; + this.$$openInventory = Inventory; + } + }; + } +} diff --git a/app/ecs/components/sprite.js b/app/ecs/components/sprite.js index 33c54c9..96b76d0 100644 --- a/app/ecs/components/sprite.js +++ b/app/ecs/components/sprite.js @@ -58,9 +58,9 @@ export default class Sprite extends Component { get scale() { return {x: this.scaleX, y: this.scaleY}; } - toNet() { + toNet(recipient, data) { // eslint-disable-next-line no-unused-vars - const {elapsed, ...rest} = super.toNet(); + const {elapsed, ...rest} = super.toNet(recipient, data); return rest; } }; diff --git a/app/ecs/entity-factory.js b/app/ecs/entity-factory.js index 007cf1d..cc8be4e 100644 --- a/app/ecs/entity-factory.js +++ b/app/ecs/entity-factory.js @@ -43,14 +43,23 @@ export default class EntityFactory { }; } Object.defineProperties(Entity.prototype, properties); + Entity.prototype.updateAttachments = new Function('update', ` + ${ + sorted + .filter((componentName) => ( + Components[componentName].Instance.prototype.updateAttachments + )) + .map((type) => `this.${type}.updateAttachments(update)`).join('; ') + } + `); Entity.prototype.toJSON = new Function('', ` return { ${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')} }; `); - Entity.prototype.toNet = new Function('', ` + Entity.prototype.toNet = new Function('recipient', ` return { - ${sorted.map((type) => `${type}: this.${type}.toNet()`).join(', ')} + ${sorted.map((type) => `${type}: this.${type}.toNet(recipient)`).join(', ')} }; `); walk.class = Entity; diff --git a/app/ecs/systems/inventory-closer.js b/app/ecs/systems/inventory-closer.js new file mode 100644 index 0000000..0aaa73a --- /dev/null +++ b/app/ecs/systems/inventory-closer.js @@ -0,0 +1,24 @@ +import {System} from '@/ecs/index.js'; +import {distance} from '@/util/math.js'; + +export default class InventoryCloser extends System { + + static queries() { + return { + default: ['Player'], + }; + } + + tick() { + for (const {Player, Position} of this.select('default')) { + const {openInventory} = Player; + if (openInventory) { + const {Position: inventoryPosition} = this.ecs.get(openInventory.entity); + if (distance(Position, inventoryPosition) > 64) { + Player.closeInventory(); + } + } + } + } + +} diff --git a/app/react/components/dom/bag.jsx b/app/react/components/dom/bag.jsx index de0044d..3019e91 100644 --- a/app/react/components/dom/bag.jsx +++ b/app/react/components/dom/bag.jsx @@ -15,7 +15,6 @@ export default function Bag({ className={styles.bag} style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}} > - + + + ); +} diff --git a/app/react/components/dom/external.module.css b/app/react/components/dom/external.module.css new file mode 100644 index 0000000..1b717b0 --- /dev/null +++ b/app/react/components/dom/external.module.css @@ -0,0 +1,7 @@ +.external { + left: 20px; + opacity: 1; + position: absolute; + top: 274px; + transition: top 150ms, opacity 200ms; +} diff --git a/app/react/components/dom/grid.jsx b/app/react/components/dom/grid.jsx index 12ffd60..1a33b49 100644 --- a/app/react/components/dom/grid.jsx +++ b/app/react/components/dom/grid.jsx @@ -49,7 +49,7 @@ export default function Grid({ )); return (
-

{label}

+

 {label} 

{ async function setEcsStuff() { const {default: Components} = await import('@/ecs/components/index.js'); @@ -333,8 +335,8 @@ function Ui({disconnected}) { if (update.MainEntity) { setMainEntity(localMainEntity = id); } - if (localMainEntity === id) { - if (update.Inventory) { + if (update.Inventory) { + if (localMainEntity === id) { setBufferSlot(entity.Inventory.item(0)); const newInventorySlots = emptySlots(); for (let i = 1; i < 41; ++i) { @@ -342,6 +344,25 @@ function Ui({disconnected}) { } setInventorySlots(newInventorySlots); } + else if (update.Inventory.slots) { + const newInventorySlots = Array(30).fill(undefined); + for (let i = 0; i < 30; ++i) { + newInventorySlots[i] = entity.Inventory.item(i); + } + setExternalInventory(entity.id) + setExternalInventorySlots(newInventorySlots); + setIsInventoryOpen(true); + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + } + else if (update.Inventory.closed) { + setExternalInventory(); + setExternalInventorySlots(); + } + } + if (localMainEntity === id) { if (update.Wielder && 'activeSlot' in update.Wielder) { setActiveSlot(update.Wielder.activeSlot); } @@ -355,7 +376,7 @@ function Ui({disconnected}) { setCamera({x, y}); } } - }, [camera, ecs, mainEntity, scale]); + }, [camera, ecs, hotbarHideHandle, mainEntity, scale]); useEffect(() => { function onContextMenu(event) { event.preventDefault(); @@ -507,7 +528,7 @@ function Ui({disconnected}) { keepHotbarOpen(); client.send({ type: 'Action', - payload: {type: 'swapSlots', value: [0, i + 1]}, + payload: {type: 'swapSlots', value: [0, mainEntity, i + 1]}, }); }} slots={inventorySlots.slice(0, 10)} @@ -515,14 +536,25 @@ function Ui({disconnected}) { { - keepHotbarOpen(); client.send({ type: 'Action', - payload: {type: 'swapSlots', value: [0, i + 11]}, + payload: {type: 'swapSlots', value: [0, mainEntity, i + 11]}, }); }} - slots={inventorySlots.slice(10)} + slots={inventorySlots.slice(10, 20)} /> + {externalInventory && ( + { + client.send({ + type: 'Action', + payload: {type: 'swapSlots', value: [0, externalInventory, i]}, + }); + }} + slots={externalInventorySlots} + /> + )} { const System = ecs.system(defaultSystem); diff --git a/app/server/create/homestead.js b/app/server/create/homestead.js index 0d8c8c8..1775aab 100644 --- a/app/server/create/homestead.js +++ b/app/server/create/homestead.js @@ -98,13 +98,14 @@ export default async function createHomestead(id) { Interactive: { interacting: 1, interactScript: ` - subject.Interlocutor.dialogue({ - body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!", - monopolizer: true, - offset: {x: 0, y: -48}, - origin: 'track', - position: 'track', - }) + initiator.Player.openInventory = subject.Inventory; + // subject.Interlocutor.dialogue({ + // body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!", + // monopolizer: true, + // offset: {x: 0, y: -48}, + // origin: 'track', + // position: 'track', + // }) `, }, Interlocutor: {}, @@ -132,7 +133,7 @@ export default async function createHomestead(id) { Sprite: { anchorX: 0.5, anchorY: 0.7, - source: '/assets/chest.json', + source: '/assets/chest/chest.json', }, Ticking: {}, VisibleAabb: {}, diff --git a/app/server/engine.js b/app/server/engine.js index ac07fce..ef8d4be 100644 --- a/app/server/engine.js +++ b/app/server/engine.js @@ -201,7 +201,11 @@ export default class Engine { } case 'swapSlots': { if (!Controlled.locked) { - Inventory.swapSlots(...payload.value); + const [l, other, r] = payload.value; + const {Inventory: OtherInventory} = ecs.get(other); + if (OtherInventory) { + Inventory.swapSlots(l, OtherInventory, r); + } } break; } @@ -413,7 +417,6 @@ export default class Engine { updateFor(connection) { const update = {}; const {entity, memory} = this.connectedPlayers.get(connection); - const mainEntityId = entity.id; const ecs = this.ecses[entity.Ecs.path]; // Entities within half a screen offscreen. const x0 = entity.Position.x - RESOLUTION.x; @@ -429,17 +432,24 @@ export default class Engine { nearby.add(master); const lastNearby = new Set(memory.nearby.values()); const firstUpdate = 0 === lastNearby.size; - for (const entity of nearby) { - const {id} = entity; + for (const nearbyEntity of nearby) { + const {id} = nearbyEntity; lastNearby.delete(id); if (!memory.nearby.has(id)) { - update[id] = entity.toNet(); - if (mainEntityId === id) { + update[id] = nearbyEntity.toNet(entity); + if (entity.id === id) { update[id].MainEntity = {}; } } else if (ecs.diff[id]) { - update[id] = ecs.diff[id]; + const nearbyEntityDiff = {}; + for (const componentName in ecs.diff[id]) { + nearbyEntityDiff[componentName] = nearbyEntity[componentName].toNet( + entity, + ecs.diff[id][componentName], + ); + } + update[id] = nearbyEntityDiff; } memory.nearby.add(id); } @@ -447,6 +457,7 @@ export default class Engine { memory.nearby.delete(id); update[id] = false; } + entity.updateAttachments(update); // Tile layer chunking const {TileLayers} = master; const {layers} = TileLayers;