Compare commits

...

41 Commits

Author SHA1 Message Date
cha0s
8527be39fb feat: debug flags 2024-11-17 04:11:15 -06:00
cha0s
9cfb06f66c fix: always set last position 2024-11-17 03:44:14 -06:00
cha0s
7c976ad8c0 fix: tighten up OS key smoothing 2024-11-17 03:38:27 -06:00
cha0s
88fa01c920 feat: tile collision 2024-11-16 07:24:11 -06:00
cha0s
7bafc4702a feat: collider prediction 2024-11-16 06:34:37 -06:00
cha0s
e540d98c46 refactor: revertPosition 2024-11-16 06:33:46 -06:00
cha0s
5aa17441a1 refactor: aabbFromPoints 2024-11-16 06:32:50 -06:00
cha0s
bfe9e427b9 fix: race condition 2024-11-16 06:29:31 -06:00
cha0s
51edbec463 revert: time smoothness 2024-11-16 02:35:30 -06:00
cha0s
d82c33338e refactor: move component/system hmr out 2024-11-16 01:17:32 -06:00
cha0s
0c234de26d refactor: remove packers 2024-11-15 22:33:00 -06:00
cha0s
b773f44c31 fix: return serialize length 2024-11-15 22:32:14 -06:00
cha0s
dfc190048f refactor: lower time frequency 2024-11-15 22:31:51 -06:00
cha0s
8589589d2b fix: work around https://github.com/vitest-dev/vscode/discussions/313 2024-11-15 20:40:09 -06:00
cha0s
5f279bf549 refactor: diffless impulse 2024-11-15 20:08:37 -06:00
cha0s
477b76acd5 refactor: schema setter 2024-11-14 23:12:29 -06:00
cha0s
e3d0cc2477 refactor: instance schema 2024-11-14 22:53:10 -06:00
cha0s
9dec5d15a3 chore: not sure what changed 2024-11-14 21:29:09 -06:00
cha0s
88055be00a refactor: fromJSON 2024-11-13 03:36:21 -06:00
cha0s
7a1e08bc1a fun: homestead 2024-11-13 03:06:53 -06:00
cha0s
5de530077f fix: will-change degrades quality 2024-11-12 01:37:18 -06:00
cha0s
6b3136a0b5 fix: rejected collision movement 2024-11-11 23:00:33 -06:00
cha0s
aaa78c99c5 feat: explicit collision rotation 2024-11-11 23:00:15 -06:00
cha0s
d55c35ca33 fix: initial previous positions 2024-11-11 22:59:38 -06:00
cha0s
bcb4115fa0 fix: cheap fix for multiple impassable collision 2024-11-11 16:26:46 -06:00
cha0s
04fd533811 flow: request/response, entity editing and labels 2024-11-10 18:38:57 -06:00
cha0s
9262e7b1a8 feat: once 2024-11-10 18:18:59 -06:00
cha0s
500ada03ff refactor: name 2024-11-10 18:16:34 -06:00
cha0s
3df5266ba3 feat: entity devtools 2024-11-10 15:53:42 -06:00
cha0s
21f8864b1b fix: player ID 2024-11-10 05:26:21 -06:00
cha0s
3fffb49e24 chore: shrub entity 2024-11-10 05:12:17 -06:00
cha0s
c21e6982ac fix: position 2024-11-10 05:12:08 -06:00
cha0s
607fbe9d9e feat: size 2024-11-10 05:11:45 -06:00
cha0s
050664a826 refactor: pass event to channel 2024-11-10 05:08:38 -06:00
cha0s
8ae9daa9ba refactor: devtools 2024-11-09 21:47:03 -06:00
cha0s
6526989db6 fun: update homestead 2024-11-09 20:58:10 -06:00
cha0s
85b0e59907 refactor: dump all layers 2024-11-09 20:57:56 -06:00
cha0s
4c9635ec83 chore: jimp for dev and bump sylvite 2024-11-09 20:57:32 -06:00
cha0s
ff52066e71 fix: particle scripts 2024-11-08 22:01:55 -06:00
cha0s
de6465df5b fix: pixelation 2024-11-07 20:47:32 -06:00
cha0s
eca9bc631e chore: indent 2024-11-05 19:57:35 -06:00
90 changed files with 31018 additions and 288 deletions

View File

@ -2,19 +2,22 @@ import fastCall from './fast-call.js';
export default class EventEmitter {
$$listeners = {};
addListener(type, listener) {
addListener(type, listener, options = {}) {
if (!this.$$listeners[type]) {
this.$$listeners[type] = new Set();
this.$$listeners[type] = new Map();
}
this.$$listeners[type].add(listener);
this.$$listeners[type].set(listener, options);
}
invoke(type, ...args) {
const listeners = this.$$listeners[type];
if (!listeners) {
return;
}
for (const listener of listeners) {
for (const [listener, options] of listeners) {
fastCall(listener, undefined, args);
if (options.once) {
listeners.delete(listener);
}
}
}
removeListener(type, listener) {

View File

@ -53,6 +53,23 @@ export const HALF_PI = Math.PI / 2;
export const TAU = Math.PI * 2;
export const PI_180 = Math.PI / 180;
export function aabbFromPoints(points) {
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
for (const point of points) {
const {x, y} = point;
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
return {
x0: x0 > x1 ? x1 : x0,
x1: x0 > x1 ? x0 : x1,
y0: y0 > y1 ? y1 : y0,
y1: y0 > y1 ? y0 : y1,
};
}
export function bresenham({x: x1, y: y1}, {x: x2, y: y2}) {
const points = [];
let x; let y; let px; let py; let xe; let ye;

View File

@ -48,7 +48,7 @@ export default class Script {
static async registerScriptsModule() {
const {default: scripts} = await import('./scripts.js');
for (const path in scripts) {
Script.register(path, scripts[path]);
this.register(path, scripts[path]);
}
}

View File

@ -34,7 +34,7 @@ export default function addKeyListener(target, listener) {
delete keyUpDelays[key];
delete keysDown[key];
listener({event, type: 'keyUp', payload: key});
}, 20);
}, 5);
}
window.addEventListener('blur', onBlur);
window.addEventListener('keyup', onKeyUp);

View File

@ -1,52 +1,37 @@
import {useCallback, useState} from 'react';
import {useEffect} from 'react';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import {useClient} from '@/react/context/client.js';
import {useEcsTick} from '@/react/context/ecs.js';
import {useMainEntity} from '@/react/context/main-entity.js';
import styles from './devtools.module.css';
import Tiles from './devtools/tiles.jsx';
import TabComponents from './devtools/components.js';
export default function Devtools({
eventsChannel,
}) {
const client = useClient();
const mainEntityRef = useMainEntity();
const [mainEntityJson, setMainEntityJson] = useState('');
const onEcsTick = useCallback((payload, ecs) => {
if (!mainEntityRef.current) {
return;
useEffect(() => {
if (import.meta.hot) {
const updating = new Set(TabComponents.map(([path]) => path));
import.meta.hot.on('vite:afterUpdate', ({updates}) => {
if (updates.some(({path}) => updating.has(path))) {
import.meta.hot.invalidate();
}
});
}
setMainEntityJson(JSON.stringify(ecs.get(mainEntityRef.current), null, 2));
}, [mainEntityRef]);
useEcsTick(onEcsTick);
}, []);
return (
<div className={styles.devtools}>
<Tabs>
<TabList>
<Tab>Dashboard</Tab>
<Tab>Tiles</Tab>
{TabComponents.map(([path, {displayName}]) => <Tab key={path}>{displayName}</Tab>)}
</TabList>
<TabPanel>
<div className={styles.dashboard}>
<form>
<div className={styles.engineBar}>
<div>{Math.round(client.rtt * 100) / 100}rtt</div>
<div>{Math.round(((client.throughput.down * 8) / 1024) * 10) / 10}kb/s down</div>
<div>{Math.round(((client.throughput.up * 8) / 1024) * 10) / 10}kb/s up</div>
</div>
</form>
<pre><code><small>{mainEntityJson}</small></code></pre>
</div>
</TabPanel>
<TabPanel>
<Tiles
eventsChannel={eventsChannel}
/>
</TabPanel>
{TabComponents.map(([path, TabComponent]) => (
<TabPanel key={path}>
<TabComponent
eventsChannel={eventsChannel}
/>
</TabPanel>
))}
</Tabs>
</div>
);

View File

@ -1,18 +1,16 @@
.engineBar {
display: flex;
gap: 16px;
margin-bottom: 16px 0;
}
.devtools {
background-color: #444444;
color: white;
font-size: 24px;
height: 100%;
overflow: hidden;
width: 100%;
}
.devtools form {
&, & * {
font-size: 100%;
}
label {
font-weight: bold;
:first-child {
@ -35,6 +33,24 @@
> :global(.react-tabs__tab-panel) {
overflow-y: auto;
}
:global(.react-tabs__tab) {
button {
background: transparent;
border: none;
border-radius: 1em;
cursor: pointer;
left: 0.25em;
padding: 0 0.125em;
position: relative;
visibility: hidden;
&:hover {
background-color: #ffffff33;
}
}
&:hover button {
visibility: visible;
}
}
:global(.react-tabs__tab--selected) {
background-color: #00000022;
color: #ffffff;
@ -43,10 +59,3 @@
background-color: #00000044;
}
}
.dashboard {
margin: 16px;
pre {
user-select: text;
}
}

View File

@ -0,0 +1,7 @@
const entries = Object.entries(
import.meta.glob('./*.jsx', {eager: true}),
);
export default entries
.filter(([, M]) => M.default)
.map(([path, M]) => ([new URL(path, import.meta.url).pathname, M.default]));

View File

@ -0,0 +1,34 @@
import {useClient} from '@/react/context/client.js';
import {useDebug} from '@/react/context/debug.js';
import styles from './dashboard.module.css';
function Dashboard() {
const client = useClient();
const [debug, setDebug] = useDebug();
return (
<div className={styles.dashboard}>
<form>
<div className={styles.engineBar}>
<div>{Math.round(client.rtt * 100) / 100}rtt</div>
<div>{Math.round(((client.throughput.down * 8) / 1024) * 10) / 10}kb/s down</div>
<div>{Math.round(((client.throughput.up * 8) / 1024) * 10) / 10}kb/s up</div>
</div>
<label>
Disable ambient light
<input
type="checkbox"
checked={debug.disableAmbientLight}
onChange={() => {
setDebug({...debug, disableAmbientLight: !debug.disableAmbientLight});
}}
/>
</label>
</form>
</div>
);
}
Dashboard.displayName = 'Dashboard';
export default Dashboard;

View File

@ -0,0 +1,12 @@
.dashboard {
margin: 16px;
pre {
user-select: text;
}
}
.engineBar {
display: flex;
gap: 16px;
margin-bottom: 16px 0;
}

View File

@ -0,0 +1,192 @@
import {Map} from 'immutable';
import {useCallback, useEffect, useState} from 'react';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import {useClient} from '@/react/context/client.js';
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
import styles from './entities.module.css';
const entityJsonPaths = Object.keys(import.meta.glob('%/**/*.entity.json'));
function entityLabel(entity) {
let label = `${entity.id}`;
const {Label, Player} = entity;
if (1 === entity.id) {
label = 'Master';
}
if (Player) {
label = `Player (${Player.id})`;
}
if (Label) {
label = Label.label;
}
return label;
}
function Entities({eventsChannel}) {
const client = useClient();
const ecsRef = useEcs();
const [activeEntity, setActiveEntity] = useState('1');
const [creatingEntityPath, setCreatingEntityPath] = useState(entityJsonPaths[0]);
const [entities, setEntities] = useState(Map());
const onEcsTick = useCallback((payload, ecs) => {
setEntities((entities) => {
return entities.withMutations((entities) => {
if (0 === entities.size) {
for (const id in ecs.$$entities) {
const entity = ecs.get(id);
entities.set(id, entityLabel(entity));
}
return;
}
for (const id in payload) {
const update = payload[id];
if (false === update) {
entities.delete(id);
}
else {
const entity = ecs.get(id);
entities.set(id, entityLabel(entity));
}
}
});
});
}, []);
useEcsTick(onEcsTick);
const list = Array.from(entities.keys())
.toSorted(new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}).compare);
useEffect(() => {
if (!ecsRef.current) {
return;
}
const master = ecsRef.current.get(1);
if (!master) {
return;
}
const {TileLayers} = master;
const {size, tileSize} = TileLayers.layer(0);
function onClick({x, y}, {shiftKey}) {
const at = {
x: shiftKey ? Math.floor(x / tileSize.x) * tileSize.x + (tileSize.x / 2) : x,
y: shiftKey ? Math.floor(y / tileSize.y) * tileSize.y + (tileSize.y / 2) : y,
};
if (at.x < 0 || at.y < 0 || at.x >= size.x || at.y >= size.y) {
return;
}
const entity = ecsRef.current.get(activeEntity);
if (!entity.Position) {
return;
}
client.send({
type: 'AdminAction',
payload: {
type: 'moveEntity',
value: {
id: activeEntity,
at,
},
},
});
}
eventsChannel.addListener('click', onClick);
return () => {
eventsChannel.removeListener('click', onClick);
};
}, [activeEntity, client, ecsRef, eventsChannel]);
const options = [];
const tabLabels = [];
const tabPanels = [];
for (const id of list) {
const label = entities.get(id);
tabLabels.push(
<Tab key={id}>
{label}
<button
onClick={(event) => {
client.send({
type: 'AdminAction',
payload: {
type: 'destroyEntity',
value: {
id,
},
},
});
event.preventDefault();
}}
>
×
</button>
</Tab>
);
tabPanels.push(
<TabPanel key={id}>
{id}
</TabPanel>
);
options.push(
<option
key={id}
value={id}
>
{label}
</option>);
}
return (
<div className={styles.entities}>
<form>
<div
className={styles.createEntity}
>
<button
onClick={async (event) => {
event.preventDefault();
const {payload} = await client.request({
type: 'AdminAction',
payload: {
type: 'createEntity',
value: {
path: creatingEntityPath,
},
},
});
setActiveEntity(`${payload.id}`);
}}
>
+
</button>
<select
onChange={(event) => {
setCreatingEntityPath(event.target.value);
}}
value={creatingEntityPath}
>
{entityJsonPaths.map((path) => (
<option key={path} value={path}>{path}</option>
))}
</select>
</div>
<div
className={styles.activeEntity}
>
<Tabs
onSelect={(index) => {
setActiveEntity(list[index]);
}}
selectedIndex={list.indexOf(activeEntity)}
>
<TabList>
{tabLabels}
</TabList>
{tabPanels}
</Tabs>
</div>
</form>
</div>
);
}
Entities.displayName = 'Entities';
export default Entities;

View File

@ -0,0 +1,7 @@
.activeEntity {
display: flex;
}
.createEntity {
display: flex;
}

View File

@ -6,7 +6,7 @@ import useRect from '@/react/hooks/use-rect.js';
import styles from './tiles.module.css';
export default function Tiles({eventsChannel}) {
function Tiles({eventsChannel}) {
const client = useClient();
const wrapperRef = useRef();
const imageRef = useRef();
@ -228,4 +228,8 @@ export default function Tiles({eventsChannel}) {
</div>
</div>
);
}
}
Tiles.displayName = 'Tiles';
export default Tiles;

View File

@ -1,6 +1,7 @@
.tiles {
display: flex;
height: 100%;
image-rendering: pixelated;
flex-direction: column;
}

View File

@ -52,7 +52,8 @@
calc(-50% + (1px * var(--offsetY)) + var(--positionY))
;
user-select: none;
will-change: color, scale, opacity, translate, transform;
/** quality degradation... */
/* will-change: color, scale, opacity, translate, transform; */
p {
margin: 0;
}

View File

@ -1,4 +1,5 @@
import {Emitter} from '@/lib/particles.js';
import Script from '@/lib/script.js';
import createEcs from '@/silphius/server/create/ecs.js';
import ClientEcs from './client-ecs.js';
@ -28,7 +29,9 @@ addEventListener('message', (particle) => {
.onEnd(() => {});
});
postMessage(null);
Script.registerScriptsModule().then(() => {
postMessage(null);
});
let last = performance.now();
function tick(now) {

View File

@ -1,6 +1,7 @@
import {Container, useApp} from '@pixi/react';
import {useCallback, useState} from 'react';
import {useDebug} from '@/react/context/debug.js';
import {useEcsTick} from '@/react/context/ecs.js';
import {useMainEntity} from '@/react/context/main-entity.js';
@ -12,6 +13,7 @@ import Water from './water.jsx';
export default function Ecs({monopolizers, particleWorker}) {
const app = useApp();
const [debug] = useDebug();
const mainEntityRef = useMainEntity();
const [layers, setLayers] = useState([]);
const [projected, setProjected] = useState([]);
@ -104,7 +106,7 @@ export default function Ecs({monopolizers, particleWorker}) {
}
}
// 7 - 19 day
if (hour >= 7 && hour < 19) {
if (debug.disableAmbientLight || hour >= 7 && hour < 19) {
brightness = 1;
color = 0xffffff;
}
@ -132,7 +134,7 @@ export default function Ecs({monopolizers, particleWorker}) {
: projected;
});
}
}, [app.ambientLight, mainEntityRef]);
}, [app.ambientLight, debug, mainEntityRef]);
useEcsTick(onEcsTick);
return (
<>

View File

@ -60,7 +60,7 @@ export default function Entities({monopolizers, particleWorker}) {
}, []);
useEffect(() => {
for (const key in entities.current) {
entities.current[key].setDebug(debug);
entities.current[key].setDebug(debug.info);
}
}, [debug]);
usePacket('EcsChange', () => {

View File

@ -198,7 +198,7 @@ function Ui({disconnected}) {
event.preventDefault();
}
if ('keyDown' === type) {
setDebug((debug) => !debug);
setDebug(({...debug}) => ({...debug, info: !debug.info}));
}
return;
}
@ -611,6 +611,7 @@ function Ui({disconnected}) {
devEventsChannel.invoke(
'click',
where,
event,
);
}
else if (!isInventoryOpen) {

View File

@ -1,30 +1,32 @@
import {useCallback, useEffect, useRef, useState} from 'react';
import {useOutletContext, useParams} from 'react-router-dom';
import ClientEcs from '@/react/components/client-ecs.js';
import Ui from '@/react/components/ui.jsx';
import AssetsContext from '@/react/context/assets.js';
import ClientContext from '@/react/context/client.js';
import DebugContext from '@/react/context/debug.js';
import EcsContext from '@/react/context/ecs.js';
import MainEntityContext from '@/react/context/main-entity.js';
import ClientEcs from '@/react/components/client-ecs.js';
import Components from '@/silphius/ecs/components/index.js';
import Systems from '@/silphius/ecs/systems/index.js';
export default function PlaySpecific() {
const Client = useOutletContext();
const assetsTuple = useState({});
const [client, setClient] = useState();
const mainEntityRef = useRef();
const debugTuple = useState(false);
const [Components, setComponents] = useState();
const [Systems, setSystems] = useState();
const debugTuple = useState({
disableAmbientLight: false,
info: false,
});
const reconnectionBackoff = useRef(0);
const ecsRef = useRef();
const [disconnected, setDisconnected] = useState(false);
const params = useParams();
const [type, url] = params['*'].split('/');
useEffect(() => {
if (!Client || !Components || !Systems) {
if (!Client) {
return;
}
const client = new Client();
@ -36,7 +38,7 @@ export default function PlaySpecific() {
return () => {
client.disconnect();
};
}, [Client, Components, Systems, url]);
}, [Client, url]);
// Sneakily use beforeunload to snag some time to save.
useEffect(() => {
if ('local' !== type) {
@ -60,16 +62,7 @@ export default function PlaySpecific() {
}
ecsRef.current = new ClientEcsPerf({Components, Systems});
mainEntityRef.current = undefined;
}, [ecsRef, Components, Systems]);
useEffect(() => {
async function setEcsStuff() {
const {default: Components} = await import('@/silphius/ecs/components/index.js');
const {default: Systems} = await import('@/silphius/ecs/systems/index.js');
setComponents(Components);
setSystems(Systems);
}
setEcsStuff();
});
}, [ecsRef]);
useEffect(() => {
refreshEcs();
}, [refreshEcs]);

View File

@ -4,6 +4,9 @@ export default class LocalClient extends Client {
server = null;
async connect() {
await super.connect();
if (!this.connected) {
return;
}
this.server = new Worker(
new URL('../server/worker.js', import.meta.url),
{type: 'module'},
@ -19,7 +22,9 @@ export default class LocalClient extends Client {
}
disconnect() {
super.disconnect();
this.server.postMessage(0);
if (this.server) {
this.server.postMessage(0);
}
}
transmit(packed) {
this.server.postMessage(packed);

View File

@ -4,6 +4,9 @@ export default class RemoteClient extends Client {
socket = null;
async connect(host) {
await super.connect();
if (!this.connected) {
return;
}
this.socket = new WebSocket(`//${host}/silphius`);
this.socket.binaryType = 'arraybuffer';
this.socket.addEventListener('message', (event) => {

View File

@ -40,7 +40,7 @@ export default class Component {
const {
constructor: Schema,
specification: {concrete: {properties}},
} = this.constructor.schema;
} = this.schema;
const defaults = {};
for (const key in properties) {
defaults[key] = ((key) => () => Schema.defaultValue(properties[key]))(key);
@ -83,9 +83,9 @@ export default class Component {
}
deserialize(entityId, view, offset) {
const {properties} = this.constructor.schema.specification.concrete;
const {properties} = this.schema.specification.concrete;
const instance = this.get(entityId);
const deserialized = this.constructor.schema.deserialize(view, offset);
const deserialized = this.schema.deserialize(view, offset);
for (const key in properties) {
instance[key] = deserialized[key];
}
@ -107,18 +107,6 @@ export default class Component {
}
}
static filterDefaults(instance) {
const {properties} = this.schema.specification.concrete;
const Schema = this.schema.constructor;
const json = {};
for (const key in properties) {
if (key in instance && instance[key] !== Schema.defaultValue(properties[key])) {
json[key] = instance[key];
}
}
return json;
}
get(entityId) {
return this.instances[entityId];
}
@ -129,7 +117,8 @@ export default class Component {
instanceFromSchema() {
const Component = this;
const {concrete} = Component.constructor.schema.specification;
const {concrete} = Component.schema.specification;
const setContext = {ecs: this.ecs};
const Instance = class {
$$entity = 0;
destroy() {}
@ -137,7 +126,7 @@ export default class Component {
const {properties} = concrete;
for (const key in values) {
if (properties[key]?.$.set) {
properties[key].$.set(Component, this, `$$${key}`, values[key]);
properties[key].$.set(this, `$$${key}`, values[key], setContext);
}
else {
this[`$$${key}`] = values[key];
@ -145,7 +134,7 @@ export default class Component {
}
for (const key in defaults) {
if (properties[key]?.$.set) {
properties[key].$.set(Component, this, `$$${key}`, defaults[key]);
properties[key].$.set(this, `$$${key}`, defaults[key], setContext);
}
else {
this[`$$${key}`] = defaults[key];
@ -180,7 +169,7 @@ export default class Component {
for (const key in values) {
if (properties[key]) {
if (properties[key]?.$.set) {
properties[key].$.set(Component, this, `$$${key}`, values[key]);
properties[key].$.set(this, `$$${key}`, values[key], setContext);
}
else {
this[`$$${key}`] = values[key];
@ -209,7 +198,7 @@ export default class Component {
set: function set(value) {
if (this[`$$${key}`] !== value) {
if (concrete.properties[key]?.$.set) {
concrete.properties[key].$.set(Component, this, `$$${key}`, value);
concrete.properties[key].$.set(this, `$$${key}`, value, setContext);
}
else {
this[`$$${key}`] = value;
@ -235,22 +224,24 @@ export default class Component {
return {...original, ...update};
}
static get schema() {
get schema() {
if (!this.$$schema) {
this.$$schema = new Schema({
type: 'object',
properties: this.properties,
});
this.$$schema = new Schema(
{
type: 'object',
properties: this.constructor.properties,
},
);
}
return this.$$schema;
}
serialize(entityId, view, offset) {
this.constructor.schema.serialize(this.get(entityId), view, offset);
this.schema.serialize(this.get(entityId), view, offset);
}
sizeOf(entityId) {
return this.constructor.schema.sizeOf(this.get(entityId));
return this.schema.sizeOf(this.get(entityId));
}
updateMany(entities) {

View File

@ -1,5 +1,5 @@
import {aabbFromPoints, distance, intersects, transform} from '@/lib/math.js';
import Component from '@/silphius/ecs/component.js';
import {distance, intersects, transform} from '@/lib/math.js';
import vector2d from './helpers/vector-2d';
@ -32,7 +32,7 @@ export default class Collider extends Component {
}
return aabbs;
}
checkCollision(other) {
checkCollision(other, {runScripts}) {
if (!this.isColliding || !other.isColliding) {
return;
}
@ -45,7 +45,7 @@ export default class Collider extends Component {
if (0 === activeIntersections.size) {
return;
}
this.endIntersections(other, intersections);
this.endIntersections(other, intersections, {runScripts});
return;
}
for (const intersection of intersections) {
@ -75,7 +75,7 @@ export default class Collider extends Component {
}
}
if (!hasMatchingIntersection) {
if (this.collisionStartScript) {
if (runScripts && this.collisionStartScript) {
const script = this.collisionStartScript.clone();
script.locals.entity = thisEntity;
script.locals.other = otherEntity;
@ -85,7 +85,7 @@ export default class Collider extends Component {
ecs.addDestructionDependency(otherEntity.id, promise);
ecs.addDestructionDependency(thisEntity.id, promise);
}
if (other.collisionStartScript) {
if (runScripts && other.collisionStartScript) {
const script = other.collisionStartScript.clone();
script.locals.entity = otherEntity;
script.locals.other = thisEntity;
@ -99,37 +99,10 @@ export default class Collider extends Component {
}
// undo restricted movement
if (!body.unstoppable && otherBody.impassable) {
const j = this.bodies.indexOf(body);
const oj = other.bodies.indexOf(otherBody);
const aabb = this.$$aabbs[j];
const otherAabb = other.aabbs[oj];
const {Position} = thisEntity;
if (!intersects(
{
x0: aabb.x0 + Position.lastX,
x1: aabb.x1 + Position.lastX,
y0: aabb.y0 + Position.y,
y1: aabb.y1 + Position.y,
},
otherAabb,
)) {
Position.x = Position.lastX
}
else if (!intersects(
{
x0: aabb.x0 + Position.x,
x1: aabb.x1 + Position.x,
y0: aabb.y0 + Position.lastY,
y1: aabb.y1 + Position.lastY,
},
otherAabb,
)) {
Position.y = Position.lastY
}
else {
Position.x = Position.lastX
Position.y = Position.lastY
}
this.revertPosition(
this.$$aabbs[this.bodies.indexOf(body)],
other.aabbs[other.bodies.indexOf(otherBody)],
);
break;
}
}
@ -152,7 +125,7 @@ export default class Collider extends Component {
}
this.$$intersections.clear();
}
endIntersections(other, intersections) {
endIntersections(other, intersections, {runScripts}) {
const otherEntity = ecs.get(other.entity);
const thisEntity = ecs.get(this.entity);
for (const intersection of intersections) {
@ -160,7 +133,7 @@ export default class Collider extends Component {
intersection.entity.bodies[intersection.i],
intersection.other.bodies[intersection.j],
];
if (this.collisionEndScript) {
if (runScripts && this.collisionEndScript) {
const script = this.collisionEndScript.clone();
script.locals.other = otherEntity;
script.locals.pair = [body, otherBody];
@ -169,7 +142,7 @@ export default class Collider extends Component {
ecs.addDestructionDependency(thisEntity.id, promise);
ecs.addDestructionDependency(otherEntity.id, promise);
}
if (other.collisionEndScript) {
if (runScripts && other.collisionEndScript) {
const script = other.collisionEndScript.clone();
script.locals.other = thisEntity;
script.locals.pair = [otherBody, body];
@ -228,30 +201,53 @@ export default class Collider extends Component {
}
return false;
}
revertPosition(aabb, otherAabb) {
const {Position} = ecs.get(this.entity);
const {lastX, lastY} = Position;
const {x, y} = Position;
if (intersects(
{
x0: aabb.x0 + lastX,
x1: aabb.x1 + lastX,
y0: aabb.y0 + y,
y1: aabb.y1 + y,
},
otherAabb,
)) {
Position.y = lastY;
Position.lastY = lastY;
}
if (intersects(
{
x0: aabb.x0 + x,
x1: aabb.x1 + x,
y0: aabb.y0 + lastY,
y1: aabb.y1 + lastY,
},
otherAabb,
)) {
Position.x = lastX;
Position.lastX = lastX;
}
}
updateAabbs() {
this.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
this.$$aabbs = [];
const {bodies} = this;
const {Direction: {direction = 0} = {}} = ecs.get(this.entity) || {};
let {Direction: {direction = 0} = {}} = ecs.get(this.entity) || {};
if (!this.rotatesCollision) {
direction = 0;
}
for (const body of bodies) {
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
for (const point of transform(body.points, {rotation: direction})) {
const points = transform(body.points, {rotation: direction});
this.$$aabbs.push(aabbFromPoints(points));
for (const point of points) {
const {x, y} = point;
if (x < x0) x0 = x;
if (x < this.$$aabb.x0) this.$$aabb.x0 = x;
if (x > x1) x1 = x;
if (x > this.$$aabb.x1) this.$$aabb.x1 = x;
if (y < y0) y0 = y;
if (y < this.$$aabb.y0) this.$$aabb.y0 = y;
if (y > y1) y1 = y;
if (y > this.$$aabb.y1) this.$$aabb.y1 = y;
}
this.$$aabbs.push({
x0: x0 > x1 ? x1 : x0,
x1: x0 > x1 ? x0 : x1,
y0: y0 > y1 ? y1 : y0,
y1: y0 > y1 ? y0 : y1,
});
}
}
}
@ -259,8 +255,8 @@ export default class Collider extends Component {
load(instance) {
for (const i in instance.bodies) {
instance.bodies[i] = {
...this.constructor.schema.constructor.defaultValue(
this.constructor.schema.specification.concrete.properties.bodies.concrete.subtype,
...this.schema.constructor.defaultValue(
this.schema.specification.concrete.properties.bodies.concrete.subtype,
),
...instance.bodies[i],
};
@ -292,5 +288,6 @@ export default class Collider extends Component {
collisionEndScript: {type: 'script'},
collisionStartScript: {type: 'script'},
isColliding: {defaultValue: 1, type: 'uint8'},
rotatesCollision: {defaultValue: 1, type: 'uint8'},
};
}

View File

@ -17,12 +17,6 @@ export default class Forces extends Component {
applyImpulse({x, y}) {
this.$$impulseX += x;
this.$$impulseY += y;
ecs.markChange(this.entity, {
Forces: {
impulseX: this.$$impulseX,
impulseY: this.$$impulseY,
},
});
}
}
}

View File

@ -0,0 +1,7 @@
import Component from '@/silphius/ecs/component.js';
export default class Label extends Component {
static properties = {
label: {type: 'string'},
};
}

View File

@ -48,4 +48,7 @@ export default class Player extends Component {
}
};
}
static properties = {
id: {type: 'string'},
};
}

View File

@ -31,6 +31,10 @@ export default class Position extends Component {
}
};
}
load(instance) {
instance.lastX = instance.$$x;
instance.lastY = instance.$$y;
}
static properties = {
x: {type: 'float32'},
y: {type: 'float32'},

View File

@ -103,6 +103,10 @@ class LayerProxy {
? this.Component.ecs.readJson(this.layer.source)
: {};
}
get size() {
const {area, tileSize} = this;
return {x: area.x * tileSize.x, y: area.y * tileSize.y};
}
get source() {
return this.layer.source;
}

View File

@ -283,29 +283,7 @@ export default class Ecs {
}
static deserialize(ecs, view) {
const componentNames = Object.keys(ecs.Components);
const {entities, systems} = decoder.decode(view.buffer);
for (const system of systems) {
const System = ecs.system(system);
if (System) {
System.active = true;
}
}
const specifics = [];
let max = 1;
for (const id in entities) {
max = Math.max(max, parseInt(id));
specifics.push([
parseInt(id),
Object.fromEntries(
Object.entries(entities[id])
.filter(([componentName]) => componentNames.includes(componentName)),
),
]);
}
ecs.$$caret = max + 1;
ecs.createManySpecific(specifics);
return ecs;
return ecs.fromJSON(decoder.decode(view.buffer));
}
destroy(entityId) {
@ -358,6 +336,32 @@ export default class Ecs {
return ids;
}
fromJSON(json) {
const componentNames = Object.keys(this.Components);
const {entities, systems} = json;
for (const system of systems) {
const System = this.system(system);
if (System) {
System.active = true;
}
}
const specifics = [];
let max = 1;
for (const id in entities) {
max = Math.max(max, parseInt(id));
specifics.push([
parseInt(id),
Object.fromEntries(
Object.entries(entities[id])
.filter(([componentName]) => componentNames.includes(componentName)),
),
]);
}
this.$$caret = max + 1;
this.createManySpecific(specifics);
return this;
}
get(entityId) {
return this.$$entities[entityId];
}

View File

@ -31,7 +31,7 @@ export default class EntityFactory {
let size = 0;
for (const componentName of this.constructor.componentNames) {
const instance = Components[componentName];
size += 2 + 4 + instance.constructor.schema.sizeOf(instance.get(this.id));
size += 2 + 4 + instance.schema.sizeOf(instance.get(this.id));
}
// ID + # of components.
return size + 4 + 2;

View File

@ -7,16 +7,11 @@ export default function () {
json: (value) => {
return value ? value.path : '';
},
set: (Component, receiver, key, value) => {
set: (receiver, key, value, {ecs}) => {
if (!value) {
return;
}
receiver[key] = Component.ecs.readScript(
value,
{
ecs: Component.ecs,
},
);
receiver[key] = ecs.readScript(value, {ecs});
},
};
}

View File

@ -44,7 +44,7 @@ export default class Schema {
}
serialize(source, view, offset = 0) {
this.constructor.serialize(source, view, offset, this.specification);
return this.constructor.serialize(source, view, offset, this.specification);
}
static sizeOf(instance, {$, concrete}) {

View File

@ -2,6 +2,24 @@ import {System} from '@/silphius/ecs/index.js';
export default class Colliders extends System {
predict(entity) {
if (!entity.Collider) {
return;
}
const within = this.ecs.system('MaintainColliderHash').collisions(entity);
for (const other of within) {
if (entity === other || !other.Collider) {
continue;
}
entity.Collider.checkCollision(other.Collider, {runScripts: false});
}
for (const [other, intersections] of entity.Collider.$$intersections) {
if (!within.has(this.ecs.get(other.entity))) {
entity.Collider.endIntersections(other, intersections, {runScripts: false});
}
}
}
static get priority() {
return {
after: 'MaintainColliderHash',
@ -29,11 +47,11 @@ export default class Colliders extends System {
continue;
}
checked.get(other).add(entity);
entity.Collider.checkCollision(other.Collider);
entity.Collider.checkCollision(other.Collider, {runScripts: true});
}
for (const [other, intersections] of entity.Collider.$$intersections) {
if (!within.has(this.ecs.get(other.entity))) {
entity.Collider.endIntersections(other, intersections);
entity.Collider.endIntersections(other, intersections, {runScripts: true});
}
}
}

View File

@ -23,14 +23,14 @@ export default class IntegratePhysics extends System {
if (!Forces || !Position) {
return;
}
Position.lastX = Position.$$x;
Position.lastY = Position.$$y;
const xd = elapsed * (Forces.$$impulseX + Forces.$$forceX);
if (xd) {
Position.lastX = Position.$$x;
Position.x = Position.$$x + xd;
}
const yd = elapsed * (Forces.$$impulseY + Forces.$$forceY);;
if (yd) {
Position.lastY = Position.$$y;
Position.y = Position.$$y + yd;
}
}

View File

@ -28,10 +28,10 @@ export default class ResetForces extends System {
return;
}
if (Forces.$$impulseX) {
Forces.impulseX = 0;
Forces.$$impulseX = 0;
}
if (Forces.$$impulseY) {
Forces.impulseY = 0;
Forces.$$impulseY = 0;
}
}

View File

@ -0,0 +1,68 @@
import {aabbFromPoints, intersects} from '@/lib/math.js';
import {System} from '@/silphius/ecs/index.js';
export default class TileCollision extends System {
predict(entity) {
this.tickSingle(entity);
}
static get priority() {
return {
after: 'Colliders',
};
}
tick() {
for (const entity of this.ecs.changed(['Position'])) {
this.tickSingle(entity);
}
}
tickSingle(entity) {
if (!entity.Collider) {
return;
}
const master = this.ecs.get(1);
const {TileLayers} = master;
const {tileSize} = TileLayers.layer(0);
const {$$aabbs, aabb, aabbs, bodies} = entity.Collider;
const tx0 = Math.floor(aabb.x0 / tileSize.x);
const tx1 = Math.floor(aabb.x1 / tileSize.x);
const ty0 = Math.floor(aabb.y0 / tileSize.y);
const ty1 = Math.floor(aabb.y1 / tileSize.y);
for (const i in aabbs) {
if (bodies[i].unstoppable) {
continue;
}
const aabb = aabbs[i];
for (let x = tx0; x <= tx1; ++x) {
for (let y = ty0; y <= ty1; ++y) {
for (let j = 0; j < TileLayers.layers.length; ++j) {
const layer = TileLayers.layer(j);
const {collision} = layer.sourceJson.meta;
if (!collision) {
return;
}
const otherBodies = collision[layer.tile({x, y})];
if (!otherBodies) {
continue;
}
for (const k in otherBodies) {
const otherAabb = aabbFromPoints(otherBodies[k].points);
otherAabb.x0 += x * tileSize.x + (tileSize.x / 2);
otherAabb.x1 += x * tileSize.x + (tileSize.x / 2);
otherAabb.y0 += y * tileSize.y + (tileSize.y / 2);
otherAabb.y1 += y * tileSize.y + (tileSize.y / 2);
// todo accuracy
if (intersects(aabb, otherAabb)) {
entity.Collider.revertPosition($$aabbs[i], otherAabb);
}
}
}
}
}
}
}
}

View File

@ -1,10 +1,12 @@
import {CLIENT_LATENCY, CLIENT_INTERPOLATION, CLIENT_PREDICTION} from '@/lib/constants.js';
import EventEmitter from '@/lib/event-emitter.js';
import {withResolvers} from '@/lib/promise.js';
import {decode, encode} from '@/silphius/net/packets/index.js';
import {Flow} from './constants.js';
export default class Client {
connected = true;
emitter = new EventEmitter();
interpolator = null;
predictor = null;
@ -70,6 +72,7 @@ export default class Client {
}
});
}
this.connected = true;
}
disconnect() {
if (CLIENT_INTERPOLATION) {
@ -80,6 +83,7 @@ export default class Client {
this.predictor?.terminate();
this.predictor = null;
}
this.connected = false;
}
receive(packed) {
this.throughput.$$down += packed.byteLength;
@ -97,6 +101,25 @@ export default class Client {
removePacketListener(type, listener) {
this.emitter.removeListener(type, listener);
}
async request(packet) {
// needs crypto strength
const id = Math.random();
const request = {
type: 'Request',
payload: {id, request: packet},
}
this.send(request);
const {promise, resolve} = withResolvers();
const listenForId = (payload) => {
if (id !== payload.id) {
return;
}
this.emitter.removeListener(listenForId);
resolve(payload.response);
}
this.emitter.addListener('Response', listenForId);
return promise;
}
send(packet) {
const transmitOrPredict = () => {
if (CLIENT_PREDICTION) {

View File

@ -15,12 +15,4 @@ export default class Packet {
return new DataView(encoder.bytes.buffer, 0, encoder.pos);
}
static pack(payload) {
return payload;
}
static unpack(packed) {
return packed;
}
}

View File

@ -1,34 +1,3 @@
import Packet from '@/silphius/net/packet.js';
const WIRE_MAP = {
'moveUp': 0,
'moveRight': 1,
'moveDown': 2,
'moveLeft': 3,
'use': 4,
'changeSlot': 5,
'interact': 6,
};
Object.entries(WIRE_MAP)
.forEach(([k, v]) => {
WIRE_MAP[v] = k;
});
export default class Action extends Packet {
static pack(payload) {
return super.pack({
type: WIRE_MAP[payload.type],
value: payload.value,
});
}
static unpack(packed) {
const unpacked = super.unpack(packed);
return {
type: WIRE_MAP[unpacked.type],
value: unpacked.value,
};
}
}
export default class Action extends Packet {}

View File

@ -0,0 +1,3 @@
import Packet from '@/silphius/net/packet.js';
export default class AdminActionResponse extends Packet {}

View File

@ -1,14 +1,14 @@
import gather from '@/lib/gather.js';
const Gathered = gather(import.meta.glob('./*.js', {eager: true, import: 'default'}));
const Packets = gather(import.meta.glob('./*.js', {eager: true, import: 'default'}));
const typeToId = new Map(Object.entries(Gathered).map(([type], id) => [type, id]));
const idToType = new Map(Object.entries(Gathered).map(([type], id) => [id, type]));
const typeToId = new Map(Object.entries(Packets).map(([type], id) => [type, id]));
const idToType = new Map(Object.entries(Packets).map(([type], id) => [id, type]));
export function decode(packed) {
const view = ArrayBuffer.isView(packed) ? packed : new DataView(packed);
const type = idToType.get(view.getUint16(0, true));
const Packet = Gathered[type];
const Packet = Packets[type];
return {
type,
payload: Packet.decode(view),
@ -16,7 +16,7 @@ export function decode(packed) {
}
export function encode(packet) {
const encoded = Gathered[packet.type].encode(packet.payload);
const encoded = Packets[packet.type].encode(packet.payload);
encoded.setUint16(0, typeToId.get(packet.type), true);
return encoded;
}

View File

@ -0,0 +1,3 @@
import Packet from '@/silphius/net/packet.js';
export default class Request extends Packet {}

View File

@ -0,0 +1,3 @@
import Packet from '@/silphius/net/packet.js';
export default class Response extends Packet {}

View File

@ -19,6 +19,7 @@ const Action = {
const actions = new Map();
let ecs = new PredictionEcs({Components, Systems});
ecs.system('MaintainColliderHash').active = true;
let mainEntityId = 0;
function applyClientActions(elapsed) {
@ -64,7 +65,9 @@ function applyClientActions(elapsed) {
continue;
}
}
ecs.predict(main, action.stop - action.start);
const actionElapsed = action.stop - action.start;
ecs.tick(actionElapsed)
ecs.predict(main, actionElapsed);
}
for (const id of finished) {
actions.delete(id);

View File

@ -26,6 +26,7 @@ export default function createEcs(Ecs) {
'Interactions',
'InventoryCloser',
'KillPerishable',
'TileCollision',
];
defaultSystems.forEach((defaultSystem) => {
const System = ecs.system(defaultSystem);

View File

@ -1,4 +1,4 @@
import data from './homestead.json';
import layers from './homestead.json';
function animal() {
return {
@ -23,25 +23,17 @@ function animal() {
}
function createMaster() {
const area = {x: 100, y: 60};
const area = {x: 25, y: 25};
return {
AreaSize: {x: area.x * 16, y: area.y * 16},
Ticking: {},
TileLayers: {
layers: [
{
area,
data,
source: '/resources/tileset.sprite.json',
tileSize: {x: 16, y: 16},
},
{
area,
data: Array(area.x * area.y).fill(0),
source: '/resources/tileset.sprite.json',
tileSize: {x: 16, y: 16},
},
],
layers: layers.map((data) => ({
area,
data,
source: '/resources/tileset/homestead.sprite.json',
tileSize: {x: 16, y: 16},
})),
},
Time: {},
Water: {water: {}},

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@ export default function createPlayer(id) {
],
},
],
rotatesCollision: 0,
},
Controlled: {},
Direction: {},

View File

@ -106,14 +106,21 @@ export default class Engine {
if (!this.incomingActions.has(connection)) {
this.incomingActions.set(connection, []);
}
this.incomingActions.get(connection).push(payload);
this.incomingActions.get(connection).push({payload});
});
this.server.addPacketListener('AdminAction', (connection, payload) => {
// check...
if (!this.incomingActions.has(connection)) {
this.incomingActions.set(connection, []);
}
this.incomingActions.get(connection).push(payload);
this.incomingActions.get(connection).push({payload});
});
this.server.addPacketListener('Request', (connection, payload) => {
if (!this.incomingActions.has(connection)) {
this.incomingActions.set(connection, []);
}
const {request} = payload;
this.incomingActions.get(connection).push({payload: request.payload, respondTo: payload.id});
});
this.server.addPacketListener('Heartbeat', (connection) => {
const playerData = this.connectedPlayers.get(connection);
@ -158,7 +165,7 @@ export default class Engine {
Wielder,
} = entity;
const ecs = this.ecses[Ecs.path];
for (const payload of payloads) {
for (const {payload, respondTo} of payloads) {
switch (payload.type) {
case 'chat': {
if (payload.value.startsWith('/')) {
@ -173,7 +180,7 @@ export default class Engine {
{
type: 'Download',
payload: {
data: TileLayers.layer(0).data,
data: TileLayers.layers.map((_, i) => TileLayers.layer(i).data),
filename: 'tiles.json',
},
},
@ -195,6 +202,29 @@ export default class Engine {
});
break;
}
case 'createEntity': {
const {path} = payload.value;
const id = ecs.create({$$extends: path});
if (respondTo) {
this.server.send(
connection,
{
type: 'Response',
payload: {
id: respondTo,
response: {type: 'AdminActionResponse', payload: {id}},
},
},
);
}
break;
}
case 'destroyEntity': {
const {id} = payload.value;
ecs.destroy(parseInt(id));
break;
}
case 'paint': {
const {TileLayers} = ecs.get(1);
const {brush, layer: paintLayer, stamp} = payload.value;
@ -213,6 +243,15 @@ export default class Engine {
}
break;
}
case 'moveEntity': {
const {at: {x, y}, id} = payload.value;
const entity = ecs.get(id);
if (entity.Position) {
entity.Position.x = x;
entity.Position.y = y;
}
break;
}
case 'moveUp':
case 'moveRight':
case 'moveDown':

View File

@ -128,7 +128,7 @@ if (import.meta.hot) {
await engine.saveEcs('homesteads/0', homestead);
resolve();
});
import.meta.hot.accept('./create/town.js', async ({default: createTown}) => {
import.meta.hot.accept('./create/town.js', async ({default: createTown}) => {
const {promise, resolve} = withResolvers();
promises.push(promise);
await before.promise;

752
package-lock.json generated
View File

@ -26,6 +26,7 @@
"cross-env": "^7.0.3",
"express": "^4.19.2",
"idb-keyval": "^6.2.1",
"immutable": "^5.0.2",
"isbot": "^4.1.0",
"kefir": "^3.8.8",
"morgan": "^1.10.0",
@ -36,7 +37,7 @@
"remark-mdx": "^3.0.1",
"remark-parse": "^11.0.0",
"simplex-noise": "^4.0.1",
"sylvite": "^1.0.3",
"sylvite": "^1.0.6",
"unified": "^11.0.5",
"unist-util-visit-parents": "^6.0.1",
"ws": "^8.18.0"
@ -62,6 +63,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"image-size": "^1.1.1",
"jimp": "^1.6.0",
"playwright": "^1.47.0",
"postcss": "^8.4.38",
"storybook": "^8.1.6",
@ -2989,6 +2991,430 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jimp/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz",
"integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==",
"dev": true,
"dependencies": {
"@jimp/file-ops": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"await-to-js": "^3.0.0",
"exif-parser": "^0.1.12",
"file-type": "^16.0.0",
"mime": "3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/core/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@jimp/diff": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz",
"integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==",
"dev": true,
"dependencies": {
"@jimp/plugin-resize": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"pixelmatch": "^5.3.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/file-ops": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz",
"integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/js-bmp": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz",
"integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"bmp-ts": "^1.0.9"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/js-gif": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz",
"integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"gifwrap": "^0.10.1",
"omggif": "^1.0.10"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/js-jpeg": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz",
"integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"jpeg-js": "^0.4.4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/js-png": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz",
"integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"pngjs": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/js-tiff": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz",
"integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"utif2": "^4.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-blit": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz",
"integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-blur": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz",
"integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/utils": "1.6.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-circle": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz",
"integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-color": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz",
"integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"tinycolor2": "^1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-contain": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz",
"integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/plugin-blit": "1.6.0",
"@jimp/plugin-resize": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-cover": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz",
"integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/plugin-crop": "1.6.0",
"@jimp/plugin-resize": "1.6.0",
"@jimp/types": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-crop": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz",
"integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-displace": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz",
"integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-dither": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz",
"integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-fisheye": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz",
"integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-flip": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz",
"integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-hash": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz",
"integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/js-bmp": "1.6.0",
"@jimp/js-jpeg": "1.6.0",
"@jimp/js-png": "1.6.0",
"@jimp/js-tiff": "1.6.0",
"@jimp/plugin-color": "1.6.0",
"@jimp/plugin-resize": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"any-base": "^1.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-mask": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz",
"integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-print": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz",
"integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/js-jpeg": "1.6.0",
"@jimp/js-png": "1.6.0",
"@jimp/plugin-blit": "1.6.0",
"@jimp/types": "1.6.0",
"parse-bmfont-ascii": "^1.0.6",
"parse-bmfont-binary": "^1.0.6",
"parse-bmfont-xml": "^1.1.6",
"simple-xml-to-json": "^1.2.2",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-quantize": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz",
"integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==",
"dev": true,
"dependencies": {
"image-q": "^4.0.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-resize": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
"integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/types": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-rotate": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz",
"integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/plugin-crop": "1.6.0",
"@jimp/plugin-resize": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/plugin-threshold": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz",
"integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/plugin-color": "1.6.0",
"@jimp/plugin-hash": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/types": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz",
"integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
"dev": true,
"dependencies": {
"zod": "^3.23.8"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@jimp/utils": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz",
"integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==",
"dev": true,
"dependencies": {
"@jimp/types": "1.6.0",
"tinycolor2": "^1.6.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@joshwooding/vite-plugin-react-docgen-typescript": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.3.1.tgz",
@ -5747,6 +6173,12 @@
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"dev": true
},
"node_modules/@types/acorn": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@ -7013,6 +7445,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-base": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
"dev": true
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@ -7294,6 +7732,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/await-to-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/axe-core": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz",
@ -7454,6 +7901,12 @@
"readable-stream": "^3.4.0"
}
},
"node_modules/bmp-ts": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
"integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@ -10063,6 +10516,12 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/exif-parser": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==",
"dev": true
},
"node_modules/exit-hook": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
@ -10235,6 +10694,23 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-type": {
"version": "16.5.4",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz",
"integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==",
"dev": true,
"dependencies": {
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.2.4",
"token-types": "^4.1.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/filesize": {
"version": "10.1.6",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
@ -10601,6 +11077,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gifwrap": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
"integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
"dev": true,
"dependencies": {
"image-q": "^4.0.0",
"omggif": "^1.0.10"
}
},
"node_modules/giget": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz",
@ -11056,6 +11542,21 @@
"node": ">= 4"
}
},
"node_modules/image-q": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
"integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
"dev": true,
"dependencies": {
"@types/node": "16.9.1"
}
},
"node_modules/image-q/node_modules/@types/node": {
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
"integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
"dev": true
},
"node_modules/image-size": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
@ -11071,6 +11572,11 @@
"node": ">=16.x"
}
},
"node_modules/immutable": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz",
"integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -11826,6 +12332,50 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true
},
"node_modules/jimp": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz",
"integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==",
"dev": true,
"dependencies": {
"@jimp/core": "1.6.0",
"@jimp/diff": "1.6.0",
"@jimp/js-bmp": "1.6.0",
"@jimp/js-gif": "1.6.0",
"@jimp/js-jpeg": "1.6.0",
"@jimp/js-png": "1.6.0",
"@jimp/js-tiff": "1.6.0",
"@jimp/plugin-blit": "1.6.0",
"@jimp/plugin-blur": "1.6.0",
"@jimp/plugin-circle": "1.6.0",
"@jimp/plugin-color": "1.6.0",
"@jimp/plugin-contain": "1.6.0",
"@jimp/plugin-cover": "1.6.0",
"@jimp/plugin-crop": "1.6.0",
"@jimp/plugin-displace": "1.6.0",
"@jimp/plugin-dither": "1.6.0",
"@jimp/plugin-fisheye": "1.6.0",
"@jimp/plugin-flip": "1.6.0",
"@jimp/plugin-hash": "1.6.0",
"@jimp/plugin-mask": "1.6.0",
"@jimp/plugin-print": "1.6.0",
"@jimp/plugin-quantize": "1.6.0",
"@jimp/plugin-resize": "1.6.0",
"@jimp/plugin-rotate": "1.6.0",
"@jimp/plugin-threshold": "1.6.0",
"@jimp/types": "1.6.0",
"@jimp/utils": "1.6.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jpeg-js": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"dev": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -15873,6 +16423,12 @@
"integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==",
"dev": true
},
"node_modules/omggif": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
"dev": true
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -16046,6 +16602,28 @@
"node": ">=6"
}
},
"node_modules/parse-bmfont-ascii": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
"integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
"dev": true
},
"node_modules/parse-bmfont-binary": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
"integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
"dev": true
},
"node_modules/parse-bmfont-xml": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
"integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
"dev": true,
"dependencies": {
"xml-parse-from-string": "^1.0.0",
"xml2js": "^0.5.0"
}
},
"node_modules/parse-entities": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
@ -16196,6 +16774,19 @@
"node": "*"
}
},
"node_modules/peek-readable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/peek-stream": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz",
@ -16260,6 +16851,27 @@
"node": ">= 6"
}
},
"node_modules/pixelmatch": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
"integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
"dev": true,
"dependencies": {
"pngjs": "^6.0.0"
},
"bin": {
"pixelmatch": "bin/pixelmatch"
}
},
"node_modules/pixelmatch/node_modules/pngjs": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
"dev": true,
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/pixi.js": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.4.2.tgz",
@ -16406,6 +17018,15 @@
"node": ">=18"
}
},
"node_modules/pngjs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
"dev": true,
"engines": {
"node": ">=14.19.0"
}
},
"node_modules/polished": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
@ -17049,6 +17670,22 @@
"node": ">= 6"
}
},
"node_modules/readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"dev": true,
"dependencies": {
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -18238,6 +18875,12 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@ -18414,6 +19057,15 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/simple-xml-to-json": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz",
"integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==",
"dev": true,
"engines": {
"node": ">=20.12.2"
}
},
"node_modules/simplex-noise": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-4.0.3.tgz",
@ -18957,6 +19609,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strtok3": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
"dev": true,
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^4.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/style-to-object": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz",
@ -18991,9 +19660,9 @@
}
},
"node_modules/sylvite": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sylvite/-/sylvite-1.0.3.tgz",
"integrity": "sha512-/2hURIj84fmKd2XtX9IAm3VShBDPWnLHEQrMzocG0q3ic1aW3yaYQKKQ3+FnWcFkMqapWv8ySFhyDo6sz2TZbw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/sylvite/-/sylvite-1.0.6.tgz",
"integrity": "sha512-aJ43Elol6QPUsL+eg9SewQjesY/x/UtsggEX0pvsDPcwnhvf8afx5rwSkLTXrbufBA/y4YurT4VqWBrIZNKDtg==",
"dependencies": {
"tapable": "^2.2.1",
"vite-plugin-restart": "^0.4.1"
@ -19336,6 +20005,12 @@
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
"dev": true
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
"dev": true
},
"node_modules/tinypool": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz",
@ -19391,6 +20066,23 @@
"node": ">=0.6"
}
},
"node_modules/token-types": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz",
"integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==",
"dev": true,
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/toml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
@ -20072,6 +20764,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/utif2": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
"integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
"dev": true,
"dependencies": {
"pako": "^1.0.11"
}
},
"node_modules/utif2/node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@ -21307,6 +22014,34 @@
}
}
},
"node_modules/xml-parse-from-string": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
"dev": true
},
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dev": true,
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -21414,6 +22149,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View File

@ -32,6 +32,7 @@
"cross-env": "^7.0.3",
"express": "^4.19.2",
"idb-keyval": "^6.2.1",
"immutable": "^5.0.2",
"isbot": "^4.1.0",
"kefir": "^3.8.8",
"morgan": "^1.10.0",
@ -42,7 +43,7 @@
"remark-mdx": "^3.0.1",
"remark-parse": "^11.0.0",
"simplex-noise": "^4.0.1",
"sylvite": "^1.0.3",
"sylvite": "^1.0.6",
"unified": "^11.0.5",
"unist-util-visit-parents": "^6.0.1",
"ws": "^8.18.0"
@ -68,6 +69,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"image-size": "^1.1.1",
"jimp": "^1.6.0",
"playwright": "^1.47.0",
"postcss": "^8.4.38",
"storybook": "^8.1.6",
@ -78,4 +80,4 @@
"engines": {
"node": ">=20.0.0"
}
}
}

View File

@ -0,0 +1,23 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{"x": -8, "y": -1},
{"x": 8, "y": -1},
{"x": -8, "y": 1},
{"x": 8, "y": 1}
],
"unstoppable": 1
}
]
},
"Label": {"label": "Fence - Horizontal Left"},
"Position": {},
"Sprite": {
"anchorY": 0.9,
"source": "/resources/ambient/construction/fence-h-l.sprite.json"
},
"VisibleAabb": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-l.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-l.normals.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,23 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{"x": -8, "y": -1},
{"x": 8, "y": -1},
{"x": -8, "y": 1},
{"x": 8, "y": 1}
],
"unstoppable": 1
}
]
},
"Label": {"label": "Fence - Horizontal LeftMiddle"},
"Position": {},
"Sprite": {
"anchorY": 0.9,
"source": "/resources/ambient/construction/fence-h-lm.sprite.json"
},
"VisibleAabb": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-lm.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-lm.normals.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,23 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{"x": -8, "y": -1},
{"x": 8, "y": -1},
{"x": -8, "y": 1},
{"x": 8, "y": 1}
],
"unstoppable": 1
}
]
},
"Label": {"label": "Fence - Horizontal Middle"},
"Position": {},
"Sprite": {
"anchorY": 0.9,
"source": "/resources/ambient/construction/fence-h-m.sprite.json"
},
"VisibleAabb": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-m.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-m.normals.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,23 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{"x": -8, "y": -1},
{"x": 8, "y": -1},
{"x": -8, "y": 1},
{"x": 8, "y": 1}
],
"unstoppable": 1
}
]
},
"Label": {"label": "Fence - Horizontal Right"},
"Position": {},
"Sprite": {
"anchorY": 0.9,
"source": "/resources/ambient/construction/fence-h-r.sprite.json"
},
"VisibleAabb": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-r.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-r.normals.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,23 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{"x": -8, "y": -1},
{"x": 8, "y": -1},
{"x": -8, "y": 1},
{"x": 8, "y": 1}
],
"unstoppable": 1
}
]
},
"Label": {"label": "Fence - Horizontal RightMiddle"},
"Position": {},
"Sprite": {
"anchorY": 0.9,
"source": "/resources/ambient/construction/fence-h-rm.sprite.json"
},
"VisibleAabb": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-rm.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,31 @@
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 16,
"h": 32
},
"sourceSize": {
"w": 16,
"h": 32
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./fence-h-rm.normals.png",
"scale": 1,
"size": {
"w": 16,
"h": 32
}
}
}

View File

@ -0,0 +1,9 @@
{
"Label": {"label": "Shrub"},
"Position": {},
"Sprite": {
"anchorY": 0.7,
"source": "/resources/ambient/shrub.sprite.json"
},
"VisibleAabb": {}
}

View File

@ -4,7 +4,7 @@ export default function*({ecs, projected}) {
const filtered = [];
for (const position of projected) {
if (
[1, 2, 3, 4, 6].includes(layer0.tile(position))
[224, 225, 226, 227, 242, 243, 244, 245].includes(layer0.tile(position))
&& ![7].includes(layer1.tile(position))
) {
filtered.push(position);

View File

@ -0,0 +1,35 @@
{
"Collider": {
"bodies": [
{
"impassable": 1,
"points": [
{
"x": -40,
"y": -40
},
{
"x": 40,
"y": -20
},
{
"x": -40,
"y": 20
},
{
"x": 40,
"y": 20
}
]
}
]
},
"Position": {},
"Sprite": {
"anchorX": 0.5,
"anchorY": 0.8,
"source": "/resources/shit-shack/shit-shack.sprite.json"
},
"Ticking": {},
"VisibleAabb": {}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -4,18 +4,18 @@
"frame": {
"x": 0,
"y": 0,
"w": 110,
"h": 102
"w": 88,
"h": 115
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 110,
"h": 102
"w": 88,
"h": 115
},
"sourceSize": {
"w": 110,
"h": 102
"w": 88,
"h": 115
}
}
},
@ -24,8 +24,8 @@
"image": "./shit-shack.png",
"scale": 1,
"size": {
"w": 110,
"h": 102
"w": 88,
"h": 115
}
}
}

View File

@ -1 +1,31 @@
{"frames":{"":{"frame":{"x":0,"y":0,"w":110,"h":102},"spriteSourceSize":{"x":0,"y":0,"w":110,"h":102},"sourceSize":{"w":110,"h":102}}},"meta":{"format":"RGBA8888","image":"./shit-shack.normals.png","scale":1,"size":{"w":110,"h":102}}}
{
"frames": {
"": {
"frame": {
"x": 0,
"y": 0,
"w": 88,
"h": 115
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 88,
"h": 115
},
"sourceSize": {
"w": 88,
"h": 115
}
}
},
"meta": {
"format": "RGBA8888",
"image": "./shit-shack.normals.png",
"scale": 1,
"size": {
"w": 88,
"h": 115
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
resources/tileset/homestead.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
"source": "/resources/tomato/tomato.item.json"
},
"Magnetic": {},
"Position": {},
"Sprite": {
"anchorX": 0.5,
"anchorY": 0.5,

View File

@ -59,6 +59,9 @@ export default defineConfig(hooks.call('sylvite:viteConfig', {
},
server: {
host: true,
hmr: {
port: 3101,
},
...(!isInsecure && {
https: {
key: readFileSync(httpsKey),