768 lines
23 KiB
JavaScript
768 lines
23 KiB
JavaScript
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
|
|
import {useClient, usePacket} from '@/react/context/client.js';
|
|
import {useDebug} from '@/react/context/debug.js';
|
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
|
import {RESOLUTION} from '@/lib/constants.js';
|
|
import EventEmitter from '@/lib/event-emitter.js';
|
|
import {distribute} from '@/lib/inventory.js';
|
|
|
|
import addKeyListener from './add-key-listener.js';
|
|
import Disconnected from './dom/disconnected.jsx';
|
|
import Chat from './dom/chat/chat.jsx';
|
|
import Bag from './dom/bag.jsx';
|
|
import DateTime from './dom/datetime.jsx';
|
|
import Dom from './dom/dom.jsx';
|
|
import Entities from './dom/entities.jsx';
|
|
import External from './dom/external.jsx';
|
|
import Trade from './dom/trade.jsx';
|
|
import Wallet from './dom/wallet.jsx';
|
|
import HotBar from './dom/hotbar.jsx';
|
|
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);
|
|
}
|
|
|
|
const devEventsChannel = new EventEmitter();
|
|
|
|
function Ui({disconnected}) {
|
|
// Key input.
|
|
const client = useClient();
|
|
const chatInputRef = useRef();
|
|
const gameRef = useRef();
|
|
const pixiRef = useRef();
|
|
const mainEntityRef = useMainEntity();
|
|
const [, setDebug] = useDebug();
|
|
const ecsRef = useEcs();
|
|
const [showDisconnected, setShowDisconnected] = useState(false);
|
|
const [bufferSlot, setBufferSlot] = useState();
|
|
const hadBufferSlot = useRef();
|
|
const [distributing, setDistributing] = useState({});
|
|
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
|
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
|
const [camera, setCamera] = useState({x: 0, y: 0});
|
|
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
|
const [inventorySlots, setInventorySlots] = useState(emptySlots());
|
|
const [activeSlot, setActiveSlot] = useState(0);
|
|
const [scale, setScale] = useState(2);
|
|
const monopolizers = useRef([]);
|
|
const [message, setMessage] = useState('');
|
|
const [chatIsOpen, setChatIsOpen] = useState(false);
|
|
const [chatHistory, setChatHistory] = useState([]);
|
|
const [chatHistoryCaret, setChatHistoryCaret] = useState(-1);
|
|
const [chatMessages, setChatMessages] = useState({});
|
|
const [pendingMessage, setPendingMessage] = useState('');
|
|
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
|
|
const hotbarHideHandle = useRef();
|
|
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
|
const [externalInventory, setExternalInventory] = useState();
|
|
const [externalInventorySlots, setExternalInventorySlots] = useState();
|
|
const [gaining, setGaining] = useState([]);
|
|
const [losing, setLosing] = useState([]);
|
|
const [wallet, setWallet] = useState(0);
|
|
const [particleWorker, setParticleWorker] = useState();
|
|
const [trading, setTrading] = useState(false);
|
|
useEffect(() => {
|
|
let handle;
|
|
if (disconnected) {
|
|
handle = setTimeout(() => {
|
|
setShowDisconnected(true);
|
|
}, 1000);
|
|
}
|
|
else {
|
|
setShowDisconnected(false)
|
|
}
|
|
return () => {
|
|
clearTimeout(handle);
|
|
};
|
|
}, [disconnected]);
|
|
const keepHotbarOpen = useCallback(() => {
|
|
if (!isInventoryOpen) {
|
|
setHotbarIsHidden(false);
|
|
if (hotbarHideHandle.current) {
|
|
clearTimeout(hotbarHideHandle.current);
|
|
}
|
|
hotbarHideHandle.current = setTimeout(() => {
|
|
setHotbarIsHidden(true);
|
|
}, 4000);
|
|
}
|
|
}, [isInventoryOpen]);
|
|
useEffect(() => {
|
|
return addKeyListener(document.body, ({event, payload, type}) => {
|
|
const actionMap = {
|
|
'd': {type: 'moveRight'},
|
|
's': {type: 'moveDown'},
|
|
'a': {type: 'moveLeft'},
|
|
'w': {type: 'moveUp'},
|
|
'e': {type: 'interact'},
|
|
'1': {type: 'changeSlot', payload: 1},
|
|
'2': {type: 'changeSlot', payload: 2},
|
|
'3': {type: 'changeSlot', payload: 3},
|
|
'4': {type: 'changeSlot', payload: 4},
|
|
'5': {type: 'changeSlot', payload: 5},
|
|
'6': {type: 'changeSlot', payload: 6},
|
|
'7': {type: 'changeSlot', payload: 7},
|
|
'8': {type: 'changeSlot', payload: 8},
|
|
'9': {type: 'changeSlot', payload: 9},
|
|
'0': {type: 'changeSlot', payload: 10},
|
|
'`': {type: 'openInventory'},
|
|
'-': {type: 'zoomOut'},
|
|
'+': {type: 'zoomIn'},
|
|
'=': {type: 'zoomIn'},
|
|
'F3': {type: 'debug'},
|
|
'F4': {type: 'devtools'},
|
|
'Enter': {type: 'openChat'},
|
|
'Escape': {type: 'closeChat'},
|
|
};
|
|
const action = actionMap[payload];
|
|
if (!action) {
|
|
return;
|
|
}
|
|
if (chatInputRef.current) {
|
|
if ('closeChat' === action.type && 'keyDown' === type) {
|
|
setChatIsOpen(false);
|
|
return;
|
|
}
|
|
chatInputRef.current.focus();
|
|
return;
|
|
}
|
|
let actionPayload;
|
|
switch (action.type) {
|
|
case 'interact': {
|
|
if ('keyDown' === type) {
|
|
if (monopolizers.current.length > 0) {
|
|
monopolizers.current[0].trigger();
|
|
break;
|
|
}
|
|
}
|
|
actionPayload = {type: 'interact', value: KEY_MAP[type]};
|
|
break;
|
|
}
|
|
case 'moveRight':
|
|
case 'moveDown':
|
|
case 'moveLeft':
|
|
case 'moveUp': {
|
|
actionPayload = {type: action.type, value: 'keyDown' === type ? 1 : 0};
|
|
break;
|
|
}
|
|
case 'changeSlot': {
|
|
if ('keyDown' === type) {
|
|
keepHotbarOpen();
|
|
actionPayload = {type: 'changeSlot', value: action.payload};
|
|
}
|
|
break;
|
|
}
|
|
case 'openInventory': {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
if ('keyDown' === type) {
|
|
setIsInventoryOpen((isInventoryOpen) => {
|
|
if (isInventoryOpen) {
|
|
setHotbarIsHidden(true);
|
|
}
|
|
else {
|
|
setHotbarIsHidden(false);
|
|
if (hotbarHideHandle.current) {
|
|
clearTimeout(hotbarHideHandle.current);
|
|
}
|
|
}
|
|
return !isInventoryOpen;
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
case 'zoomIn': {
|
|
if ('keyDown' === type) {
|
|
setScale((scale) => scale < 4 ? Math.floor(scale + 1) : 4);
|
|
}
|
|
return;
|
|
}
|
|
case 'zoomOut': {
|
|
if ('keyDown' === type) {
|
|
setScale((scale) => scale > 1 ? scale - 1 : 1);
|
|
}
|
|
return;
|
|
}
|
|
case 'debug': {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
if ('keyDown' === type) {
|
|
setDebug(({...debug}) => ({...debug, info: !debug.info}));
|
|
}
|
|
return;
|
|
}
|
|
case 'devtools': {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
if ('keyDown' === type) {
|
|
setDevtoolsIsOpen((devtoolsIsOpen) => !devtoolsIsOpen);
|
|
}
|
|
return;
|
|
}
|
|
case 'openChat': {
|
|
if ('keyDown' === type) {
|
|
setChatIsOpen(true);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (actionPayload) {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: actionPayload,
|
|
});
|
|
}
|
|
});
|
|
}, [client, keepHotbarOpen, mainEntityRef, setDebug]);
|
|
const onEcsChangePacket = useCallback(() => {
|
|
mainEntityRef.current = undefined;
|
|
monopolizers.current = [];
|
|
}, [
|
|
mainEntityRef,
|
|
]);
|
|
usePacket('EcsChange', onEcsChangePacket);
|
|
const onEcsTick = useCallback((payload, ecs) => {
|
|
for (const id in payload) {
|
|
const entity = ecs.get(id);
|
|
const update = payload[id];
|
|
if (!update) {
|
|
continue;
|
|
}
|
|
if (update.MainEntity) {
|
|
mainEntityRef.current = id;
|
|
}
|
|
if (update.Wallet && mainEntityRef.current === id) {
|
|
setWallet(update.Wallet.gold);
|
|
}
|
|
if (update.Inventory) {
|
|
if (mainEntityRef.current === id) {
|
|
setBufferSlot(entity.Inventory.item(0));
|
|
setHotbarSlots(() => {
|
|
const newHotbarSlots = [];
|
|
for (let i = 1; i < 11; ++i) {
|
|
const item = entity.Inventory.item(i);
|
|
newHotbarSlots.push(
|
|
item
|
|
? {
|
|
icon: item.icon,
|
|
label: item.label,
|
|
price: item.price,
|
|
qty: item.qty,
|
|
}
|
|
: undefined,
|
|
);
|
|
}
|
|
return newHotbarSlots;
|
|
});
|
|
const newInventorySlots = emptySlots();
|
|
for (let i = 11; i < 41; ++i) {
|
|
newInventorySlots[i - 11] = entity.Inventory.item(i);
|
|
}
|
|
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)
|
|
setTrading(!!entity.Shop);
|
|
setExternalInventorySlots(newInventorySlots);
|
|
setIsInventoryOpen(true);
|
|
setHotbarIsHidden(false);
|
|
if (hotbarHideHandle.current) {
|
|
clearTimeout(hotbarHideHandle.current);
|
|
}
|
|
}
|
|
else if (update.Inventory.closed) {
|
|
setExternalInventory();
|
|
setExternalInventorySlots();
|
|
setGaining([]);
|
|
setLosing([]);
|
|
setTrading(false);
|
|
}
|
|
}
|
|
if (mainEntityRef.current === id) {
|
|
if (update.Wielder && 'activeSlot' in update.Wielder) {
|
|
setActiveSlot(update.Wielder.activeSlot);
|
|
}
|
|
}
|
|
}
|
|
}, [mainEntityRef]);
|
|
useEcsTick(onEcsTick);
|
|
const onEcsTickParticles = useCallback((payload, ecs) => {
|
|
if (!payload[1]?.AreaSize) {
|
|
return;
|
|
}
|
|
if (particleWorker) {
|
|
particleWorker.terminate();
|
|
}
|
|
const localParticleWorker = new Worker(
|
|
new URL('./particle-worker.js', import.meta.url),
|
|
{type: 'module'},
|
|
);
|
|
localParticleWorker.addEventListener('message', () => {
|
|
localParticleWorker.postMessage(ecs.get(1).toJSON());
|
|
setParticleWorker(localParticleWorker);
|
|
});
|
|
}, [particleWorker]);
|
|
useEcsTick(onEcsTickParticles);
|
|
const onEcsTickSound = useCallback((payload) => {
|
|
for (const id in payload) {
|
|
const update = payload[id];
|
|
if (update.Sound?.play) {
|
|
for (const sound of update.Sound.play) {
|
|
(new Audio(sound)).play();
|
|
}
|
|
}
|
|
}
|
|
}, []);
|
|
useEcsTick(onEcsTickSound);
|
|
const onEcsTickAabbs = useCallback((payload, ecs) => {
|
|
for (const id in payload) {
|
|
const entity = ecs.get(id);
|
|
const update = payload[id];
|
|
if (!update) {
|
|
continue;
|
|
}
|
|
if (update.Direction && entity.Collider) {
|
|
entity.Collider.updateAabbs();
|
|
}
|
|
}
|
|
}, []);
|
|
useEcsTick(onEcsTickAabbs);
|
|
const onEcsTickCamera = useCallback((payload) => {
|
|
if (mainEntityRef.current && payload[mainEntityRef.current]?.Camera) {
|
|
setCamera((camera) => ({
|
|
x: camera.x,
|
|
y: camera.y,
|
|
...payload[mainEntityRef.current].Camera,
|
|
}))
|
|
}
|
|
}, [mainEntityRef]);
|
|
useEcsTick(onEcsTickCamera);
|
|
useEffect(() => {
|
|
function onContextMenu(event) {
|
|
event.preventDefault();
|
|
}
|
|
document.body.addEventListener('contextmenu', onContextMenu);
|
|
return () => {
|
|
document.body.removeEventListener('contextmenu', onContextMenu);
|
|
};
|
|
}, []);
|
|
const computePosition = useCallback(({clientX, clientY}) => {
|
|
if (!gameRef.current || !mainEntityRef.current || !ecsRef.current) {
|
|
return;
|
|
}
|
|
const {top, left, width} = gameRef.current.getBoundingClientRect();
|
|
const master = ecsRef.current.get(1);
|
|
if (!master) {
|
|
return;
|
|
}
|
|
const {Camera} = ecsRef.current.get(mainEntityRef.current);
|
|
const size = width / RESOLUTION.x;
|
|
const camera = {
|
|
x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
|
|
y: ((Camera.y * scale) - (RESOLUTION.y / 2)),
|
|
}
|
|
return {
|
|
x: (((clientX - left) / size) + camera.x) / scale,
|
|
y: (((clientY - top) / size) + camera.y) / scale,
|
|
};
|
|
}, [
|
|
ecsRef,
|
|
mainEntityRef,
|
|
scale,
|
|
]);
|
|
const hotbarOnSlotMouseDown = useCallback((i, event) => {
|
|
keepHotbarOpen();
|
|
if (trading) {
|
|
const index = losing.indexOf(i + 1);
|
|
if (-1 === index) {
|
|
losing.push(i + 1);
|
|
}
|
|
else {
|
|
losing.splice(index, 1);
|
|
}
|
|
setLosing([...losing]);
|
|
}
|
|
else {
|
|
hadBufferSlot.current = bufferSlot;
|
|
switch (event.button) {
|
|
case 0:
|
|
if (bufferSlot) {
|
|
// ...
|
|
}
|
|
else {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 1]},
|
|
});
|
|
}
|
|
break;
|
|
case 2:
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {
|
|
type: 'distribute',
|
|
value: [
|
|
i + 1,
|
|
[
|
|
[mainEntityRef.current, 0],
|
|
[mainEntityRef.current, i + 1],
|
|
],
|
|
],
|
|
},
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}, [bufferSlot, client, keepHotbarOpen, losing, mainEntityRef, trading]);
|
|
const hotbarOnSlotMouseMove = useCallback((i) => {
|
|
if (hadBufferSlot.current) {
|
|
setDistributing((distributing) => ({
|
|
...distributing,
|
|
[[mainEntityRef.current, i + 1].join(':')]: true,
|
|
}));
|
|
}
|
|
}, [mainEntityRef]);
|
|
useEffect(() => {
|
|
if (!bufferSlot) {
|
|
return;
|
|
}
|
|
const entity = ecsRef.current.get(mainEntityRef.current);
|
|
setHotbarSlots(() => {
|
|
const newHotbarSlots = [];
|
|
for (let i = 1; i < 11; ++i) {
|
|
const item = entity.Inventory.item(i);
|
|
newHotbarSlots.push(
|
|
item
|
|
? {icon: item.icon, qty: item.qty}
|
|
: undefined,
|
|
);
|
|
}
|
|
const qtys = [];
|
|
for (const key in distributing) {
|
|
const [entityId, slot] = key.split(':');
|
|
const {Inventory} = ecsRef.current.get(entityId);
|
|
qtys.push(Inventory.slots[slot]?.qty ?? 0);
|
|
}
|
|
const item = {
|
|
maximumStack: bufferSlot.maximumStack,
|
|
qty: bufferSlot.qty,
|
|
};
|
|
const distributed = distribute(item, qtys);
|
|
let i = 0;
|
|
for (const key in distributing) {
|
|
const [entityId, slot] = key.split(':');
|
|
if (
|
|
entityId == mainEntityRef.current
|
|
&& (slot >= 1 && slot <= 11)
|
|
) {
|
|
if (!newHotbarSlots[slot - 1]) {
|
|
newHotbarSlots[slot - 1] = {
|
|
icon: bufferSlot.icon,
|
|
};
|
|
}
|
|
newHotbarSlots[slot - 1].qty = distributed[i];
|
|
newHotbarSlots[slot - 1].temporary = true;
|
|
}
|
|
i += 1;
|
|
}
|
|
return newHotbarSlots;
|
|
});
|
|
}, [bufferSlot, distributing, ecsRef, mainEntityRef])
|
|
const hotbarOnSlotMouseUp = useCallback((i, event) => {
|
|
keepHotbarOpen();
|
|
if (trading) {
|
|
// ...
|
|
}
|
|
else {
|
|
switch (event.button) {
|
|
case 0:
|
|
if (Object.keys(distributing).length > 0) {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {
|
|
type: 'distribute',
|
|
value: [
|
|
0,
|
|
Object.keys(distributing).map((idAndSlot) => idAndSlot.split(':')),
|
|
],
|
|
},
|
|
});
|
|
setDistributing({});
|
|
}
|
|
else if (hadBufferSlot.current) {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {
|
|
type: 'distribute',
|
|
value: [
|
|
0,
|
|
[
|
|
[mainEntityRef.current, i + 1],
|
|
],
|
|
],
|
|
},
|
|
});
|
|
}
|
|
else {
|
|
// ...
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
hadBufferSlot.current = undefined;
|
|
}, [client, distributing, keepHotbarOpen, mainEntityRef, trading]);
|
|
const bagOnSlotMouseDown = useCallback((i) => {
|
|
if (trading) {
|
|
const index = losing.indexOf(i + 11);
|
|
if (-1 === index) {
|
|
losing.push(i + 11);
|
|
}
|
|
else {
|
|
losing.splice(index, 1);
|
|
}
|
|
setLosing([...losing]);
|
|
}
|
|
else {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 11]},
|
|
});
|
|
}
|
|
}, [client, losing, mainEntityRef, trading]);
|
|
const externalInventoryOnSlotMouseDown = useCallback((i) => {
|
|
if (trading) {
|
|
const index = gaining.indexOf(i);
|
|
if (-1 === index) {
|
|
gaining.push(i);
|
|
}
|
|
else {
|
|
gaining.splice(index, 1);
|
|
}
|
|
setGaining([...gaining]);
|
|
}
|
|
else {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'swapSlots', value: [0, externalInventory, i]},
|
|
});
|
|
}
|
|
}, [client, externalInventory, gaining, trading]);
|
|
const onTradeAccepted = useCallback(() => {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'acceptTrade', value: {gaining, losing}},
|
|
});
|
|
setGaining([]);
|
|
setLosing([]);
|
|
}, [client, gaining, losing]);
|
|
useEffect(() => {
|
|
if (!pixiRef.current) {
|
|
return;
|
|
}
|
|
pixiRef.current.scale.set(scale);
|
|
pixiRef.current.x = -Math.round((camera.x * scale) - RESOLUTION.x / 2);
|
|
pixiRef.current.y = -Math.round((camera.y * scale) - RESOLUTION.y / 2);
|
|
}, [camera, scale])
|
|
return (
|
|
<div
|
|
className={styles.ui}
|
|
>
|
|
<style>
|
|
{`
|
|
@media (max-aspect-ratio: ${ratio}) { .${styles.game}, .${styles.ui} { width: 100%; } }
|
|
@media (min-aspect-ratio: ${ratio}) { .${styles.game}, .${styles.ui} { height: 100%; } }
|
|
.${styles.game} {
|
|
cursor: ${
|
|
bufferSlot
|
|
? `url('${bufferSlot.icon}'), auto !important`
|
|
: 'auto'
|
|
};
|
|
}
|
|
`}
|
|
</style>
|
|
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
|
|
<div
|
|
className={[styles.game, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}
|
|
onMouseDown={(event) => {
|
|
if (chatInputRef.current) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
const where = computePosition(event);
|
|
switch (event.button) {
|
|
case 0:
|
|
if (devtoolsIsOpen) {
|
|
devEventsChannel.invoke(
|
|
'click',
|
|
where,
|
|
event,
|
|
);
|
|
}
|
|
else if (!isInventoryOpen) {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'use', value: [1, where]},
|
|
});
|
|
}
|
|
break;
|
|
case 2:
|
|
if (monopolizers.current.length > 0) {
|
|
monopolizers.current[0].trigger();
|
|
break;
|
|
}
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'interact', value: 1},
|
|
});
|
|
break;
|
|
}
|
|
}}
|
|
onMouseUp={(event) => {
|
|
if (chatInputRef.current) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
const where = computePosition(event);
|
|
switch (event.button) {
|
|
case 0:
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'use', value: [0, where]},
|
|
});
|
|
break;
|
|
case 2:
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'interact', value: 0},
|
|
});
|
|
break;
|
|
}
|
|
event.preventDefault();
|
|
}}
|
|
onWheel={(event) => {
|
|
if (chatInputRef.current) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
if (!isInventoryOpen) {
|
|
setHotbarIsHidden(false);
|
|
if (hotbarHideHandle.current) {
|
|
clearTimeout(hotbarHideHandle.current);
|
|
}
|
|
hotbarHideHandle.current = setTimeout(() => {
|
|
setHotbarIsHidden(true);
|
|
}, 4000);
|
|
}
|
|
if (event.deltaY > 0) {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'changeSlot', value: 1 + ((activeSlot + 1) % 10)},
|
|
});
|
|
}
|
|
else {
|
|
client.send({
|
|
type: 'Action',
|
|
payload: {type: 'changeSlot', value: 1 + ((activeSlot + 9) % 10)},
|
|
});
|
|
}
|
|
}}
|
|
ref={gameRef}
|
|
>
|
|
<Pixi
|
|
monopolizers={monopolizers.current}
|
|
particleWorker={particleWorker}
|
|
ref={pixiRef}
|
|
/>
|
|
<Dom>
|
|
<HotBar
|
|
active={activeSlot}
|
|
highlighted={trading && losing.filter((i) => i < 11).map((i) => i - 1)}
|
|
hotbarIsHidden={hotbarIsHidden}
|
|
onSlotMouseDown={hotbarOnSlotMouseDown}
|
|
onSlotMouseMove={hotbarOnSlotMouseMove}
|
|
onSlotMouseUp={hotbarOnSlotMouseUp}
|
|
slots={hotbarSlots}
|
|
/>
|
|
<Bag
|
|
highlighted={trading && losing.filter((i) => i >= 11).map((i) => i - 11)}
|
|
isInventoryOpen={isInventoryOpen}
|
|
onSlotMouseDown={bagOnSlotMouseDown}
|
|
slots={inventorySlots}
|
|
/>
|
|
{externalInventory && (
|
|
<div className={styles.external}>
|
|
<External
|
|
highlighted={trading && gaining}
|
|
isInventoryOpen={isInventoryOpen}
|
|
onSlotMouseDown={externalInventoryOnSlotMouseDown}
|
|
slots={externalInventorySlots}
|
|
/>
|
|
{trading && (
|
|
<Trade
|
|
isInventoryOpen={isInventoryOpen}
|
|
onTradeAccepted={onTradeAccepted}
|
|
gaining={gaining.map((slot) => externalInventorySlots[slot])}
|
|
losing={losing.map((slot) => {
|
|
return slot < 11 ? hotbarSlots[slot - 1] : inventorySlots[slot - 11];
|
|
})}
|
|
wallet={wallet}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
<Entities
|
|
camera={camera}
|
|
scale={scale}
|
|
setChatMessages={setChatMessages}
|
|
monopolizers={monopolizers.current}
|
|
/>
|
|
{chatIsOpen && (
|
|
<Chat
|
|
chatHistory={chatHistory}
|
|
chatHistoryCaret={chatHistoryCaret}
|
|
chatInputRef={chatInputRef}
|
|
chatMessages={chatMessages}
|
|
message={message}
|
|
pendingMessage={pendingMessage}
|
|
setChatHistory={setChatHistory}
|
|
setChatHistoryCaret={setChatHistoryCaret}
|
|
setMessage={setMessage}
|
|
setPendingMessage={setPendingMessage}
|
|
/>
|
|
)}
|
|
{showDisconnected && (
|
|
<Disconnected />
|
|
)}
|
|
<DateTime />
|
|
<Wallet gold={wallet} />
|
|
</Dom>
|
|
</div>
|
|
{devtoolsIsOpen && (
|
|
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
|
<Devtools
|
|
eventsChannel={devEventsChannel}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Ui;
|