Compare commits
9 Commits
f9f3abf4ef
...
15d4abaad1
Author | SHA1 | Date | |
---|---|---|---|
|
15d4abaad1 | ||
|
2a0321f15e | ||
|
3f580c47c1 | ||
|
af571e25af | ||
|
b592ecb56b | ||
|
6ee9176756 | ||
|
1992c0ae68 | ||
|
44c19fe76b | ||
|
d19e917505 |
|
@ -1,6 +1,6 @@
|
|||
import createEcs from './create-ecs.js';
|
||||
|
||||
export default async function createHomestead(Ecs) {
|
||||
export default async function createHomestead(Ecs, id) {
|
||||
const ecs = createEcs(Ecs);
|
||||
const area = {x: 100, y: 60};
|
||||
await ecs.create({
|
||||
|
@ -49,7 +49,9 @@ export default async function createHomestead(Ecs) {
|
|||
],
|
||||
collisionStartScript: '/assets/shit-shack/collision-start.js',
|
||||
},
|
||||
Ecs: {},
|
||||
Ecs: {
|
||||
path: ['houses', `${id}`].join('/'),
|
||||
},
|
||||
Position: {x: 100, y: 100},
|
||||
Sprite: {
|
||||
anchorX: 0.5,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import createEcs from './create-ecs.js';
|
||||
|
||||
export default async function createHouse(Ecs) {
|
||||
export default async function createHouse(Ecs, id) {
|
||||
const ecs = createEcs(Ecs);
|
||||
const area = {x: 20, y: 20};
|
||||
await ecs.create({
|
||||
|
@ -31,7 +31,9 @@ export default async function createHouse(Ecs) {
|
|||
],
|
||||
collisionStartScript: '/assets/house/collision-start.js',
|
||||
},
|
||||
Ecs: {},
|
||||
Ecs: {
|
||||
path: ['homesteads', `${id}`].join('/'),
|
||||
},
|
||||
Position: {
|
||||
x: 72,
|
||||
y: 320,
|
||||
|
|
|
@ -120,7 +120,12 @@ export default class Engine {
|
|||
const {TileLayers} = ecs.get(1);
|
||||
const {brush, layer: paintLayer, stamp} = payload.value;
|
||||
const layer = TileLayers.layer(paintLayer);
|
||||
layer.stamp(stamp.at, stamp.data)
|
||||
switch (brush) {
|
||||
case 0: {
|
||||
layer.stamp(stamp.at, stamp.data)
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'changeSlot': {
|
||||
|
@ -215,14 +220,12 @@ export default class Engine {
|
|||
if ('ENOENT' !== error.code) {
|
||||
throw error;
|
||||
}
|
||||
const homestead = await createHomestead(this.Ecs);
|
||||
homestead.get(2).Ecs.path = ['houses', `${id}`].join('/');
|
||||
const homestead = await createHomestead(this.Ecs, id);
|
||||
await this.saveEcs(
|
||||
['homesteads', `${id}`].join('/'),
|
||||
homestead,
|
||||
);
|
||||
const house = await createHouse(this.Ecs);
|
||||
house.get(2).Ecs.path = ['homesteads', `${id}`].join('/');
|
||||
const house = await createHouse(this.Ecs, id);
|
||||
await this.saveEcs(
|
||||
['houses', `${id}`].join('/'),
|
||||
house,
|
||||
|
|
|
@ -73,6 +73,7 @@ if (import.meta.hot) {
|
|||
import.meta.hot.on('vite:beforeUpdate', async () => {
|
||||
engine.stop();
|
||||
await engine.disconnectPlayer(0);
|
||||
await engine.saveEcses();
|
||||
beforeResolver.resolve();
|
||||
});
|
||||
import.meta.hot.accept('../../engine.js');
|
||||
|
@ -97,7 +98,7 @@ if (import.meta.hot) {
|
|||
await beforeResolver;
|
||||
// delete engine.ecses['homesteads/0'];
|
||||
// await engine.server.removeData('homesteads/0');
|
||||
// const homestead = await createHomestead(engine.Ecs);
|
||||
// const homestead = await createHomestead(engine.Ecs, '0');
|
||||
// homestead.get(2).Ecs.path = 'houses/0';
|
||||
// await engine.saveEcs('homesteads/0', homestead);
|
||||
resolver.resolve();
|
||||
|
|
|
@ -1,134 +1,49 @@
|
|||
import {useRef, useState} from 'react';
|
||||
|
||||
import {useEcs} from '@/context/ecs.js';
|
||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
|
||||
import styles from './devtools.module.css';
|
||||
|
||||
import Tiles from './devtools/tiles.jsx';
|
||||
|
||||
export default function Devtools({
|
||||
brush,
|
||||
layer,
|
||||
setBrush,
|
||||
setLayer,
|
||||
setStamp,
|
||||
applyFilters,
|
||||
eventsChannel,
|
||||
setApplyFilters,
|
||||
}) {
|
||||
const offsetRef = useRef();
|
||||
const [selection, setSelection] = useState({x: 0, y: 0, w: 2, h: 2});
|
||||
const [moveStart, setMoveStart] = useState();
|
||||
const [ecs] = useEcs();
|
||||
if (!ecs) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {sourceJson, tileSize} = TileLayers.layer(0);
|
||||
const {w, h} = sourceJson.meta.size;
|
||||
return (
|
||||
<div className={styles.devtools}>
|
||||
<form>
|
||||
<div className={styles.topBar}>
|
||||
<div className={styles.layer}>
|
||||
<label>
|
||||
Layer:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={layer}
|
||||
>
|
||||
{
|
||||
Object.keys(TileLayers.layers)
|
||||
.map((layer, i) => (
|
||||
<option
|
||||
key={i}
|
||||
value={i}
|
||||
>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>Dashboard</Tab>
|
||||
<Tab>Tiles</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
<div className={styles.dashboard}>
|
||||
<form>
|
||||
<div className={styles.engineBar}>
|
||||
<label>
|
||||
<span>Apply filters</span>
|
||||
<input
|
||||
checked={applyFilters}
|
||||
onChange={() => {
|
||||
setApplyFilters(!applyFilters);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className={styles.brush}>
|
||||
<label>
|
||||
Brush:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={brush}
|
||||
>
|
||||
<option>Paint</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
onMouseDown={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
const x = Math.floor((event.clientX - left) / tileSize.x);
|
||||
const y = Math.floor((event.clientY - top) / tileSize.y);
|
||||
setMoveStart({x, y});
|
||||
setSelection({x, y, w: 1, h: 1});
|
||||
}}
|
||||
onMouseMove={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!moveStart) {
|
||||
return;
|
||||
}
|
||||
const {x: sx, y: sy} = moveStart;
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
const x = Math.floor(
|
||||
Math.max(0, Math.min(w - 1, (event.clientX - left)) / tileSize.x),
|
||||
);
|
||||
const y = Math.floor(
|
||||
Math.max(0, Math.min(h - 1, (event.clientY - top)) / tileSize.y),
|
||||
);
|
||||
const mx = Math.min(sx, x);
|
||||
const my = Math.min(sy, y);
|
||||
setSelection({x: mx, y: my, w: Math.abs(sx - x) + 1, h: Math.abs(sy - y) + 1});
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setMoveStart();
|
||||
const stamp = [];
|
||||
const {x, y, w: sw, h: sh} = selection;
|
||||
const tw = w / tileSize.x;
|
||||
for (let iy = 0; iy < sh; ++iy) {
|
||||
const row = [];
|
||||
for (let ix = 0; ix < sw; ++ix) {
|
||||
row.push((y + iy) * tw + x + ix);
|
||||
}
|
||||
stamp.push(row);
|
||||
}
|
||||
setStamp(stamp);
|
||||
}}
|
||||
className={styles.selectionWrapper}
|
||||
ref={offsetRef}
|
||||
>
|
||||
<div
|
||||
className={styles.selection}
|
||||
style={{
|
||||
top: selection.y * tileSize.y,
|
||||
left: selection.x * tileSize.x,
|
||||
height: selection.h * tileSize.x,
|
||||
width: selection.w * tileSize.y,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
alt="tileset"
|
||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<Tiles
|
||||
eventsChannel={eventsChannel}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,30 +1,49 @@
|
|||
.topBar {
|
||||
.engineBar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 16px 0;
|
||||
}
|
||||
|
||||
.devtools {
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.devtools p {
|
||||
text-align: center;
|
||||
.devtools form {
|
||||
label {
|
||||
font-weight: bold;
|
||||
:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
input, select, option {
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.selectionWrapper {
|
||||
position: relative;
|
||||
.devtools > :global(.react-tabs) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
> :global(.react-tabs__tab-list) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
> :global(.react-tabs__tab-panel) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
:global(.react-tabs__tab--selected) {
|
||||
background-color: #00000022;
|
||||
color: #ffffff;
|
||||
}
|
||||
:global(.react-tabs__tab:focus:after) {
|
||||
background-color: #00000044;
|
||||
}
|
||||
}
|
||||
|
||||
.selectionWrapper img {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.selection {
|
||||
background-color: #ffffff44;
|
||||
position: absolute;
|
||||
.dashboard {
|
||||
margin: 16px;
|
||||
}
|
||||
|
|
184
app/react-components/devtools/tiles.jsx
Normal file
184
app/react-components/devtools/tiles.jsx
Normal file
|
@ -0,0 +1,184 @@
|
|||
import {useEffect, useRef, useState} from 'react';
|
||||
|
||||
import {useClient} from '@/context/client.js';
|
||||
import {useEcs} from '@/context/ecs.js';
|
||||
import useRect from '@/util/react-hooks/use-rect.js';
|
||||
|
||||
import styles from './tiles.module.css';
|
||||
|
||||
export default function Tiles({eventsChannel}) {
|
||||
const client = useClient();
|
||||
const wrapperRef = useRef();
|
||||
const imageRef = useRef();
|
||||
const imageRect = useRect(imageRef);
|
||||
const [selection, setSelection] = useState({x: 0, y: 0, w: 1, h: 1});
|
||||
const [moveStart, setMoveStart] = useState();
|
||||
const [layer, setLayer] = useState(0);
|
||||
const [brush, setBrush] = useState(0);
|
||||
const [stamp, setStamp] = useState([]);
|
||||
const [ecs] = useEcs();
|
||||
useEffect(() => {
|
||||
if (!ecs) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {area, tileSize} = TileLayers.layer(0);
|
||||
function onClick({x, y}) {
|
||||
const at = {
|
||||
x: Math.floor(x / tileSize.x),
|
||||
y: Math.floor(y / tileSize.y),
|
||||
};
|
||||
if (at.x < 0 || at.y < 0 || at.x >= area.x || at.y >= area.y) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
brush,
|
||||
layer,
|
||||
stamp: {
|
||||
at,
|
||||
data: stamp,
|
||||
},
|
||||
}
|
||||
client.send({
|
||||
type: 'AdminAction',
|
||||
payload: {type: 'paint', value: payload},
|
||||
});
|
||||
}
|
||||
eventsChannel.addListener('click', onClick);
|
||||
return () => {
|
||||
eventsChannel.removeListener('click', onClick);
|
||||
};
|
||||
});
|
||||
if (!ecs) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {sourceJson, tileSize} = TileLayers.layer(0);
|
||||
const {w, h} = sourceJson.meta.size;
|
||||
const factor = (imageRect?.width ?? 1) / w;
|
||||
return (
|
||||
<div className={styles.tiles}>
|
||||
<form>
|
||||
<div className={styles.paintBar}>
|
||||
<div className={styles.layer}>
|
||||
<label>
|
||||
<span>Layer</span>
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={layer}
|
||||
>
|
||||
{
|
||||
Object.keys(TileLayers.layers)
|
||||
.map((layer, i) => (
|
||||
<option
|
||||
key={i}
|
||||
value={i}
|
||||
>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.brush}>
|
||||
<label>
|
||||
<span>Brush</span>
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setBrush(event.target.value)
|
||||
}}
|
||||
value={brush}
|
||||
>
|
||||
<option>Paint</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
onMouseDown={(event) => {
|
||||
const wrapperRect = wrapperRef.current.getBoundingClientRect();
|
||||
const {left, top} = wrapperRect;
|
||||
const c = {
|
||||
x: event.clientX,
|
||||
y: event.clientY + wrapperRef.current.scrollTop,
|
||||
};
|
||||
if (
|
||||
c.x - left >= (w * factor)
|
||||
|| c.y - top >= (h * factor)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const x = Math.floor((c.x - left) / (tileSize.x * factor));
|
||||
const y = Math.floor((c.y - top) / (tileSize.y * factor));
|
||||
setMoveStart({x, y});
|
||||
setSelection({x, y, w: 1, h: 1});
|
||||
}}
|
||||
onMouseMove={(event) => {
|
||||
if (!moveStart) {
|
||||
return;
|
||||
}
|
||||
const {x: sx, y: sy} = moveStart;
|
||||
const wrapperRect = wrapperRef.current.getBoundingClientRect();
|
||||
const {left, top} = wrapperRect;
|
||||
const c = {
|
||||
x: event.clientX,
|
||||
y: event.clientY + wrapperRef.current.scrollTop,
|
||||
};
|
||||
const x = Math.floor(
|
||||
Math.max(0, Math.min((w * factor) - 1, (c.x - left)) / (tileSize.x * factor)),
|
||||
);
|
||||
const y = Math.floor(
|
||||
Math.max(0, Math.min((h * factor) - 1, (c.y - top)) / (tileSize.y * factor)),
|
||||
);
|
||||
const mx = Math.min(sx, x);
|
||||
const my = Math.min(sy, y);
|
||||
setSelection({x: mx, y: my, w: Math.abs(sx - x) + 1, h: Math.abs(sy - y) + 1});
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setMoveStart();
|
||||
const stamp = [];
|
||||
const {x, y, w: sw, h: sh} = selection;
|
||||
const tw = w / tileSize.x;
|
||||
for (let iy = 0; iy < sh; ++iy) {
|
||||
const row = [];
|
||||
for (let ix = 0; ix < sw; ++ix) {
|
||||
row.push((y + iy) * tw + x + ix);
|
||||
}
|
||||
stamp.push(row);
|
||||
}
|
||||
setStamp(stamp);
|
||||
}}
|
||||
className={styles.selectionWrapper}
|
||||
ref={wrapperRef}
|
||||
>
|
||||
<div
|
||||
className={styles.selection}
|
||||
style={{
|
||||
top: selection.y * tileSize.y * factor,
|
||||
left: selection.x * tileSize.x * factor,
|
||||
height: selection.h * tileSize.x * factor,
|
||||
width: selection.w * tileSize.y * factor,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
alt="tileset"
|
||||
ref={imageRef}
|
||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
30
app/react-components/devtools/tiles.module.css
Normal file
30
app/react-components/devtools/tiles.module.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.tiles {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.paintBar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.selectionWrapper {
|
||||
margin: 16px;
|
||||
margin-right: 0;
|
||||
overflow-y: auto;
|
||||
padding-right: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selectionWrapper img {
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selection {
|
||||
background-color: #ffffff44;
|
||||
position: absolute;
|
||||
}
|
|
@ -79,7 +79,7 @@ function createLayerMask(layer) {
|
|||
return Texture.from(canvas);
|
||||
}
|
||||
|
||||
export default function Ecs({scale}) {
|
||||
export default function Ecs({applyFilters, scale}) {
|
||||
const [ecs] = useEcs();
|
||||
const [entities, setEntities] = useState({});
|
||||
const [mainEntity] = useMainEntity();
|
||||
|
@ -163,7 +163,7 @@ export default function Ecs({scale}) {
|
|||
const layer0 = TileLayers.layer(0);
|
||||
const layer1 = TileLayers.layer(1);
|
||||
const filters = [];
|
||||
if (night) {
|
||||
if (applyFilters && night) {
|
||||
filters.push(night);
|
||||
}
|
||||
const [cx, cy] = [
|
||||
|
|
|
@ -45,7 +45,7 @@ export const Stage = ({children, ...props}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default function Pixi({scale}) {
|
||||
export default function Pixi({applyFilters, scale}) {
|
||||
return (
|
||||
<Stage
|
||||
className={styles.stage}
|
||||
|
@ -55,7 +55,7 @@ export default function Pixi({scale}) {
|
|||
background: 0x1099bb,
|
||||
}}
|
||||
>
|
||||
<Ecs scale={scale} />
|
||||
<Ecs applyFilters={applyFilters} scale={scale} />
|
||||
</Stage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,32 @@ function emptySlots() {
|
|||
return Array(10).fill(undefined);
|
||||
}
|
||||
|
||||
const devEventsChannel = {
|
||||
$$listeners: {},
|
||||
addListener(type, listener) {
|
||||
if (!this.$$listeners[type]) {
|
||||
this.$$listeners[type] = new Set();
|
||||
}
|
||||
this.$$listeners[type].add(listener);
|
||||
},
|
||||
invoke(type, payload) {
|
||||
const listeners = this.$$listeners[type];
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
for (const listener of listeners) {
|
||||
listener(payload);
|
||||
}
|
||||
},
|
||||
removeListener(type, listener) {
|
||||
const listeners = this.$$listeners[type];
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
listeners.delete(listener);
|
||||
},
|
||||
};
|
||||
|
||||
export default function Ui({disconnected}) {
|
||||
// Key input.
|
||||
const client = useClient();
|
||||
|
@ -35,9 +61,7 @@ export default function Ui({disconnected}) {
|
|||
const [scale, setScale] = useState(2);
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
const [layer, setLayer] = useState(0);
|
||||
const [brush, setBrush] = useState(0);
|
||||
const [stamp, setStamp] = useState([]);
|
||||
const [applyFilters, setApplyFilters] = useState(true);
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs-components/index.js');
|
||||
|
@ -277,36 +301,22 @@ export default function Ui({disconnected}) {
|
|||
return;
|
||||
}
|
||||
const {Camera} = ecs.get(mainEntity);
|
||||
const {TileLayers} = master;
|
||||
const {area, tileSize} = TileLayers.layer(0);
|
||||
const size = width / RESOLUTION.x;
|
||||
const cr = {
|
||||
const client = {
|
||||
x: (event.clientX - left) / size,
|
||||
y: (event.clientY - top) / size,
|
||||
};
|
||||
const cm = {
|
||||
const camera = {
|
||||
x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
|
||||
y: ((Camera.y * scale) - (RESOLUTION.y / 2)),
|
||||
}
|
||||
const at = {
|
||||
x: Math.floor((cr.x + cm.x) / (tileSize.x * scale)),
|
||||
y: Math.floor((cr.y + cm.y) / (tileSize.y * scale)),
|
||||
};
|
||||
if (at.x < 0 || at.y < 0 || at.x >= area.x || at.y >= area.y) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
brush,
|
||||
layer,
|
||||
stamp: {
|
||||
at,
|
||||
data: stamp,
|
||||
devEventsChannel.invoke(
|
||||
'click',
|
||||
{
|
||||
x: (client.x + camera.x) / scale,
|
||||
y: (client.y + camera.y) / scale,
|
||||
},
|
||||
}
|
||||
client.send({
|
||||
type: 'AdminAction',
|
||||
payload: {type: 'paint', value: payload},
|
||||
});
|
||||
);
|
||||
}
|
||||
else {
|
||||
client.send({
|
||||
|
@ -357,7 +367,7 @@ export default function Ui({disconnected}) {
|
|||
}}
|
||||
ref={gameRef}
|
||||
>
|
||||
<Pixi scale={scale} />
|
||||
<Pixi applyFilters={applyFilters} scale={scale} />
|
||||
{mainEntity && (
|
||||
<Dom devtoolsIsOpen={devtoolsIsOpen}>
|
||||
<HotBar
|
||||
|
@ -378,12 +388,9 @@ export default function Ui({disconnected}) {
|
|||
</div>
|
||||
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
||||
<Devtools
|
||||
brush={brush}
|
||||
layer={layer}
|
||||
stamp={stamp}
|
||||
setBrush={setBrush}
|
||||
setLayer={setLayer}
|
||||
setStamp={setStamp}
|
||||
applyFilters={applyFilters}
|
||||
eventsChannel={devEventsChannel}
|
||||
setApplyFilters={setApplyFilters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
16
app/util/react-hooks/use-rect.js
vendored
Normal file
16
app/util/react-hooks/use-rect.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {useLayoutEffect, useState} from 'react'
|
||||
import useResizeObserver from '@react-hook/resize-observer'
|
||||
|
||||
export default function useRect(target) {
|
||||
const [rect, setRect] = useState();
|
||||
useLayoutEffect(() => {
|
||||
setRect(target.current.getBoundingClientRect())
|
||||
}, [target]);
|
||||
useResizeObserver(
|
||||
target,
|
||||
(entry) => {
|
||||
setRect(entry.target.getBoundingClientRect());
|
||||
},
|
||||
);
|
||||
return rect;
|
||||
}
|
56
package-lock.json
generated
56
package-lock.json
generated
|
@ -15,6 +15,7 @@
|
|||
"@pixi/react": "^7.1.2",
|
||||
"@pixi/spritesheet": "^7.4.2",
|
||||
"@pixi/tilemap": "^4.1.0",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
"@remix-run/express": "^2.9.2",
|
||||
"@remix-run/node": "^2.9.2",
|
||||
"@remix-run/react": "^2.9.2",
|
||||
|
@ -28,6 +29,7 @@
|
|||
"pixi.js": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tabs": "^6.0.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -3019,6 +3021,11 @@
|
|||
"integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||
},
|
||||
"node_modules/@leodeslf/simplex-noise": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@leodeslf/simplex-noise/-/simplex-noise-1.0.0.tgz",
|
||||
|
@ -3970,6 +3977,35 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/latest": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz",
|
||||
"integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/passive-layout-effect": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
|
||||
"integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/resize-observer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-2.0.1.tgz",
|
||||
"integrity": "sha512-9PCX9grWfxdPizY8ohr+X4IkV1JhGMWr2Nm4ngbg6IcAIv0WBs7YoJcNBqYl22OqPHr5eOMItGcStZrmj2mbmQ==",
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
"@react-hook/latest": "^1.0.2",
|
||||
"@react-hook/passive-layout-effect": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/dev": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.9.2.tgz",
|
||||
|
@ -8419,6 +8455,14 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
@ -15449,6 +15493,18 @@
|
|||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-tabs": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz",
|
||||
"integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"prop-types": "^15.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@pixi/react": "^7.1.2",
|
||||
"@pixi/spritesheet": "^7.4.2",
|
||||
"@pixi/tilemap": "^4.1.0",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
"@remix-run/express": "^2.9.2",
|
||||
"@remix-run/node": "^2.9.2",
|
||||
"@remix-run/react": "^2.9.2",
|
||||
|
@ -35,6 +36,7 @@
|
|||
"pixi.js": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tabs": "^6.0.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
Loading…
Reference in New Issue
Block a user