diff --git a/app/ecs/components/inventory.js b/app/ecs/components/inventory.js index 2dbb4cd..633efb1 100644 --- a/app/ecs/components/inventory.js +++ b/app/ecs/components/inventory.js @@ -91,6 +91,9 @@ class ItemProxy { get icon() { return this.json.icon; } + get label() { + return this.json.label; + } get projection() { return this.json.projection; } diff --git a/app/react/components/dom/bag.jsx b/app/react/components/dom/bag.jsx new file mode 100644 index 0000000..f4e979e --- /dev/null +++ b/app/react/components/dom/bag.jsx @@ -0,0 +1,54 @@ +import styles from './bag.module.css'; +import Slot from './slot.jsx'; + +/** + * Inventory bag. 10-40 slots of inventory. + */ +export default function Bag({ + isInventoryOpen, + slots, +}) { + const Slots = slots.map((slot, i) => ( +
+ { + // onActivate(i) + // event.stopPropagation(); + // }} + // onMouseUp={(event) => { + // event.stopPropagation(); + // }} + // onDragOver={(event) => { + // event.preventDefault(); + // }} + // onDragStart={(event) => { + // if (!slot) { + // event.preventDefault(); + // } + // event.dataTransfer.setData('silphius/item', i); + // onActivate(i); + // }} + // onDrop={(event) => { + // event.preventDefault(); + // onActivate(i); + // }} + qty={slot?.qty} + /> +
+ )); + return ( +
+ {Slots} +
+ ); +} diff --git a/app/react/components/dom/bag.module.css b/app/react/components/dom/bag.module.css new file mode 100644 index 0000000..ba2e9d1 --- /dev/null +++ b/app/react/components/dom/bag.module.css @@ -0,0 +1,31 @@ +.bag { + align-self: left; + --border: calc(var(--unit) * 3px); + background-color: rgba(02, 02, 28, 0.6); + border: var(--border) solid #444444; + box-sizing: border-box; + display: inline-block; + left: calc(var(--unit) * 20px); + line-height: 0; + position: absolute; + top: calc(var(--unit) * 90px); + transition: left 150ms; + max-width: 430.5px; +} + +.slotWrapper { + border: var(--border) solid #999999; + box-sizing: border-box; + display: inline-block; + line-height: 0; + padding: 0; + &:not(:nth-child(10n)) { + border-right: none; + } + &:not(:nth-last-of-type(-n+10)) { + border-bottom: none; + } + &:hover { + background-color: rgba(0, 0, 0, 0.2); + } +} diff --git a/app/react/components/dom/chat/chat.module.css b/app/react/components/dom/chat/chat.module.css index 83333f3..b5262fb 100644 --- a/app/react/components/dom/chat/chat.module.css +++ b/app/react/components/dom/chat/chat.module.css @@ -9,8 +9,3 @@ user-select: text; width: 100%; } - -@font-face { - font-family: "Cookbook"; - src: url("/assets/fonts/Cookbook.woff") format("woff"); -} diff --git a/app/react/components/dom/chat/input.module.css b/app/react/components/dom/chat/input.module.css index 342c9bb..e3a9f2b 100644 --- a/app/react/components/dom/chat/input.module.css +++ b/app/react/components/dom/chat/input.module.css @@ -5,7 +5,7 @@ background-color: #00000044; border: 1px solid #333333; color: #ffffff; - font-family: "Cookbook"; + font-family: Cookbook, Georgia, 'Times New Roman', Times, serif; font-size: 16px; margin: 4px; padding: 0; diff --git a/app/react/components/dom/dialogues.module.css b/app/react/components/dom/dialogues.module.css index cc4b6f2..8d6c086 100644 --- a/app/react/components/dom/dialogues.module.css +++ b/app/react/components/dom/dialogues.module.css @@ -2,8 +2,3 @@ font-family: Cookbook, Georgia, 'Times New Roman', Times, serif; font-size: 22px; } - -@font-face { - font-family: "Cookbook"; - src: url("/assets/fonts/Cookbook.woff") format("woff"); -} diff --git a/app/react/components/dom/hotbar.jsx b/app/react/components/dom/hotbar.jsx index 1ac516a..fdb4b08 100644 --- a/app/react/components/dom/hotbar.jsx +++ b/app/react/components/dom/hotbar.jsx @@ -4,7 +4,12 @@ import Slot from './slot.jsx'; /** * The hotbar. 10 slots of inventory with an active selection. */ -export default function Hotbar({active, onActivate, slots}) { +export default function Hotbar({ + active, + hotbarIsHidden, + onActivate, + slots, +}) { const Slots = slots.map((slot, i) => (
+

{slots[active] && slots[active].label}

{Slots}
); diff --git a/app/react/components/dom/hotbar.module.css b/app/react/components/dom/hotbar.module.css index 87cad00..77819a9 100644 --- a/app/react/components/dom/hotbar.module.css +++ b/app/react/components/dom/hotbar.module.css @@ -1,13 +1,32 @@ .hotbar { - align-self: center; + align-self: left; --border: calc(var(--unit) * 3px); - background-color: rgba(0, 0, 0, 0.2); + background-color: rgba(02, 02, 57, 0.6); border: var(--border) solid #444444; box-sizing: border-box; display: inline-block; + left: calc(var(--unit) * 20px); line-height: 0; position: absolute; - top: calc(var(--unit) * 25px); + top: calc(var(--unit) * 20px); + transition: top 150ms; +} + +.label { + background-color: transparent; + color: white; + font-family: Cookbook, Georgia, 'Times New Roman', Times, serif; + left: 50%; + margin: 0; + position: absolute; + text-shadow: + 0px -1px 0px black, + 1px 0px 0px black, + 0px 1px 0px black, + -1px 0px 0px black + ; + top: -17.5px; + transform: translateX(-50%); } .slotWrapper { diff --git a/app/react/components/ui.jsx b/app/react/components/ui.jsx index 8edb39e..02c2da4 100644 --- a/app/react/components/ui.jsx +++ b/app/react/components/ui.jsx @@ -11,6 +11,7 @@ import addKeyListener from './add-key-listener.js'; import ClientEcs from './client-ecs.js'; import Disconnected from './dom/disconnected.jsx'; import Chat from './dom/chat/chat.jsx'; +import Bag from './dom/bag.jsx'; import Dom from './dom/dom.jsx'; import Entities from './dom/entities.jsx'; import HotBar from './dom/hotbar.jsx'; @@ -18,6 +19,11 @@ import Pixi from './pixi/pixi.jsx'; import Devtools from './devtools.jsx'; import styles from './ui.module.css'; +const KEY_MAP = { + keyDown: 1, + keyUp: 0, +}; + function emptySlots() { return Array(10).fill(undefined); } @@ -50,6 +56,9 @@ function Ui({disconnected}) { const [chatHistoryCaret, setChatHistoryCaret] = useState(-1); const [chatMessages, setChatMessages] = useState({}); const [pendingMessage, setPendingMessage] = useState(''); + const [hotbarIsHidden, setHotbarIsHidden] = useState(true); + const [hotbarHideHandle, setHotbarHideHandle] = useState(); + const [isInventoryOpen, setIsInventoryOpen] = useState(false); useEffect(() => { async function setEcsStuff() { const {default: Components} = await import('@/ecs/components/index.js'); @@ -76,6 +85,44 @@ function Ui({disconnected}) { clearTimeout(handle); }; }, [disconnected]); + useEffect(() => { + return addKeyListener(document.body, ({type, payload}) => { + if (chatInputRef.current) { + chatInputRef.current.focus(); + } + if (chatIsOpen) { + return; + } + let actionPayload; + switch (payload) { + case 'w': { + actionPayload = {type: 'moveUp', value: KEY_MAP[type]}; + break; + } + case 'a': { + actionPayload = {type: 'moveLeft', value: KEY_MAP[type]}; + break; + } + case 's': { + actionPayload = {type: 'moveDown', value: KEY_MAP[type]}; + break; + } + case 'd': { + actionPayload = {type: 'moveRight', value: KEY_MAP[type]}; + break; + } + } + if (actionPayload) { + client.send({ + type: 'Action', + payload: actionPayload, + }); + } + }); + }, [ + chatIsOpen, + client, + ]); useEffect(() => { return addKeyListener(document.body, ({event, type, payload}) => { if ('Escape' === payload && 'keyDown' === type && chatIsOpen) { @@ -88,10 +135,6 @@ function Ui({disconnected}) { if (chatIsOpen) { return; } - const KEY_MAP = { - keyDown: 1, - keyUp: 0, - }; let actionPayload; switch (payload) { case '-': @@ -123,26 +166,25 @@ function Ui({disconnected}) { } break; } - case 'w': { - actionPayload = {type: 'moveUp', value: KEY_MAP[type]}; - break; - } - case 'a': { - actionPayload = {type: 'moveLeft', value: KEY_MAP[type]}; - break; - } - case 's': { - actionPayload = {type: 'moveDown', value: KEY_MAP[type]}; - break; - } - case 'd': { - actionPayload = {type: 'moveRight', value: KEY_MAP[type]}; - break; - } case ' ': { actionPayload = {type: 'use', value: KEY_MAP[type]}; break; } + case 'Tab': { + if ('keyDown' === type) { + if (isInventoryOpen) { + setHotbarIsHidden(true); + } + else { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + } + setIsInventoryOpen(!isInventoryOpen); + } + break + } case 'Enter': { if ('keyDown' === type) { setChatIsOpen(true); @@ -161,60 +203,150 @@ function Ui({disconnected}) { } case '1': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 1}; } break; } case '2': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 2}; } break; } case '3': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 3}; } break; } case '4': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 4}; } break; } case '5': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 5}; } break; } case '6': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 6}; } break; } case '7': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 7}; } break; } case '8': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 8}; } break; } case '9': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 9}; } break; } case '0': { if ('keyDown' === type) { + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } actionPayload = {type: 'changeSlot', value: 10}; } break; @@ -227,7 +359,17 @@ function Ui({disconnected}) { }); } }); - }, [chatIsOpen, client, debug, devtoolsIsOpen, monopolizers, setDebug, setScale]); + }, [ + chatIsOpen, + client, + debug, + devtoolsIsOpen, + hotbarHideHandle, + isInventoryOpen, + monopolizers, + setDebug, + setScale, + ]); usePacket('EcsChange', async () => { setEcs(new ClientEcs({Components, Systems})); setMainEntity(undefined); @@ -387,6 +529,15 @@ function Ui({disconnected}) { event.preventDefault(); return; } + if (!isInventoryOpen) { + setHotbarIsHidden(false); + if (hotbarHideHandle) { + clearTimeout(hotbarHideHandle); + } + setHotbarHideHandle(setTimeout(() => { + setHotbarIsHidden(true); + }, 4000)); + } if (event.deltaY > 0) { client.send({ type: 'Action', @@ -411,6 +562,7 @@ function Ui({disconnected}) { { client.send({ type: 'Action', @@ -419,6 +571,10 @@ function Ui({disconnected}) { }} slots={hotbarSlots} /> +