Compare commits
19 Commits
782fdfc28c
...
324123df14
Author | SHA1 | Date | |
---|---|---|---|
|
324123df14 | ||
|
ceaa7c1287 | ||
|
11457a3c75 | ||
|
e8c9633a4e | ||
|
0e8e58b365 | ||
|
9872cbb423 | ||
|
626df5b912 | ||
|
2e6e339d6d | ||
|
9494e0b901 | ||
|
4eef129c53 | ||
|
4218f1adc7 | ||
|
e599a10186 | ||
|
b72a0db2be | ||
|
21fa00da85 | ||
|
01e085499a | ||
|
5fa3b72880 | ||
|
531266f93e | ||
|
c499afb246 | ||
|
bd5c069090 |
|
@ -20,29 +20,19 @@ module.exports = {
|
|||
es6: true,
|
||||
},
|
||||
globals: {
|
||||
process: false,
|
||||
WeakRef: false,
|
||||
},
|
||||
rules: {
|
||||
'no-constant-condition': ['error', {checkLoops: false}],
|
||||
},
|
||||
ignorePatterns: ['!**/.server', '!**/.client'],
|
||||
|
||||
// Base config
|
||||
extends: ['eslint:recommended'],
|
||||
rules: {
|
||||
'no-constant-condition': ['error', {checkLoops: false}],
|
||||
'require-yield': 0,
|
||||
},
|
||||
|
||||
overrides: [
|
||||
// Tests
|
||||
{
|
||||
files: ['**/*.test.{js,jsx,ts,tsx}'],
|
||||
rules: {
|
||||
'no-empty-pattern': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// React
|
||||
{
|
||||
files: ['**/*.{jsx,tsx}'],
|
||||
files: ['**/*.{js,jsx}'],
|
||||
plugins: ['react', 'jsx-a11y'],
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
|
@ -56,8 +46,8 @@ module.exports = {
|
|||
},
|
||||
formComponents: ['Form'],
|
||||
linkComponents: [
|
||||
{ name: 'Link', linkAttribute: 'to' },
|
||||
{ name: 'NavLink', linkAttribute: 'to' },
|
||||
{name: 'Link', linkAttribute: 'to'},
|
||||
{name: 'NavLink', linkAttribute: 'to'},
|
||||
],
|
||||
},
|
||||
rules: {
|
||||
|
@ -71,15 +61,28 @@ module.exports = {
|
|||
// Node
|
||||
{
|
||||
files: [
|
||||
'app/websocket.js',
|
||||
'.eslintrc.cjs',
|
||||
'server.js',
|
||||
'app/websocket/**',
|
||||
'**/.server/**',
|
||||
'*.server.{js,jsx}',
|
||||
'**/{build,node}.js',
|
||||
'vite.config.js',
|
||||
'vitest.workspace.js',
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
|
||||
// game scripts
|
||||
{
|
||||
files: [
|
||||
'resources/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'require-yield': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,7 +2,7 @@ node_modules
|
|||
|
||||
/.cache
|
||||
/build
|
||||
/coverage
|
||||
.env
|
||||
|
||||
/data
|
||||
/dev
|
||||
.env
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
process.env.STORYBOOK = 1
|
||||
process.env.REMIX_DISABLED = 1
|
||||
|
||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
stories: ['../app/**/*.stories.mdx', '../app/**/*.stories.@(js|jsx|mjs)'],
|
||||
};
|
||||
export default config;
|
||||
|
|
9
.vscode/css_custom_data.json
vendored
Normal file
9
.vscode/css_custom_data.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"version": 1.1,
|
||||
"atDirectives": [
|
||||
{
|
||||
"name": "@tailwind",
|
||||
"description": "Use the @tailwind directive to insert Tailwind's `base`, `components`, `utilities`, and `screens` styles into your CSS."
|
||||
}
|
||||
]
|
||||
}
|
49
.vscode/launch.json
vendored
49
.vscode/launch.json
vendored
|
@ -7,59 +7,38 @@
|
|||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Silphius Chrome",
|
||||
"url": "https://localhost:3000",
|
||||
"name": "Client",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"runtimeArgs": ["--auto-open-devtools-for-tabs"]
|
||||
"outputCapture": "std",
|
||||
"runtimeArgs": [
|
||||
"https://localhost:3200/",
|
||||
"https://localhost:3200/storybook"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Silphius Dev",
|
||||
"name": "Server",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"resolveSourceMapLocations": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"MKCERT": "1",
|
||||
"PORT": "3200"
|
||||
},
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "dev"],
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Storybook Chrome",
|
||||
"url": "http://localhost:6006",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Storybook Dev",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"resolveSourceMapLocations": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "storybook", "--", "--no-open"],
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Silphius",
|
||||
"name": "App",
|
||||
"configurations": [
|
||||
"Silphius Dev",
|
||||
"Silphius Chrome",
|
||||
"Server",
|
||||
"Client",
|
||||
],
|
||||
"stopAll": true,
|
||||
},
|
||||
{
|
||||
"name": "Storybook",
|
||||
"configurations": [
|
||||
"Storybook Dev",
|
||||
"Storybook Chrome",
|
||||
],
|
||||
"stopAll": true,
|
||||
}
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"css.customData": [".vscode/css_custom_data.json"]
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
import Client from '@/net/client.js';
|
||||
import {decode, encode} from '@/net/packets/index.js';
|
||||
import {CLIENT_INTERPOLATION, CLIENT_PREDICTION} from '@/util/constants.js';
|
||||
|
||||
export default class LocalClient extends Client {
|
||||
server = null;
|
||||
interpolator = null;
|
||||
predictor = null;
|
||||
async connect() {
|
||||
this.server = new Worker(
|
||||
new URL('../server/worker.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator = new Worker(
|
||||
new URL('./interpolator.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
this.interpolator.addEventListener('message', (event) => {
|
||||
const packet = event.data;
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor = new Worker(
|
||||
new URL('./predictor.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
this.predictor.addEventListener('message', (event) => {
|
||||
const [flow, packet] = event.data;
|
||||
switch (flow) {
|
||||
case 0: {
|
||||
const packed = encode(packet);
|
||||
this.throughput.$$up += packed.byteLength;
|
||||
this.server.postMessage(packed);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
this.accept(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this.server.addEventListener('message', (event) => {
|
||||
if (0 === event.data) {
|
||||
this.server.terminate();
|
||||
this.server = null;
|
||||
return;
|
||||
}
|
||||
this.throughput.$$down += event.data.byteLength;
|
||||
const packet = decode(event.data);
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.postMessage(packet);
|
||||
}
|
||||
else if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
disconnect() {
|
||||
this.server.postMessage(0);
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.terminate();
|
||||
}
|
||||
}
|
||||
transmit(packet) {
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([0, packet]);
|
||||
}
|
||||
else {
|
||||
const packed = encode(packet);
|
||||
this.throughput.$$up += packed.byteLength;
|
||||
this.server.postMessage(packed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
import Client from '@/net/client.js';
|
||||
import {decode, encode} from '@/net/packets/index.js';
|
||||
import {CLIENT_INTERPOLATION, CLIENT_PREDICTION} from '@/util/constants.js';
|
||||
|
||||
export default class RemoteClient extends Client {
|
||||
socket = null;
|
||||
interpolator = null;
|
||||
predictor = null;
|
||||
async connect(host) {
|
||||
this.interpolator = new Worker(
|
||||
new URL('./interpolator.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator = new Worker(
|
||||
new URL('./interpolator.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
this.interpolator.addEventListener('message', (event) => {
|
||||
this.accept(event.data);
|
||||
});
|
||||
}
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor = new Worker(
|
||||
new URL('./predictor.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
this.predictor.addEventListener('message', (event) => {
|
||||
const [flow, packet] = event.data;
|
||||
switch (flow) {
|
||||
case 0: {
|
||||
const packed = encode(packet);
|
||||
this.throughput.$$up += packed.byteLength;
|
||||
this.socket.send(packed);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.postMessage(packet);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const url = new URL(`wss://${host}/ws`)
|
||||
this.socket = new WebSocket(url.href);
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
this.socket.addEventListener('message', (event) => {
|
||||
this.throughput.$$down += event.data.byteLength;
|
||||
const packet = decode(event.data);
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.postMessage(packet);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
});
|
||||
this.socket.addEventListener('close', () => {
|
||||
this.accept({type: 'ConnectionStatus', payload: 'aborted'});
|
||||
});
|
||||
this.socket.addEventListener('error', () => {
|
||||
this.accept({type: 'ConnectionStatus', payload: 'aborted'});
|
||||
});
|
||||
this.accept({type: 'ConnectionStatus', payload: 'connected'});
|
||||
}
|
||||
disconnect() {
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.terminate();
|
||||
}
|
||||
}
|
||||
transmit(packet) {
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([0, packet]);
|
||||
}
|
||||
else {
|
||||
const packed = encode(packet);
|
||||
this.throughput.$$up += packed.byteLength;
|
||||
this.socket.send(packed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Grabber extends Component {}
|
|
@ -1,31 +0,0 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Harmful extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
return class HarmfulInstance extends super.instanceFromSchema() {
|
||||
harm(other) {
|
||||
const entity = ecs.get(this.entity);
|
||||
const script = this.$$harm.clone();
|
||||
script.locals.other = other;
|
||||
script.locals.entity = entity;
|
||||
entity.Ticking.add(script.ticker());
|
||||
}
|
||||
}
|
||||
}
|
||||
load(instance) {
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.$$harm = this.ecs.readScript(
|
||||
instance.harmScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
},
|
||||
);
|
||||
}
|
||||
static properties = {
|
||||
harmScript: {type: 'string'},
|
||||
};
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Plant extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
const Instance = super.instanceFromSchema();
|
||||
return class PlantInstance extends Instance {
|
||||
$$grow;
|
||||
$$mayGrow;
|
||||
grow() {
|
||||
const {Ticking} = ecs.get(this.entity);
|
||||
Ticking.add(this.$$grow.ticker());
|
||||
}
|
||||
mayGrow() {
|
||||
return this.$$mayGrow.evaluate();
|
||||
}
|
||||
};
|
||||
}
|
||||
load(instance) {
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.$$grow = this.ecs.readScript(
|
||||
instance.growScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
plant: instance,
|
||||
},
|
||||
);
|
||||
instance.$$mayGrow = this.ecs.readScript(
|
||||
instance.mayGrowScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
plant: instance,
|
||||
},
|
||||
);
|
||||
}
|
||||
// heavy handed...
|
||||
markChange() {}
|
||||
static properties = {
|
||||
growScript: {type: 'string'},
|
||||
growth: {type: 'uint16'},
|
||||
growthFactor: {defaultValue: 127, type: 'uint8'},
|
||||
mayGrowScript: {type: 'string'},
|
||||
stage: {type: 'uint8'},
|
||||
stages: {
|
||||
type: 'array',
|
||||
subtype: {type: 'uint16'},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Shop extends Component {}
|
|
@ -4,9 +4,9 @@
|
|||
* For more information, see https://remix.run/file-conventions/entry.client
|
||||
*/
|
||||
|
||||
import { RemixBrowser } from "@remix-run/react";
|
||||
import { startTransition, StrictMode } from "react";
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
import {RemixBrowser} from '@remix-run/react';
|
||||
import {startTransition, StrictMode} from 'react';
|
||||
import {hydrateRoot} from 'react-dom/client';
|
||||
|
||||
startTransition(() => {
|
||||
hydrateRoot(
|
||||
|
|
|
@ -4,17 +4,18 @@
|
|||
* For more information, see https://remix.run/file-conventions/entry.server
|
||||
*/
|
||||
|
||||
import { PassThrough } from "node:stream";
|
||||
import {PassThrough} from 'node:stream';
|
||||
|
||||
import { createReadableStreamFromReadable } from "@remix-run/node";
|
||||
import { RemixServer } from "@remix-run/react";
|
||||
import { isbot } from "isbot";
|
||||
import { renderToPipeableStream } from "react-dom/server";
|
||||
import {createReadableStreamFromReadable} from '@remix-run/node';
|
||||
import {RemixServer} from '@remix-run/react';
|
||||
import {isbot} from 'isbot';
|
||||
import {renderToPipeableStream} from 'react-dom/server';
|
||||
import {hooks} from 'virtual:sylvite/server';
|
||||
|
||||
const ABORT_DELAY = 5_000;
|
||||
|
||||
export async function handleUpgrade(request, socket, head) {
|
||||
const {handleUpgrade} = await import('./server/websocket.js');
|
||||
const {handleUpgrade} = await import('./websocket.js');
|
||||
handleUpgrade(request, socket, head);
|
||||
}
|
||||
|
||||
|
@ -23,23 +24,21 @@ export default function handleRequest(
|
|||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext,
|
||||
// This is ignored so we can keep it in the template for visibility. Feel
|
||||
// free to delete this parameter in your app if you're not using it!
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
loadContext
|
||||
) {
|
||||
return isbot(request.headers.get("user-agent") || "")
|
||||
return isbot(request.headers.get('user-agent') || '')
|
||||
? handleBotRequest(
|
||||
request,
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext
|
||||
remixContext,
|
||||
)
|
||||
: handleBrowserRequest(
|
||||
request,
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext
|
||||
remixContext,
|
||||
loadContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -51,7 +50,7 @@ function handleBotRequest(
|
|||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let shellRendered = false;
|
||||
const { pipe, abort } = renderToPipeableStream(
|
||||
const {pipe, abort} = renderToPipeableStream(
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
|
@ -63,7 +62,7 @@ function handleBotRequest(
|
|||
const body = new PassThrough();
|
||||
const stream = createReadableStreamFromReadable(body);
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html");
|
||||
responseHeaders.set('Content-Type', 'text/html');
|
||||
|
||||
resolve(
|
||||
new Response(stream, {
|
||||
|
@ -97,11 +96,12 @@ function handleBrowserRequest(
|
|||
request,
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext
|
||||
remixContext,
|
||||
loadContext,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let shellRendered = false;
|
||||
const { pipe, abort } = renderToPipeableStream(
|
||||
const {pipe, abort} = renderToPipeableStream(
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
|
@ -110,15 +110,11 @@ function handleBrowserRequest(
|
|||
{
|
||||
onShellReady() {
|
||||
shellRendered = true;
|
||||
const body = new PassThrough();
|
||||
const body = hooks.call('@/sylvite/cha0s-remix:requestBody', new PassThrough(), request, loadContext);
|
||||
const stream = createReadableStreamFromReadable(body);
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html");
|
||||
// security
|
||||
// responseHeaders.set("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
// responseHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
|
||||
// responseHeaders.set("Cross-Origin-Resource-Policy", "same-site");
|
||||
// responseHeaders.set("Content-Security-Policy", "default-src 'self'");
|
||||
responseHeaders.set('Content-Type', 'text/html');
|
||||
hooks.call('@/sylvite/cha0s-remix:responseHeaders', responseHeaders, request, loadContext);
|
||||
|
||||
resolve(
|
||||
new Response(stream, {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Ticker from '@/util/ticker.js';
|
||||
import Ticker from '@/lib/ticker.js';
|
||||
|
||||
export default function delta(object, properties) {
|
||||
const deltas = {};
|
|
@ -5,7 +5,7 @@ import {createNoise2D} from 'simplex-noise';
|
|||
import {unified} from 'unified';
|
||||
import {visitParents as visit} from 'unist-util-visit-parents';
|
||||
|
||||
import {TAU} from '@/util/math.js';
|
||||
import {TAU} from '@/lib/math.js';
|
||||
|
||||
const rawNoise = createNoise2D();
|
||||
const noise = (x, y) => (1 + rawNoise(x, y)) / 2;
|
14
app/lib/event-source.js
Normal file
14
app/lib/event-source.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import streamResponse from './stream-response';
|
||||
|
||||
export function eventSource(request, init) {
|
||||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'text/event-stream');
|
||||
return streamResponse(request, headers, (send, close) => {
|
||||
const encoder = new TextEncoder();
|
||||
function sendEvent(data, event = 'message') {
|
||||
send(encoder.encode(`event: ${event}\n`));
|
||||
send(encoder.encode(`data: ${data}\n\n`));
|
||||
}
|
||||
return init(sendEvent, close);
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Ticker from '@/util/ticker.js';
|
||||
import Ticker from '@/lib/ticker.js';
|
||||
|
||||
const Modulators = {
|
||||
flat: () => 0.5,
|
7
app/lib/long-poll.js
Normal file
7
app/lib/long-poll.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import streamResponse from './stream-response';
|
||||
|
||||
export function longPoll(request, init) {
|
||||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'text/html');
|
||||
return streamResponse(request, headers, init);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// import {Texture} from '@pixi/core';
|
||||
// import {Sprite} from '@pixi/sprite';
|
||||
|
||||
import {bresenham, createNoise2D, TAU} from '@/util/math.js';
|
||||
import {bresenham, createNoise2D, TAU} from '@/lib/math.js';
|
||||
|
||||
const simplex2D = createNoise2D();
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import K from 'kefir';
|
||||
|
||||
import * as easings from '@/util/easing.js';
|
||||
import {TAU} from '@/util/math.js';
|
||||
import * as easings from '@/lib/easing.js';
|
||||
import {TAU} from '@/lib/math.js';
|
||||
|
||||
export default class Emitter {
|
||||
export class Emitter {
|
||||
constructor(ecs) {
|
||||
this.ecs = ecs;
|
||||
this.scheduled = [];
|
|
@ -1,9 +1,9 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import Components from '@/ecs/components/index.js';
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import Components from '@/silphius/ecs/components/index.js';
|
||||
import Ecs from '@/silphius/ecs/ecs.js';
|
||||
|
||||
import Emitter from './emitter.js';
|
||||
import {Emitter} from './particles.js';
|
||||
|
||||
test('emits particles at once', async () => {
|
||||
const ecs = new Ecs({
|
|
@ -1,3 +1,5 @@
|
|||
import Script from '@/lib/script.js';
|
||||
|
||||
export async function computeMissing(current, manifest) {
|
||||
const missing = [];
|
||||
for (const path in manifest) {
|
||||
|
@ -30,7 +32,7 @@ export async function fetchMissingResources(manifest, signal) {
|
|||
}
|
||||
}
|
||||
}
|
||||
loadResources(current);
|
||||
await loadResources(current);
|
||||
}
|
||||
|
||||
export async function fetchResources(paths, {signal} = {}) {
|
||||
|
@ -53,7 +55,8 @@ export async function get() {
|
|||
|
||||
const cache = new Map();
|
||||
|
||||
export function loadResources(resources) {
|
||||
export async function loadResources(resources) {
|
||||
await Script.registerScriptsModule();
|
||||
for (const path in resources) {
|
||||
cache.set(path, resources[path].asset);
|
||||
}
|
|
@ -25,7 +25,7 @@ async function computeAsset(path) {
|
|||
}
|
||||
|
||||
async function assetPaths() {
|
||||
return glob(RESOURCES_GLOB, {nodir: true});
|
||||
return glob(RESOURCES_GLOB, {ignore: '**/*.js', nodir: true});
|
||||
}
|
||||
|
||||
export async function createManifest() {
|
||||
|
@ -48,7 +48,7 @@ export async function loadManifest() {
|
|||
for (const path in created) {
|
||||
manifest[path] = created[path];
|
||||
}
|
||||
const watcher = chokidar.watch(RESOURCES_GLOB);
|
||||
const watcher = chokidar.watch(RESOURCES_GLOB, {ignored: '**/*.js'});
|
||||
watcher.on('add', async (path) => {
|
||||
const asset = await computeAsset(path);
|
||||
manifest[asset.path] = asset.hash.toString('hex');
|
|
@ -1,48 +1,41 @@
|
|||
import Ticker from '@/util/ticker.js';
|
||||
import Ticker from '@/lib/ticker.js';
|
||||
|
||||
export default class Script {
|
||||
|
||||
static registered = {};
|
||||
|
||||
constructor(fn, locals) {
|
||||
constructor(path, fn, locals) {
|
||||
if (!fn) {
|
||||
throw new TypeError('Script needs a function');
|
||||
}
|
||||
this.fn = fn;
|
||||
this.iterator = null;
|
||||
this.locals = locals;
|
||||
this.path = path;
|
||||
this.$$ticker = null;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new this.constructor(this.fn, this.locals);
|
||||
return new this.constructor(this.path, this.fn, this.locals);
|
||||
}
|
||||
|
||||
evaluate() {
|
||||
return this.fn(this.locals).next().value;
|
||||
}
|
||||
|
||||
static load(pathOrFunction, locals) {
|
||||
let fn;
|
||||
if (this.registered[pathOrFunction]) {
|
||||
fn = this.registered[pathOrFunction];
|
||||
}
|
||||
else if (pathOrFunction) {
|
||||
try {
|
||||
fn = eval(pathOrFunction);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Couldn't eval script", pathOrFunction);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
static load(path, locals) {
|
||||
const fn = this.registered[path];
|
||||
if (!fn) {
|
||||
return undefined;
|
||||
throw new Error(`no such script: ${path}`);
|
||||
}
|
||||
const script = new this(fn, locals);
|
||||
const script = new this(path, fn, locals);
|
||||
if (import.meta.hot) {
|
||||
const hotRef = new WeakRef(script);
|
||||
import.meta.hot.accept('./scripts.js', ({default: scripts}) => {
|
||||
script.fn = scripts[`../..${pathOrFunction}`];
|
||||
const ref = hotRef.deref();
|
||||
if (ref) {
|
||||
ref.fn = scripts[path];
|
||||
}
|
||||
});
|
||||
}
|
||||
return script;
|
||||
|
@ -52,6 +45,13 @@ export default class Script {
|
|||
this.registered[path] = fn;
|
||||
}
|
||||
|
||||
static async registerScriptsModule() {
|
||||
const {default: scripts} = await import('./scripts.js');
|
||||
for (const path in scripts) {
|
||||
Script.register(path, scripts[path]);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.iterator = null;
|
||||
this.$$ticker = null;
|
||||
|
@ -107,16 +107,8 @@ export default class Script {
|
|||
|
||||
}
|
||||
|
||||
function register({default: scripts}) {
|
||||
for (const path in scripts) {
|
||||
Script.register(path.slice('../..'.length), scripts[path]);
|
||||
}
|
||||
}
|
||||
|
||||
register(await import('./scripts.js'));
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('./scripts.js', (M) => {
|
||||
register(M);
|
||||
import.meta.hot.accept('./scripts.js', () => {
|
||||
Script.registerScriptsModule();
|
||||
});
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
export default import.meta.glob(
|
||||
'../../resources/**/*.js',
|
||||
'%/**/*.js',
|
||||
{eager: true, import: 'default'},
|
||||
);
|
||||
|
|
@ -6,4 +6,8 @@ export function singleton(key, value) {
|
|||
|
||||
singleton.reset = function (key) {
|
||||
delete global.__singletons[key];
|
||||
}
|
||||
};
|
||||
|
||||
singleton.set = function (key, value) {
|
||||
global.__singletons[key] = value;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import {clamp, intersects} from '@/util/math.js';
|
||||
import {clamp, intersects} from '@/lib/math.js';
|
||||
|
||||
export default class SpatialHash {
|
||||
|
30
app/lib/stream-response.js
Normal file
30
app/lib/stream-response.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
export default function streamResponse(request, headers, init) {
|
||||
headers.set('Connection', 'keep-alive');
|
||||
headers.set('Cache-Control', 'no-store, no-transform');
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
function send(chunk) {
|
||||
controller.enqueue(chunk);
|
||||
}
|
||||
const cleanup = init(send, close);
|
||||
let closed = false;
|
||||
function close() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
closed = true;
|
||||
request.signal.removeEventListener('abort', close);
|
||||
controller.close();
|
||||
}
|
||||
request.signal.addEventListener('abort', close);
|
||||
if (request.signal.aborted) {
|
||||
close();
|
||||
}
|
||||
},
|
||||
});
|
||||
return new Response(stream, {
|
||||
headers,
|
||||
status: 200,
|
||||
});
|
||||
}
|
33
app/lib/top-level-long-poll.js
Normal file
33
app/lib/top-level-long-poll.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {Buffer} from 'node:buffer';
|
||||
import {Transform} from 'node:stream';
|
||||
|
||||
const TERMINATION = Buffer.from('</body></html>');
|
||||
|
||||
export class TopLevelLongPoll extends Transform {
|
||||
|
||||
constructor(pump) {
|
||||
super();
|
||||
this.pump = pump;
|
||||
}
|
||||
|
||||
async _flush(done) {
|
||||
await this.pump((chunk) => {
|
||||
this.push(chunk);
|
||||
});
|
||||
this.push(TERMINATION);
|
||||
done();
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, done) {
|
||||
if (0 === Buffer.compare(TERMINATION, chunk.slice(-TERMINATION.length))) {
|
||||
this.push(chunk.slice(0, -TERMINATION.length));
|
||||
done();
|
||||
}
|
||||
else {
|
||||
this.push(chunk);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import Ticker from '@/util/ticker.js';
|
||||
import Ticker from '@/lib/ticker.js';
|
||||
|
||||
import * as Easing from './easing';
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import {CLIENT_LATENCY, CLIENT_PREDICTION} from '@/util/constants.js';
|
||||
import EventEmitter from '@/util/event-emitter.js';
|
||||
|
||||
export default class Client {
|
||||
emitter = new EventEmitter();
|
||||
rtt = 0;
|
||||
throughput = {$$down: 0, down: 0, $$up: 0, up: 0};
|
||||
constructor() {
|
||||
setInterval(() => {
|
||||
const {throughput} = this;
|
||||
throughput.down = throughput.$$down * 4;
|
||||
throughput.up = throughput.$$up * 4;
|
||||
throughput.$$down = throughput.$$up = 0;
|
||||
}, 250);
|
||||
}
|
||||
accept(packet) {
|
||||
if ('Heartbeat' === packet.type) {
|
||||
this.rtt = packet.payload.rtt;
|
||||
this.send(packet);
|
||||
return;
|
||||
}
|
||||
this.emitter.invoke(packet.type, packet.payload);
|
||||
}
|
||||
addPacketListener(type, listener) {
|
||||
this.emitter.addListener(type, listener);
|
||||
}
|
||||
removePacketListener(type, listener) {
|
||||
this.emitter.removeListener(type, listener);
|
||||
}
|
||||
send(packet) {
|
||||
if (CLIENT_LATENCY > 0 && !CLIENT_PREDICTION) {
|
||||
setTimeout(() => {
|
||||
this.transmit(packet);
|
||||
}, CLIENT_LATENCY);
|
||||
}
|
||||
else {
|
||||
this.transmit(packet);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import Packet from '@/net/packet.js';
|
||||
|
||||
export default class Tick extends Packet {}
|
|
@ -1,5 +1,5 @@
|
|||
import Ecs from '@/ecs/ecs.js';
|
||||
import {readAsset} from '@/util/resources.js';
|
||||
import Ecs from '@/silphius/ecs/ecs.js';
|
||||
import {readAsset} from '@/lib/resources.js';
|
||||
|
||||
export default class ClientEcs extends Ecs {
|
||||
constructor(specification) {
|
||||
|
|
|
@ -204,7 +204,7 @@ export default function Tiles({eventsChannel}) {
|
|||
<img
|
||||
alt="tileset"
|
||||
ref={imageRef}
|
||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||
src={TileLayers.layer(0).source.replace('.sprite.json', '.png')}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {memo, useEffect, useState} from 'react';
|
||||
|
||||
import {useRadians} from '@/react/context/radians.js';
|
||||
import {render} from '@/util/dialogue.js';
|
||||
import {render} from '@/lib/dialogue.js';
|
||||
|
||||
import styles from './message.module.css';
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {memo, useCallback, useRef} from 'react';
|
||||
|
||||
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
||||
import {DamageTypes} from '@/silphius/ecs/components/vulnerable.js';
|
||||
import {useEcsTick} from '@/react/context/ecs.js';
|
||||
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
||||
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
|
||||
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/lib/easing.js';
|
||||
|
||||
import styles from './damages.module.css';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
|
||||
import styles from './dialogue-caret.module.css';
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|||
import {useDomScale} from '@/react/context/dom-scale.js';
|
||||
import {useRadians} from '@/react/context/radians.js';
|
||||
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
||||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import {render} from '@/util/dialogue.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
import {render} from '@/lib/dialogue.js';
|
||||
|
||||
import DialogueCaret from './dialogue-caret.jsx';
|
||||
import styles from './dialogue.module.css';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {useEffect, useRef, useState} from 'react';
|
||||
|
||||
import DomContext from '@/react/context/dom-scale.js';
|
||||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
|
||||
import styles from './dom.module.css';
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import {useCallback, useState} from 'react';
|
|||
|
||||
import {usePacket} from '@/react/context/client.js';
|
||||
import {useEcsTick} from '@/react/context/ecs.js';
|
||||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import {parseLetters} from '@/util/dialogue.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
import {parseLetters} from '@/lib/dialogue.js';
|
||||
|
||||
import Damages from './damages.jsx';
|
||||
import Entity from './entity.jsx';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Emitter from '@/particles/emitter.js';
|
||||
import createEcs from '@/server/create/ecs.js';
|
||||
import {Emitter} from '@/lib/particles.js';
|
||||
import createEcs from '@/silphius/server/create/ecs.js';
|
||||
|
||||
import ClientEcs from './client-ecs.js';
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ 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 {RESOLUTION} from '@/util/constants.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
|
||||
import Ecs from './ecs.jsx';
|
||||
import {ApplicationStageLayers, ApplicationStageLights} from './extensions.js';
|
||||
|
|
|
@ -7,7 +7,7 @@ import '@pixi/spritesheet'; // NECESSARY!
|
|||
import {CompositeTilemap} from '@pixi/tilemap';
|
||||
|
||||
import {useAsset} from '@/react/context/assets.js';
|
||||
import {CHUNK_SIZE, RESOLUTION} from '@/util/constants.js';
|
||||
import {CHUNK_SIZE, RESOLUTION} from '@/lib/constants.js';
|
||||
|
||||
import {deferredLighting} from './lights.js';
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import {useClient, usePacket} from '@/react/context/client.js';
|
|||
import {useDebug} from '@/react/context/debug.js';
|
||||
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import EventEmitter from '@/util/event-emitter.js';
|
||||
import {distribute} from '@/util/inventory.js';
|
||||
import {RESOLUTION} from '@/lib/constants.js';
|
||||
import EventEmitter from '@/lib/event-emitter.js';
|
||||
import {distribute} from '@/lib/inventory.js';
|
||||
|
||||
import addKeyListener from './add-key-listener.js';
|
||||
import Disconnected from './dom/disconnected.jsx';
|
||||
|
@ -233,20 +233,6 @@ function Ui({disconnected}) {
|
|||
mainEntityRef,
|
||||
]);
|
||||
usePacket('EcsChange', onEcsChangePacket);
|
||||
const onTickPacket = useCallback((payload, client) => {
|
||||
if (!ecsRef.current || 0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ecsRef.current.apply(payload.ecs);
|
||||
client.emitter.invoke(':Ecs', payload.ecs);
|
||||
}
|
||||
catch (error) {
|
||||
ecsRef.current = undefined;
|
||||
console.error('tick crash', error);
|
||||
}
|
||||
}, [ecsRef]);
|
||||
usePacket('Tick', onTickPacket);
|
||||
const onEcsTick = useCallback((payload, ecs) => {
|
||||
for (const id in payload) {
|
||||
const entity = ecs.get(id);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {useCallback, useState} from 'react';
|
||||
|
||||
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
||||
import {TAU} from '@/util/math.js';
|
||||
import {TAU} from '@/lib/math.js';
|
||||
|
||||
export function useRadians() {
|
||||
const [radians, setRadians] = useState(0);
|
||||
|
|
29
app/react/hooks/use-web-socket.js
Normal file
29
app/react/hooks/use-web-socket.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import {useEffect, useState} from 'react';
|
||||
|
||||
export function useWebSocket(path, {host, schema} = {}) {
|
||||
const [socket, setSocket] = useState();
|
||||
useEffect(() => {
|
||||
async function connect() {
|
||||
const socket = new WebSocket(`${schema || ''}//${host || location.host}${path || '/'}`);
|
||||
socket.addEventListener('open', () => {
|
||||
setSocket(socket);
|
||||
});
|
||||
socket.addEventListener('close', ({reason}) => {
|
||||
if ('hmr' === reason) {
|
||||
connect();
|
||||
}
|
||||
setSocket(undefined);
|
||||
});
|
||||
}
|
||||
connect();
|
||||
return () => {
|
||||
setSocket((socket) => {
|
||||
if (socket) {
|
||||
socket.close();
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}, [path, host, schema]);
|
||||
return socket;
|
||||
}
|
|
@ -4,11 +4,11 @@ import {
|
|||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from "@remix-run/react";
|
||||
} from '@remix-run/react';
|
||||
|
||||
import './root.css';
|
||||
|
||||
export function Layout({ children }) {
|
||||
export function Layout({children}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
@ -18,12 +18,13 @@ export default function PlaySpecific() {
|
|||
const debugTuple = useState(false);
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
const reconnectionBackoff = useRef(0);
|
||||
const ecsRef = useRef();
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
useEffect(() => {
|
||||
if (!Client) {
|
||||
if (!Client || !Components || !Systems) {
|
||||
return;
|
||||
}
|
||||
const client = new Client();
|
||||
|
@ -35,7 +36,7 @@ export default function PlaySpecific() {
|
|||
return () => {
|
||||
client.disconnect();
|
||||
};
|
||||
}, [Client, url]);
|
||||
}, [Client, Components, Systems, url]);
|
||||
// Sneakily use beforeunload to snag some time to save.
|
||||
useEffect(() => {
|
||||
if ('local' !== type) {
|
||||
|
@ -62,8 +63,8 @@ export default function PlaySpecific() {
|
|||
}, [ecsRef, Components, Systems]);
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs/components/index.js');
|
||||
const {default: Systems} = await import('@/ecs/systems/index.js');
|
||||
const {default: Components} = await import('@/silphius/ecs/components/index.js');
|
||||
const {default: Systems} = await import('@/silphius/ecs/systems/index.js');
|
||||
setComponents(Components);
|
||||
setSystems(Systems);
|
||||
}
|
||||
|
@ -84,19 +85,52 @@ export default function PlaySpecific() {
|
|||
client.removePacketListener('EcsChange', onEcsChangePacket);
|
||||
};
|
||||
}, [client, refreshEcs]);
|
||||
const onTickPacket = useCallback((payload) => {
|
||||
if (!ecsRef.current || 0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
}
|
||||
reconnectionBackoff.current = 0;
|
||||
setDisconnected(false);
|
||||
try {
|
||||
ecsRef.current.apply(payload.ecs);
|
||||
client.emitter.invoke(':Ecs', payload.ecs);
|
||||
}
|
||||
catch (error) {
|
||||
ecsRef.current = undefined;
|
||||
console.error('tick crash', error);
|
||||
}
|
||||
}, [client, ecsRef]);
|
||||
useEffect(() => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
client.addPacketListener('Tick', onTickPacket);
|
||||
return () => {
|
||||
client.removePacketListener('Tick', onTickPacket);
|
||||
};
|
||||
}, [client, onTickPacket]);
|
||||
useEffect(() => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
let handle;
|
||||
function onConnectionStatus(status) {
|
||||
switch (status) {
|
||||
case 'aborted': {
|
||||
client.disconnect();
|
||||
setDisconnected(true);
|
||||
break;
|
||||
}
|
||||
case 'connected': {
|
||||
setDisconnected(false);
|
||||
if (!handle) {
|
||||
client.disconnect();
|
||||
setDisconnected(true);
|
||||
reconnectionBackoff.current = (
|
||||
Math.max(100, Math.min(1000, reconnectionBackoff.current * 2))
|
||||
);
|
||||
handle = setTimeout(
|
||||
() => {
|
||||
client.connect(url);
|
||||
handle = null;
|
||||
},
|
||||
reconnectionBackoff.current,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -104,8 +138,9 @@ export default function PlaySpecific() {
|
|||
client.addPacketListener('ConnectionStatus', onConnectionStatus);
|
||||
return () => {
|
||||
client.removePacketListener('ConnectionStatus', onConnectionStatus);
|
||||
clearTimeout(handle);
|
||||
};
|
||||
}, [client]);
|
||||
}, [client, url]);
|
||||
useEffect(() => {
|
||||
if (!client) {
|
||||
return;
|
||||
|
@ -125,15 +160,6 @@ export default function PlaySpecific() {
|
|||
client.removePacketListener('Download', onDownload);
|
||||
};
|
||||
}, [client]);
|
||||
useEffect(() => {
|
||||
if (!client || !disconnected) {
|
||||
return;
|
||||
}
|
||||
async function reconnect() {
|
||||
await client.connect(url);
|
||||
}
|
||||
reconnect();
|
||||
}, [client, disconnected, mainEntityRef, url]);
|
||||
// useEffect(() => {
|
||||
// let source = true;
|
||||
// async function play() {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {json, useLoaderData} from "@remix-run/react";
|
|||
import {useEffect, useState} from 'react';
|
||||
import {Outlet, useParams} from 'react-router-dom';
|
||||
|
||||
import {fetchMissingResources, readAsset} from '@/util/resources.js';
|
||||
import {fetchMissingResources, readAsset} from '@/lib/resources.js';
|
||||
|
||||
import styles from './play.module.css';
|
||||
|
||||
|
@ -13,8 +13,8 @@ settings.ADAPTER.fetch = async (path) => {
|
|||
};
|
||||
|
||||
export async function loader({request}) {
|
||||
const {juggleSession} = await import('@/server/session.server.js');
|
||||
const {loadManifest} = await import('@/util/resources.server.js');
|
||||
const {juggleSession} = await import('@/silphius/server/session.server.js');
|
||||
const {loadManifest} = await import('@/lib/resources.server.js');
|
||||
await juggleSession(request);
|
||||
return json({
|
||||
manifest: await loadManifest(),
|
||||
|
@ -43,10 +43,10 @@ export default function Play() {
|
|||
let Client;
|
||||
switch (type) {
|
||||
case 'local':
|
||||
({default: Client} = await import('@/client/local.js'));
|
||||
({default: Client} = await import('@/silphius/client/local.js'));
|
||||
break;
|
||||
case 'remote':
|
||||
({default: Client} = await import('@/client/remote.js'));
|
||||
({default: Client} = await import('@/silphius/client/remote.js'));
|
||||
break;
|
||||
}
|
||||
setClient(() => Client);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import {Form, json, redirect, useLoaderData} from '@remix-run/react';
|
||||
import {useState} from 'react';
|
||||
|
||||
import {commitSession, getSession, juggleSession} from '@/server/session.server.js';
|
||||
import {commitSession, getSession, juggleSession} from '@/silphius/server/session.server.js';
|
||||
|
||||
import NoiseField from './noise-field.jsx';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {useState} from 'react';
|
|||
import SliderText from '@/react/components/dev/slider-text.jsx';
|
||||
import TileLayer from '@/react/components/pixi/tile-layer.jsx';
|
||||
import AssetsContext from '@/react/context/assets.js';
|
||||
import {CHUNK_SIZE} from '@/util/constants.js';
|
||||
import {CHUNK_SIZE} from '@/lib/constants.js';
|
||||
|
||||
import alea from 'alea';
|
||||
import {createNoise2D} from 'simplex-noise';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {encodeResources, loadResources} from '@/util/resources.server.js';
|
||||
import {encodeResources, loadResources} from '@/lib/resources.server.js';
|
||||
|
||||
export async function action({request}) {
|
||||
const paths = await request.json();
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import {mkdir, readFile, unlink, writeFile} from 'node:fs/promises';
|
||||
import {dirname, join} from 'node:path';
|
||||
|
||||
import {WebSocketServer} from 'ws';
|
||||
|
||||
import Server from '@/net/server.js';
|
||||
import {getSession} from '@/server/session.server.js';
|
||||
import {loadResources, readAsset} from '@/util/resources.js';
|
||||
import {loadResources as loadServerResources} from '@/util/resources.server.js';
|
||||
|
||||
import Engine from './engine.js';
|
||||
|
||||
global.__silphiusWebsocket = null;
|
||||
|
||||
class SocketServer extends Server {
|
||||
async ensurePath(path) {
|
||||
await mkdir(path, {recursive: true});
|
||||
}
|
||||
async load() {
|
||||
await loadResources(await loadServerResources());
|
||||
}
|
||||
static qualify(path) {
|
||||
return join(import.meta.dirname, '..', '..', 'data', 'remote', 'UNIVERSE', path);
|
||||
}
|
||||
readAsset(path) {
|
||||
return readAsset(path) ?? new ArrayBuffer(0);
|
||||
}
|
||||
async readData(path) {
|
||||
const qualified = this.constructor.qualify(path);
|
||||
await this.ensurePath(dirname(qualified));
|
||||
return readFile(qualified);
|
||||
}
|
||||
async removeData(path) {
|
||||
await unlink(path);
|
||||
}
|
||||
async writeData(path, view) {
|
||||
const qualified = this.constructor.qualify(path);
|
||||
await this.ensurePath(dirname(qualified));
|
||||
await writeFile(qualified, view);
|
||||
}
|
||||
transmit(ws, packed) { ws.send(packed); }
|
||||
}
|
||||
|
||||
export async function handleUpgrade(request, socket, head) {
|
||||
if (!global.__silphiusWebsocket) {
|
||||
const engine = new Engine(SocketServer);
|
||||
await engine.load();
|
||||
engine.start();
|
||||
const handleConnection = async (ws, request) => {
|
||||
ws.on('close', async () => {
|
||||
await engine.disconnectPlayer(ws);
|
||||
})
|
||||
ws.on('message', (packed) => {
|
||||
engine.server.accept(ws, new DataView(packed.buffer, packed.byteOffset, packed.length));
|
||||
});
|
||||
const session = await getSession(request.headers['cookie']);
|
||||
await engine.connectPlayer(ws, session.get('id'));
|
||||
};
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
});
|
||||
wss.on('connection', handleConnection);
|
||||
global.__silphiusWebsocket = {engine, handleConnection, wss};
|
||||
}
|
||||
const {pathname} = new URL(request.url, 'wss://base.url');
|
||||
if (pathname === '/ws') {
|
||||
const {wss} = global.__silphiusWebsocket;
|
||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
}
|
||||
else {
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on('vite:beforeUpdate', async () => {
|
||||
if (global.__silphiusWebsocket) {
|
||||
const {engine, handleConnection, wss} = global.__silphiusWebsocket;
|
||||
wss.off('connection', handleConnection);
|
||||
const connections = [];
|
||||
for (const [connection] of engine.connectedPlayers) {
|
||||
engine.server.send(connection, {type: 'EcsChange'});
|
||||
connections.push(connection);
|
||||
}
|
||||
await engine.stop();
|
||||
for (const connection of connections) {
|
||||
connection.close();
|
||||
}
|
||||
global.__silphiusWebsocket = null;
|
||||
}
|
||||
});
|
||||
import.meta.hot.accept();
|
||||
}
|
17
app/silphius/build.js
Normal file
17
app/silphius/build.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
export async function implement({hooks}) {
|
||||
hooks.tap('sylvite:viteConfig', (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: [
|
||||
...config.resolve.alias,
|
||||
{
|
||||
find: '%',
|
||||
replacement: fileURLToPath(new URL('../../resources', import.meta.url))
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
}
|
27
app/silphius/client/local.js
Normal file
27
app/silphius/client/local.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Client from '@/silphius/net/client.js';
|
||||
|
||||
export default class LocalClient extends Client {
|
||||
server = null;
|
||||
async connect() {
|
||||
await super.connect();
|
||||
this.server = new Worker(
|
||||
new URL('../server/worker.js', import.meta.url),
|
||||
{type: 'module'},
|
||||
);
|
||||
this.server.addEventListener('message', (event) => {
|
||||
if (0 === event.data) {
|
||||
this.server.terminate();
|
||||
this.server = null;
|
||||
return;
|
||||
}
|
||||
this.receive(event.data);
|
||||
});
|
||||
}
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
this.server.postMessage(0);
|
||||
}
|
||||
transmit(packed) {
|
||||
this.server.postMessage(packed);
|
||||
}
|
||||
}
|
25
app/silphius/client/remote.js
Normal file
25
app/silphius/client/remote.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Client from '@/silphius/net/client.js';
|
||||
|
||||
export default class RemoteClient extends Client {
|
||||
socket = null;
|
||||
async connect(host) {
|
||||
await super.connect();
|
||||
this.socket = new WebSocket(`//${host}/silphius`);
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
this.socket.addEventListener('message', (event) => {
|
||||
this.receive(event.data);
|
||||
});
|
||||
this.socket.addEventListener('close', () => {
|
||||
this.accept({type: 'ConnectionStatus', payload: 'aborted'});
|
||||
});
|
||||
this.socket.addEventListener('error', () => {
|
||||
this.accept({type: 'ConnectionStatus', payload: 'aborted'});
|
||||
});
|
||||
this.socket.addEventListener('open', () => {
|
||||
this.accept({type: 'ConnectionStatus', payload: 'connected'});
|
||||
});
|
||||
}
|
||||
transmit(packed) {
|
||||
this.socket.send(packed);
|
||||
}
|
||||
}
|
|
@ -134,11 +134,22 @@ export default class Component {
|
|||
$$entity = 0;
|
||||
destroy() {}
|
||||
initialize(values, defaults) {
|
||||
const {properties} = concrete;
|
||||
for (const key in values) {
|
||||
this[`$$${key}`] = values[key];
|
||||
if (properties[key]?.$.set) {
|
||||
properties[key].$.set(Component, this, `$$${key}`, values[key]);
|
||||
}
|
||||
else {
|
||||
this[`$$${key}`] = values[key];
|
||||
}
|
||||
}
|
||||
for (const key in defaults) {
|
||||
this[`$$${key}`] = defaults[key];
|
||||
if (properties[key]?.$.set) {
|
||||
properties[key].$.set(Component, this, `$$${key}`, defaults[key]);
|
||||
}
|
||||
else {
|
||||
this[`$$${key}`] = defaults[key];
|
||||
}
|
||||
}
|
||||
Component.ecs.markChange(this.entity, {[Component.constructor.componentName]: values})
|
||||
}
|
||||
|
@ -146,7 +157,12 @@ export default class Component {
|
|||
const {properties} = concrete;
|
||||
const json = {};
|
||||
for (const key in properties) {
|
||||
json[key] = this[key];
|
||||
if (properties[key]?.$.json) {
|
||||
json[key] = properties[key].$.json(this[key]);
|
||||
}
|
||||
else {
|
||||
json[key] = this[key];
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
@ -160,9 +176,15 @@ export default class Component {
|
|||
return this.toFullJSON();
|
||||
}
|
||||
update(values) {
|
||||
const {properties} = concrete;
|
||||
for (const key in values) {
|
||||
if (concrete.properties[key]) {
|
||||
this[`$$${key}`] = values[key];
|
||||
if (properties[key]) {
|
||||
if (properties[key]?.$.set) {
|
||||
properties[key].$.set(Component, this, `$$${key}`, values[key]);
|
||||
}
|
||||
else {
|
||||
this[`$$${key}`] = values[key];
|
||||
}
|
||||
}
|
||||
else {
|
||||
this[key] = values[key];
|
||||
|
@ -186,7 +208,12 @@ export default class Component {
|
|||
},
|
||||
set: function set(value) {
|
||||
if (this[`$$${key}`] !== value) {
|
||||
this[`$$${key}`] = value;
|
||||
if (concrete.properties[key]?.$.set) {
|
||||
concrete.properties[key].$.set(Component, this, `$$${key}`, value);
|
||||
}
|
||||
else {
|
||||
this[`$$${key}`] = value;
|
||||
}
|
||||
Component.markChange(this.entity, key, value);
|
||||
}
|
||||
},
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Alive extends Component {
|
||||
instanceFromSchema() {
|
||||
|
@ -19,8 +19,8 @@ export default class Alive extends Component {
|
|||
this.$$dead = true;
|
||||
const {Ticking} = ecs.get(this.entity);
|
||||
if (Ticking) {
|
||||
this.$$death.locals.entity = ecs.get(this.entity);
|
||||
const ticker = this.$$death.ticker();
|
||||
this.deathScript.locals.entity = ecs.get(this.entity);
|
||||
const ticker = this.deathScript.ticker();
|
||||
ecs.addDestructionDependency(this.entity.id, Ticking.add(ticker));
|
||||
}
|
||||
}
|
||||
|
@ -30,21 +30,11 @@ export default class Alive extends Component {
|
|||
if (0 === instance.maxHealth) {
|
||||
instance.maxHealth = instance.health;
|
||||
}
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.$$death = this.ecs.readScript(
|
||||
instance.deathScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
},
|
||||
);
|
||||
}
|
||||
static properties = {
|
||||
deathScript: {
|
||||
defaultValue: '/resources/misc/death-default.js',
|
||||
type: 'string',
|
||||
type: 'script',
|
||||
},
|
||||
health: {type: 'uint32'},
|
||||
maxHealth: {type: 'uint32'},
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Animation extends Component {
|
||||
static properties = {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Arbitrary extends Component {
|
||||
instanceFromSchema() {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class AreaSize extends Component {
|
||||
static properties = {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Behaving extends Component {
|
||||
instanceFromSchema() {
|
||||
|
@ -16,10 +16,6 @@ export default class Behaving extends Component {
|
|||
};
|
||||
}
|
||||
load(instance) {
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
for (const key in instance.routines) {
|
||||
instance.$$routineInstances[key] = this.ecs.readScript(instance.routines[key]);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Camera extends Component {
|
||||
static properties = {
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import {distance, intersects, transform} from '@/util/math.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
import {distance, intersects, transform} from '@/lib/math.js';
|
||||
|
||||
import vector2d from './helpers/vector-2d';
|
||||
|
||||
|
@ -9,8 +9,6 @@ export default class Collider extends Component {
|
|||
return class ColliderInstance extends super.instanceFromSchema() {
|
||||
$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
||||
$$aabbs = [];
|
||||
$$collisionStart;
|
||||
$$collisionEnd;
|
||||
$$intersections = new Map();
|
||||
get aabb() {
|
||||
const {Position: {x: px, y: py}} = ecs.get(this.entity);
|
||||
|
@ -77,8 +75,8 @@ export default class Collider extends Component {
|
|||
}
|
||||
}
|
||||
if (!hasMatchingIntersection) {
|
||||
if (this.$$collisionStart) {
|
||||
const script = this.$$collisionStart.clone();
|
||||
if (this.collisionStartScript) {
|
||||
const script = this.collisionStartScript.clone();
|
||||
script.locals.entity = thisEntity;
|
||||
script.locals.other = otherEntity;
|
||||
script.locals.pair = [body, otherBody];
|
||||
|
@ -87,8 +85,8 @@ export default class Collider extends Component {
|
|||
ecs.addDestructionDependency(otherEntity.id, promise);
|
||||
ecs.addDestructionDependency(thisEntity.id, promise);
|
||||
}
|
||||
if (other.$$collisionStart) {
|
||||
const script = other.$$collisionStart.clone();
|
||||
if (other.collisionStartScript) {
|
||||
const script = other.collisionStartScript.clone();
|
||||
script.locals.entity = otherEntity;
|
||||
script.locals.other = thisEntity;
|
||||
script.locals.pair = [otherBody, body];
|
||||
|
@ -162,8 +160,8 @@ export default class Collider extends Component {
|
|||
intersection.entity.bodies[intersection.i],
|
||||
intersection.other.bodies[intersection.j],
|
||||
];
|
||||
if (this.$$collisionEnd) {
|
||||
const script = this.$$collisionEnd.clone();
|
||||
if (this.collisionEndScript) {
|
||||
const script = this.collisionEndScript.clone();
|
||||
script.locals.other = otherEntity;
|
||||
script.locals.pair = [body, otherBody];
|
||||
const ticker = script.ticker();
|
||||
|
@ -171,8 +169,8 @@ export default class Collider extends Component {
|
|||
ecs.addDestructionDependency(thisEntity.id, promise);
|
||||
ecs.addDestructionDependency(otherEntity.id, promise);
|
||||
}
|
||||
if (other.$$collisionEnd) {
|
||||
const script = other.$$collisionEnd.clone();
|
||||
if (other.collisionEndScript) {
|
||||
const script = other.collisionEndScript.clone();
|
||||
script.locals.other = thisEntity;
|
||||
script.locals.pair = [otherBody, body];
|
||||
const ticker = script.ticker();
|
||||
|
@ -268,22 +266,6 @@ export default class Collider extends Component {
|
|||
};
|
||||
}
|
||||
instance.updateAabbs();
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.$$collisionEnd = this.ecs.readScript(
|
||||
instance.collisionEndScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
},
|
||||
);
|
||||
instance.$$collisionStart = this.ecs.readScript(
|
||||
instance.collisionStartScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
},
|
||||
);
|
||||
}
|
||||
static properties = {
|
||||
bodies: {
|
||||
|
@ -307,8 +289,8 @@ export default class Collider extends Component {
|
|||
},
|
||||
},
|
||||
},
|
||||
collisionEndScript: {type: 'string'},
|
||||
collisionStartScript: {type: 'string'},
|
||||
collisionEndScript: {type: 'script'},
|
||||
collisionStartScript: {type: 'script'},
|
||||
isColliding: {defaultValue: 1, type: 'uint8'},
|
||||
};
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Controlled extends Component {
|
||||
instanceFromSchema() {
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import {HALF_PI, TAU} from '@/util/math.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
import {HALF_PI, TAU} from '@/lib/math.js';
|
||||
|
||||
export default class Direction extends Component {
|
||||
instanceFromSchema() {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Ecs extends Component {
|
||||
static properties = {
|
|
@ -1,7 +1,7 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
import Emitter from '@/particles/emitter.js';
|
||||
import Ticker from '@/util/ticker.js';
|
||||
import {Emitter} from '@/lib/particles.js';
|
||||
import Ticker from '@/lib/ticker.js';
|
||||
|
||||
export default class EmitterComponent extends Component {
|
||||
instanceFromSchema() {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Forces extends Component {
|
||||
instanceFromSchema() {
|
3
app/silphius/ecs/components/grabber.js
Normal file
3
app/silphius/ecs/components/grabber.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Grabber extends Component {}
|
21
app/silphius/ecs/components/harmful.js
Normal file
21
app/silphius/ecs/components/harmful.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Harmful extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
return class HarmfulInstance extends super.instanceFromSchema() {
|
||||
harm(other) {
|
||||
const entity = ecs.get(this.entity);
|
||||
const script = this.harmScript.clone();
|
||||
script.locals.other = other;
|
||||
script.locals.entity = entity;
|
||||
const promise = entity.Ticking.add(script.ticker());
|
||||
ecs.addDestructionDependency(entity.id, promise);
|
||||
ecs.addDestructionDependency(other.id, promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
static properties = {
|
||||
harmScript: {type: 'script'},
|
||||
};
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Health extends Component {
|
||||
static properties = {
|
|
@ -1,4 +1,4 @@
|
|||
import gather from '@/util/gather.js';
|
||||
import gather from '@/lib/gather.js';
|
||||
|
||||
const Gathered = gather(
|
||||
import.meta.glob(['./*.js', '!./*.test.js'], {eager: true, import: 'default'}),
|
||||
|
@ -13,7 +13,7 @@ if (import.meta.env.PROD) {
|
|||
);
|
||||
}
|
||||
else {
|
||||
const {default: ieval} = await import('@/util/eval.js');
|
||||
const {default: ieval} = await import('@/lib/eval.js');
|
||||
wrapComponent = (componentName, Component) => (
|
||||
ieval(`
|
||||
((Component) => (
|
|
@ -1,12 +1,11 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Interactive extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
return class ControlledInstance extends super.instanceFromSchema() {
|
||||
$$interact;
|
||||
interact(initiator) {
|
||||
const script = this.$$interact.clone();
|
||||
const script = this.interactScript.clone();
|
||||
script.locals.initiator = initiator;
|
||||
script.locals.subject = ecs.get(this.entity);
|
||||
const {Ticking} = script.locals.subject;
|
||||
|
@ -20,20 +19,8 @@ export default class Interactive extends Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
load(instance) {
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.$$interact = this.ecs.readScript(
|
||||
instance.interactScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
},
|
||||
);
|
||||
}
|
||||
static properties = {
|
||||
interacting: {type: 'uint8'},
|
||||
interactScript: {type: 'string'},
|
||||
interactScript: {type: 'script'},
|
||||
};
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Interacts extends Component {
|
||||
instanceFromSchema() {
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Interlocutor extends Component {
|
||||
mergeDiff(original, update) {
|
|
@ -1,6 +1,6 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
import {distribute} from '@/util/inventory.js';
|
||||
import {distribute} from '@/lib/inventory.js';
|
||||
|
||||
class ItemProxy {
|
||||
scripts = {};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user